Attempted JXA of Custom Metadata does not work

I have a JXA script for custom metadata which @chrillek helped with in the past. It works very well though I am trying expand my JXA/Javascript abilities and learn different methods to accomplish something.

I thought this script would work to retrieve a specific custom metadata field from each selected record. But that is not what I am getting. Any help on what I am doing wrong?


(() => {
     const app = Application("DEVONthink 3");
    let attorneyEmails = [];

    for (let i = 0; i < app.selectedRecords.length; i++) {
        let attorneyEmail = app.selectedRecords[i].customMetaData.attorneyemail1;
        attorneyEmails.push(attorneyEmail);
    }
    return(attorneyEmails);
})();

What are you trying to achieve? To me, it looks ok, but I don’t understand the return … at the end – there’s no caller for that anonymous function the array can be returned to.

To ensure that the code basically does what you want, use console.log() with your array as parameter and see what that gives you,

Thanks

The reason for the return is that I am running the script in TextExpander and with other scripts if I use a return then that becomes the output of the TextExpander macro.

Here it is in Script Editor in the original form I posted:

And here it is if I replace return with console.log:

When I see that error, I try if it goes away when I append parentheses somewhere, thus taking the value of a object specifier. Since I’m not at my desktop today, I can’t give better advice.

Alternatively, you could use the method to get a specific metadata value (getCustomMetadata or so)

For that purpose this kind of thing seems to be working here:

(() => {
    "use strict";

    return Application("DEVONthink 3")
    .selectedRecords.customMetaData()
    .flatMap(x => {
        const v = x.mdattorneyemail1;

        return v ? [v] : [];
    })
    .join("\n");
})();
2 Likes

So customMetadata is an object specifier requiring () to get is value into JavaScript.
Nice method-chaining example!

That works - huge help -thanks.

DId I make an error or is this a bug? I cannot get my original version to work adding () anywhere

No bug.

Which part puzzles you ?

One thing to notice is that – here at least – the meta data key that appears in the GUI is prefaced with “md” in the record’s metadata object.

Ahah! Did not initially notice that. Huge thanks to both of you.

So I had to add () and also add md now the original version (see below) works and I thank you for the alternate version which I will study for further understanding as well.

Huge thanks to both of you.


(() => {
     const app = Application("DEVONthink 3");
    let attorneyEmails = [];

    for (let i = 0; i < app.selectedRecords.length; i++) {
        let attorneyEmail = app.selectedRecords[i].customMetaData().mdattorneyemail1;
        attorneyEmails.push(attorneyEmail);
    }
    return(attorneyEmails);
})();

Generally, wherever you find yourself writing

let ... = []
for (let i = 0; i < x.length; i++) {
     ...push()
} 

it’s always possible (and less accident-prone, as well as easier) to simplify to
a .map expression which directly defines the array you want, for example:

(() => {
    "use strict";

    const app = Application("DEVONthink 3");

    return app.selectedRecords().map(
        record => record.customMetaData().mdattorneyemail1
    )
    .join("\n");

})();
2 Likes

Thanks again - yes that works as well

Some explanations for those not (yet) initiated to the beauties of JavaScript:

In JavaScript, method calls can be chained

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

can be written as a one-liner

const records = Application("DEVONthink 3").selectedRecords();

since Application() returns an Application object providing the method selectedRecords().

Similarly, since selectedRecords() returns an Array object (a list in AppleScript parlance), you can use the method map on this object. Which leads to

const newArray = Application("DEVONthink 3").selectedRecords().map(/* callback here */)

map is executed once for every element of the array and passes this array element to the callback function given as a parameter to map. Thus,
selectedRecords.map(record => record.name())
returns a new array containing the names of all selected records.

In JXA, however, this can be short-circuited to
selectedRecords.name(), avoiding the map call. This is possible because selectedRecords is an Object Specifier. They support chaining of attributes. E.g. mail.mailboxes.name() might return the names of all the mailboxes.

@winter used this in the first script they posted like so

The only new thing in this snippet is flatMap: like map it returns an array, but it “flattens” the values returned by the callback by one level, so that [v] becomes v in the final array. The final join() converts this array to a multi-line string, containing one element per line

4 Likes

which enables us to filter, without requiring an additional call to Array.filter.

When we use .flatMap, we wrap the values we want to retain in Array brackets, and we map the values we want to discard to the empty Array [], which disappears in the flattening concatenation.

  • .map defines a new array, of the same length as the input list from which we derive it.
  • .filter defines a new array, potentially shorter than the input array, because it includes only items which return true from a boolean predicate function.
  • .flatMap combines the roles of .map and .filter in a single pass, given a function which wraps its output as a (possibly empty) Array
  • .reduce is totally flexible, and all of the above (.map, .filter and .flatMap) can be defined in terms of it.
  • .reduceRight ditto, but it works through the input Array from right to left.
3 Likes

Thanks - what are the best books or websites you recommend for learning moderate to advanced Javascript techniques such as these?

In the past, I liked O’Reilly books best. I wouldn’t know how they compare to others nowadays. IIRC, A-Press had (has?) some JS offerings, too. Those were more in the “advanced to medium” range, I think.

Unfortunately, it’s ages ago that I had a look in any JavaScript book. For reference, the MDN (Mozilla Developer Network) is best, in my opinion. Stay away from W3Schools.

2 Likes

Some examples of using .reduce (to define stand-alone map, filter, flatMap, flat, etc):

Click disclosure triangle to see JavaScript
(() => {
    "use strict";

    // map, flatMap, filter and flat
    // all defined in terms of Array.reduce.

    const main = () => {
        const xs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

        return [
            map(x => 2 * x, xs),

            filter(even, xs),

            flatMap(
                x => !even(x) ? [2 * x] : [],
                xs
            ),

            flat([[1, 2, 3], [4, 5, 6], [7, 8, 9]]),

            length(xs)
        ];
    };


    // Definitions in terms of Array.reduce

    const map = (f, xs) =>
        xs.reduce(
            (accumulator, x) => [...accumulator, f(x)],
            []
        );


    const flatMap = (f, xs) =>
        xs.reduce(
            (accumulator, x) => [...accumulator, ...f(x)],
            []
        );


    const filter = (p, xs) =>
        xs.reduce(
            (accumulator, x) => p(x)
                ? accumulator.concat(x)
                : accumulator,
            []
        );


    const flat = xs =>
        xs.reduce(
            (accumulator, x) => accumulator.concat(x),
            []
        );


    const length = xs =>
        xs.reduce(a => 1 + a, 0);


    // For tests
    const even = n =>
        0 === n % 2;

    return JSON.stringify(
        main(), null, 2
    );
})();
1 Like

‘Effective JavaScript’ might be worth a look.

1 Like

Thanks

https://www.amazon.com/Effective-JavaScript-Specific-Software-Development-ebook/dp/B00AC1RP14/ref=tmm_kin_swatch_0?_encoding=UTF8&qid=&sr=

Also available as ePub, it seems. Not at Amazon, of course

A problem with JavaScript books is that many assume a web page context, and start to deal with things which are not part of the JavaScript language, and really part of the Document Object Model
(only defined in browser-embedded JavaScript interpreters)

In the case of the free Eloquent JavaScript, for example, the first 9 chapters are quite useful and relevant to JXA, but not the remaining chapters.

1 Like