JavaScript / JXA Q&A

Don’t get me wrong - I think it’s great! For me, I need to be able to say “that thing is kind of like this thing”. So knowing what this is similar to, for instance I know it’s a hammer, and I know how to use a hammer, even if the handle on this one is different, I’m good.

But if someone tries to explain it to me, saying it’s a device that operates on the principal of inertia, mass and acceleration (or whatever), then I’m lost.

So thanks!

1 Like

Rob, many thanks for your tutorial on fold/reduce. Very interesting.

Since you seem to be in an educational mood, I wonder if you could help neophytes like me better understand some of the symbols I've seen you use in your scripts?

From the fold article:

I've often seen you use this notation, but I don't understand it.
For example:
// writePlist :: Object -> String -> IO ()

I did do some reasearch, and found this, but it does not include the double colon ::
Mathematical Symbols

So, if you have the time and interest, an explanation of (or a reference for) the above symbols used in the fold statement would be great!

Thanks.

The type signature

map :: (a -> b) -> [a] -> [b]

would be a brief way of announcing the intention that a function named map

had, as its first argument, another function with the type signature (a -> b)

(a function (a -> b) is taking an argument of any type, and returning a value which may be of a different type)

and as its second argument: an array of values of type a.

Finally it tells us that the output of this function is intended to be an array of values of type b.

