Very Basic JXA Question

I am playing with some ideas to expand my use of JXA for scripting.

The very brief script below is intended simply to explore how I can assign names and other metadata to a file in DT3.

I was hoping this example would reassign the filename of the first item in the list of selected items. Script Editor gives me “Error -2700 Script Error.” Keyboard Maestro give the error that I am assigning to the incorrect type.

What am I missing?

const app=Application("DEVONthink 3");
const names = app.selectedRecords.name();
names[0]='test';

Happens on the second run over here, try var instead of const.

What am I missing?

The JavaScript interpreter has a global namespace which persists between runs.

Once you have defined names in it, the definition will still be there on your next script run. Potentially creating confusion.

var is now deprecated (let is less complex and tricky, and now preferred), but neigher var nor let avoid the problem here of polluting the global namespace.

(names and app will retain those definitions in the global namespace after your script is over, if you use let/var, and could prove confusing later)

The trick is to wrap your code in its own local and temporary name-space, protecting the global definitions, and protecting subsequent script runs.

You can do this either with an IIFE, which has the advantage of working in any JavaScript context, and is a good habit to learn:

(() => {
    "use strict";

    const app = Application("DEVONthink 3");
    const names = app.selectedRecords.name();

    names[0] = "test";

    return names[0];
})();

or with a run function, which is fine, but JXA (osascript) only:

function run() {
    "use strict";

    const app = Application("DEVONthink 3");
    const names = app.selectedRecords.name();

    names[0] = "test";

    return names[0];
}

PS, "use strict"; just switches on more helpful error messages in case anything goes wrong.

1 Like

@winter has already explained the const issue. That’s why I always use an anonymous self-executing function to run my JS code :wink:
Also, your assignment will not change the name of the first record to “test”. Here’s what happens:

  • app.selectedRecords gets an array of Object Specifiers, namely the selected records
  • app.selectedRecords.name gets an array of Object Specifiers, namely the names of the selected records
  • app.selectedRecords.name() does a get on these names, which results in an array of Strings.
  • Now you assign a new value to the first element of this array, which works fine. But it will not change the name of the first record because the first element of the array of Strings will not have any connection to the records. It does not contain references (or Object Specifiers), but values. So to say.

What would work is
app.selectedRecords[0].name = "test"
because there name is an Object Specifier, and assigning to it is equivalent to a set operation.

The problem here is that JXA uses confusing syntax: () gets a value, and assignment to the Object Specifiers sets its value. If they’d used set and get, it would be clearer.

2 Likes

Thank you both @chrillek and @winter

Amazing how such a simple example can teach such fundamental concepts - that is a huge help

Final question… you helped me once before in addressing Custom Metadata here Custom MetaData "Can't Convert Types" Using JXA - #16 by rkaplan - I am trying to apply that concept to set a CustomMetadata Field but nothing I try works

I have tried various permutations around this:

app.selectedRecords[0].customMetaData['mdattorneyemail1']="test2";

And I am getting errors including incorrect type and syntax error. What would be the correct syntax to assign a particular custom metadata field?

I’d use addCustomMetadata because I know that it works. But that’s not a direct answer to your question. What you could try if you want to change the property directly:

  • get the customMetaData array
  • change its mdattorneyemail1 field to “test”
  • and then set the customMetaData property again.

Something like that:

const cmd = r.customMetaData();
cmd['mdattorneyemail1'] = 'whatever';
r.customMetadata = cmd;

I didn’t try that approach, though.

Attempt at an explanation: app.selectedRecords[0].customMetadata is an Object Specifier, but app.selectedRecords[0].customMetadata['md…'] is not, but only a string (or number or whatever). JXA is probably getting confused because everything kind of “looks” like an Object Specifier, and then it tries to call its set method, which doesn’t work. Sounds a bit far-fetched, perhaps.

Thanks for the ideas. Changing the property directly gave some errors.

addCustomMetadata seems straightforward. However this gives the error “Can’t convert types”:

(() => {
    "use strict";

    const app = Application("DEVONthink 3");
    

    app.addCustomMetaData("Just testing", {for: "attorneyemail1", to:  app.selectedrecords[0]});   

    return "test";
})();

And same error when I try it the other way:

(() => {
    "use strict";

    const app = Application("DEVONthink 3");
    

    test =  app.getCustomMetaData( {for: "attorneyemail1", from:  app.selectedrecords[0]});   

    return test;
})();

Typo. The property is selectedRecords, and case is relevant in JavaScript.

If you run the script in Script Editor, you’ll immediately spot these things (not because the program is so good, but:) You can have it display the Apple Events it sends and receives, and then you’ll see that selectedrecords[0] is passed as null:

app = Application("DEVONthink 3")
	app.getCustomMetaData({for:"betrag", from:null})
		--> Error -1700: Types can't be converted

If it were a good program, it would immediately tell you that app doesn’t have a property selectedrecords when “compiling”. But, helas … it’s not even compiling anything but just running a basic syntax check.

Excellent - those both work and are great teaching examples to apply other methods in the scripting directory.

Huge thanks.

(() => {
    "use strict";

    const app = Application("DEVONthink 3");
    

    app.addCustomMetaData("Just testing", {for: "attorneyemail1", to:  app.selectedRecords[0]});   

    return "test";
})();
(() => {
    "use strict";

    const app = Application("DEVONthink 3");
    

     let  test =  app.getCustomMetaData( {for: "attorneyemail1", from:  app.selectedRecords[0]});   

    return test;
})();

Is there any alternative IDE/compiler that can be used for JXA instead of Script Editor and which is more informative? WebStorm maybe - or is that overkill just for scripts?

AFAIK: Not. The main issue here is (in my opinion) that Apple does not provide for introspection of its objects. They are just black boxes. While you can step through a JXA script in Safari (add debugger at the point where you want Safari to take over), it is of very little help for anything related to application scripting (for the reason mentioned before).

You’d see the same problem with WebStorm (which would not be able to run your scripts because it doesn’t know how to call into OSA!). So, I use plain old config.log to “debug” my code.

1 Like

Well, you can activate the Safari JS debugger for JXA scripts. I don’t use it much, but it can be helpful.

Only for the JS code outside of application scripting, since even Safari does not provide any introspection for application objects, like DT records. So, it’s really quite limited. Running the script in Script Editor can sometimes provide more insight, since you can follow the Apple events and responses there.

1 Like