JavaScript, once a language which ran only in web browsers, can now be used in a variety of environments. From Keyboard Maestro you can:
JavaScript for Automation (JXA) is a more flexible alternative to AppleScript, with a richer set of default libraries for some basics like regexes and URL handling. Its equivalent of AppleScript 'records' is more forgiving and much easier to use. JXA is the same fast JavaScript Core language that Safari uses, but it is running in an osascript context:
The first script to try in JXA consists of just one word:
this
the result will be a listing of all the pre-defined names in JXA's osascript environment.
We can either run this directly from from one of KM's 'Execute a JavaScript for Automation' actions, or use an osascript command in an Execute Shell Script action.
Later on, you may find it interesting to scan this output list for useful functions, but for the moment, it's enough to simply notice that the JXA JavaScript environment is very different from the set of web page (window and document) references that you find in the Browser JavaScript environments of Chrome and Safari.
Keyboard Maestro's 'Execute a JavaScript in Safari' and 'Execute a JavaScript in Google Chrome' actions are for interacting with web pages, whereas JXA is for automating OS X and OS X Applications.
The simplest scripts evaluate an expression and return its value.
NB KM Maestro can only make use of values returned as (or converted to) a string – a KM variable is always a string of some kind. This means that:
The following all return the same number to KM (in string format, and at a slightly higher level of precision – more decimal points – from JavaScript).
AppleScript | JXA |
---|---|
((sqrt 5) + 1) / 2 | (Math.sqrt(5) + 1) / 2 |
JavaScript's Math object provides a number of numeric functions and constants, in the format Math.functionName([argument, …]
).
In the common JavaScript patterns:
[argument, …]
)
The dot corresponds to Applescript's of
, reversing the sequence of object name and property name.
AppleScript | JXA |
---|---|
count of documents of application "TextEdit" | Application("TextEdit").documents.length |
If we often need to calculate the Golden Ratio, we can attach a name to its computation, and reuse it by that name. Nameable computations are called 'handlers' in AppleScript, and 'functions' in JavaScript.
AppleScript | JXA |
---|---|
on goldenRatio() return ((sqrt 5) + 1) / 2 end goldenRatio class of goldenRatio | function goldenRatio () { return (Math.sqrt(5) + 1) / 2; } typeof goldenRatio |
In both languages, we can call/run/apply a handler/function (that is, get an evaluation of it), by adding a pair of parentheses to its name. (If the function has an input argument, the argument goes between the parentheses).
AppleScript | JXA |
---|---|
on goldenRatio() return ((sqrt 5) + 1) / 2 end goldenRatio goldenRatio() | function goldenRatio () { return (Math.sqrt(5) + 1) / 2; } goldenRatio() |
JavaScript is more flexible with white space than AppleScript – a JS expression can be broken up over several lines, if that makes its structure clearer, while a new line is interpreted as the end of an AppleScript expression.
In JavaScript, a semicolon means 'end of expression'.
In AppleScript, the word return in the handler is optional – if you omit it, the value of the last expression to be evaluated inside the handler will still be returned.
In JavaScript, however, a function only returns what you ask it to. If you don't use the return keyword, the function evaluates to the special value undefined
, and your Keyboard Maestro action will just get an empty string. Always include a return line.
If you are building a well-proportioned temple or table, you will probably want to multiply the Golden Ratio by something else, so a named computation with a variable 'parameter' or 'argument' might make sense.
AppleScript | JXA |
---|---|
on goldenRatio(n) return (((sqrt 5) + 1) / 2) * n end goldenRatio -- For a square of side 8 goldenRatio(8) -- --> 12.944271909999 | function goldenRatio (n) { // n is optional here – defaults to 1 n = n || 1; return ((Math.sqrt(5) + 1) / 2) * n; } // For a square of side 8 goldenRatio(8); // --> 12.94427190999916 |
Both languages allow us to include explanatory comments. AS prefixes comments with two dashes, JS with two forward slashes.
JavaScript is relaxed about whether we supply an argument or not. If we don't, the name n
will have the special value undefined
, which takes on the value false
in a logical expression. Above we are using the JavaScript logical operator OR ( || ) to attach the name n to the value 1 if no argument has been supplied.
In addition to reusable names for particular computations and their arguments, we can also give names to the results of computations. Once a result is stored in a named variable, we can reuse it without having to recalculate it.
AppleScript | JXA |
---|---|
on run {} set phi to (((sqrt 5) + 1) / 2) set phi18 to phi * 18 set phi7 to phi * 7 return phi18 + phi7 end run --> 40.450849718747 | function run() { var phi = (Math.sqrt(5) + 1) / 2, phi18 = phi * 18, phi7 = phi * 7; return phi18 + phi7; } // --> 40.45084971874737 |
In osascript (AppleScript or JXA), run is a special name. A handler or function with that name is called automatically – you don't have to append parentheses to call it, like we did with the goldenRatio computation.
In fact, when we don't assign any names to computations (don't create any AS handlers or JS functions), osascript automatically wraps our our script in a computation which it names run, and immediately calls it.
The following names are all given to the result of a computation, rather than to a computation or argument:
Where AS uses the pattern: set name to resultingValue (and reserves = for testing equality),
JS uses one or more variable declaration statements (and tests equality with ===)
In JS we can either:
Good and simple JXA – temporary local names | Messy and complex JXA – persistent global names |
---|---|
var phi = (Math.sqrt(5) + 1) / 2, phi18 = phi * 18, phi7 = phi * 7; // or var phi = (Math.sqrt(5) + 1) / 2; var phi18 = phi * 18; var phi7 = phi * 7; | phi = (Math.sqrt(5) + 1) / 2; phi18 = phi * 18; phi7 = phi * 7; |
JavaScript gives us a lot of freedom, sometimes more than is helpful. Misspelled variable names, and accidental uses of the same globally visible name in different parts of a script are rich sources of puzzling bugs and wasted time. To use a less liberal but more helpful set of JavaScript rules, we can add the line 'use strict'; at the start of a function. When we do this, JavaScript checks that all of our variables are declared (and therefore 'local' – visible only to their containing functions, and also temporary – vanishing when the evaluation of a function is completed). If an undeclared variable name is detected when the 'use strict'; statement has been added, we are given a helpful warning:
Length of AppleScript 'list' | JavaScript 'array' length |
---|---|
length of {"alpha", "beta", "gamma"} | ['alpha', 'beta', 'gamma'].length |
to follow …
to follow …
to follow …
to follow …
to follow …
to follow …
to follow …
to follow …
to follow …
to follow …