(In other words, of any consistent type, which doesn't have to be the same as type a, but will be the same as the output of the (a -> b) function.

JavaScript is not a strongly typed language, but it can still be helpful to use type signatures to communicate clarity about the intended type behaviour of a function.

See for example, the entries in this JS library:

http://ramdajs.com/docs/#chain


If you work with Haskell, in which the compiler can actually make use of the optional type signature, there is a very useful search engine (Hoogle) which allows you to find functions by their type signature rather than by their name.

There is, in fact, a sense in which the type signature is more eloquent and fundamental than the name.

https://www.haskell.org/hoogle/

1 Like

Rob, merged per your request. Please edit as needed.

1 Like

Got another question. Consider this code:

function getKMMacroAndGroupByMacroUUID(macroUUID, plist) {
    plist.forEach(function(group) {
        var macros = group.macros;
        if (macros) {
            var macro = macros.find(function(m) {
                return m.uid === macroUUID;
            });
            if (macro) {
                return {
                    macro: macro,
                    group: group
                };
            }
        }
    });
    return undefined;
}

Obviously this doesn’t work. I’m looking through an array of Groups that may have an array of Macros. I want to find a Macro with a particular UUID (UID), and if I find it, return the Macro and its Group.

I wasn’t really thinking, and I tried to do it with a “return” in the anonymous function that the “forEach” calls, and of course that doesn’t work.

I can get something to work, of course, and I will, but I thought this was probably the perfect time to learn one of those fancy-sounding functions. :slight_smile:

This is what I came up with:

function getKMMacroAndGroupByMacroUUID(macroUUID, plist) {
    var _result = undefined;
    plist.find(function(group) {
        var macros = group.macros;
        if (!macros) {
            return false;
        }

        var macro = macros.find(function(m) {
            return m.uid === macroUUID;
        });
        if (macro) {
            _result = { macro: macro, group: group };
            return true;
        }
        return false;
    });
    return _result;
}

Using “find” allows me to exit the iteration loops immediately if I get a hit. Using a result variable in the outer scope allows the result to be accessible after the loops exit.

Is there a better way? This doesn’t smell too awful.

Looks good.

The only thing that jumps to the eye is case sensitivity in JS names, so for example, I think you may need:

  • group.Macros
  • m.UID

I’m very much in favour of built-in array methods like find.

If you ever wanted to do it more iteratively, the while(i--) idiom is typically the fastest with JS compilers, so one other way might be something like:

(function () {
    'use strict';


    function macroUUIDFound(lstGroups, strUID) {
        var i = lstGroups.length,
            dctGroup,
            lstMacros;

        while (i--) {
            dctGroup = lstGroups[i];
            lstMacros = dctGroup.Macros;

            if (lstMacros && lstMacros
                .find(function (m) {
                    return strUID === m.UID;
                })) {
                return true;
            }
        }
        return false;
    }


    var lstSeln = Application("Keyboard Maestro")
        .selectedmacros(),
        strUID = lstSeln.length ? lstSeln[0] : undefined;


    var lstMacroGroups = strUID ? ObjC.deepUnwrap(
            $.NSDictionary.dictionaryWithContentsOfFile(
                $(
                    '~/Library/Application Support/Keyboard Maestro/Keyboard Maestro Macros.plist'
                )
                .stringByStandardizingPath.js
            )
        )
        .MacroGroups : undefined;



    return strUID ? macroUUIDFound(lstMacroGroups, strUID) : "No macro selected"
})();

You would think that, wouldn't you? That's what I thought also. Except in this case, they're lower case. I suspect this is a mistake on Peter's part, when he returns the result of "getmacros". But let's not tell him, OK? :slight_smile:

But I'm assuming, since you said you're a fan of built-in methods, that you normally wouldn't do that unless speed was an issue, right? 'Cause to me, that feels like going back to the dark ages. :open_mouth: :stuck_out_tongue:

Except in this case, they're lower case.

That's interesting ... I wonder if we are working with different builds ?

(On this system it fails with lower case, and shows Upper in the debugger ... )

that feels like going back to the dark ages.

Absolutely agree. Performance seldom seems a genuine or even perceptible issue in Mac scripting – scripter time is certainly real.

Here's what I'm using (I'm editing this slightly manually, so if it doesn't compile, fix it. :slight_smile:)

var _kme = Application("Keyboard Maestro Engine");
var macros = kme.getmacros({
    asstring: true
});
console.log(macros);

I wonder if the issue is that the key strings can get written out (or have historically been written out) to the .plist in varying cases ?

If that were ‘the case’ it might need a double test …

Heh.

All other instance I've seen with KM plists, to my recollection, have been upper case.

Or perhaps we are reading the plist into JavaScript objects in different ways ?

I am getting 'Macros' and 'UID'

from:

$.NSDictionary.dictionaryWithContentsOfFile

We are doing it differently. Look again. I’m calling Peter’s KME method “getmacros”. I’m not reading from a file.

Here’s a more complete example:

(function Run() {
    'use strict';

    function convertArrayToPlist(array) {
        return ObjC.deepUnwrap(
            $.NSPropertyListSerialization.propertyListWithDataOptionsFormatError(
                $(array).dataUsingEncoding($.NSUTF8StringEncoding), 0, 0, null));
    };

    function getAllKMGroupsAndMacrosArray(kme) {
        var _kme = kme || Application("Keyboard Maestro Engine");
        var _macros = _kme.getmacros({
            asstring: true
        });
        console.log(_macros);
        return convertArrayToPlist(_macros);
    }

    var plist = getAllKMGroupsAndMacrosArray();
    // ...
})()

Worth knowing ...

Perhaps a pity that they differ ?

(Possibly also worth enabling functions to behave sensibly with either parse ?)

(Looking at your original post – I think the source of the plist parse was left open)

@peternlewis - It looks like the data you return from KME’s “getmacros” uses lower-case keys “name” and “uid” (and probably others), whereas the rest of the time, they’re “Name” and “UID”.

Was this intentional?

Speaking of case, you probably know this but 'just in case':

A top level function named run (lower case) has a special status in osascript processing – If it's found, it's treated as the Main().

function run() {
}

To create local variables, avoiding pollution of the global namespace, and making it easier to find your own variables in the debugger, you clearly need to enclose your own name bindings in something, and just using a run() function is a perfectly good way of doing that.

My personal habit is usually to use just an anonymous

(function() {
'use strict';

})();

mainly because it gives me more flexibility in argument handling. (run() gets a special kind of argument in some script launch contexts)

(I often use the arguments of the anonymous module function as a place to hold script options)

But perhaps your capitalized Run is a preferred shorthand for indicating the top level of the module ?

Uh, yeah, that was it...

Nope, just ignorance. Thanks, I'll do that in the future, assuming I remember to change it in the code I keep copying... :slight_smile:

The AppleScript export facility grew out of communication of the macros with KeyCue, so it is compatible with that which is independent of most other things.

1 Like

How Do I Get “name of me” in JXA?

In AppleScript, you can get the name of the currently running script by:
name of me

I can’t seem to find the JXA equivalent. I’ve done extensive searching, and tried the following:

me.name
me.name()

var app = Application.currentApplication()
app.includeStandardAdditions = true

var nameStr = app.me.name()
var nameStr = app.me.name

Any solutions/ideas/suggestions?