Ok, I’ve been beating my head against using JXA (that’s “JavaScript for Automation” for those who don’t know) for a while. Something I think should be trivially simple, like say iterating over all the markdown documents in a given database, isn’t trivially simple to arrive at–for me at least. Like so many things, it is trivially simple once you know precisely how to do it. So I’m going to post here from time to time (if I can remember) whenever I’ve got something worth sharing.
Case in point for today: iterating databases and records. I was able to find examples on these forums already about how to use the selected records in the application, and that’s great! I could do a lot of things by selecting a few particular documents in the UI and then kicking off a script. But what if I want to process all the records in a given database? Or all the records in every database? That’s what today’s post is about. So let me start with the former. Take a look at the following code:
var numRecords = 0;
var iterationDepth = 0;
function processRecord(r) {
console.log(" ".repeat(iterationDepth) + r.name() + " (" + r.type() + ")");
numRecords++;
}
function iterateRecords(records) {
iterationDepth++;
for (var i = 0; i < records.length; i++) {
processRecord(records[i]);
iterateRecords(records[i].children);
}
iterationDepth--;
}
(() => {
const app = Application("DEVONthink 3")
app.includeStandardAdditions = true;
const db = app.databases["YOUR-DATABASE-NAME-GOES-HERE"];
iterateRecords(db.records());
console.log("Processed a total of " + numRecords + " records");
return numRecords;
})()
That’s a little framework script that nicely iterates over all the records in a given database and prints them out with a bit of spacing to indicate hierarchy. For those who don’t understand recursion, let me note that each record in a given database may or may not have “children” depending on what kind of thing it is. A non-empty group, for example, will have children whereas a markdown document won’t–at least as I understand it. If you just start looping over the records in a database, you’ll only get the stuff at its root level and not the children, so iterating like this is helpful if you want to process all of the records in a database. You may easily copy/paste this code and then edit the “processRecord” function to do your own thing easily enough.
But what about if you want to process all of your databases like this? Sure, you could change the name of the database in the script and run it once for each of your databases, that would work just fine. I don’t know about you, but I already have dozens of DEVONthink databases and would rather not go through that much hassle. That’s where a script more like the following can be helpful:
var numRecords = 0;
var iterationDepth = 0;
function processRecord(r) {
console.log(" ".repeat(iterationDepth) + r.name() + " (" + r.type() + ")");
numRecords ++;
}
function iterateRecords(records) {
iterationDepth++;
for (var i = 0; i < records.length; i++) {
processRecord(records[i]);
iterateRecords(records[i].children);
}
iterationDepth--;
}
(() => {
const app = Application("DEVONthink 3")
app.includeStandardAdditions = true;
var numDatabases = 0;
app.databases().forEach(db => {
var startNumRecords = numRecords;
console.log("Processing database " + db.name());
iterateRecords(db.records());
numDatabases++;
console.log(db.name() + " had " + (numRecords - startNumRecords) + " records");
})
console.log("Processed " + numDatabases + " databases with a total of " + numRecords + " records");
return numRecords;
})()
That script iterates databases too and logs totals of records for each individual database processed as well as the total for all. Be advised: that one can take a while to run if you have as much data as I do. I hope that saves somebody else some time. Cheers!