Learning & Using AppleScript & JavaScript for Automation (JXA)

Thanks, Chris.

This is for users of AppleScripts, not developers.
Most of them just want to use the script I provide without understanding any of the tech stuff. So I need it simple, not comprehensive. Step 1, 2, 3. :smile:

This is the best have found so far, but it's a bit dated:
How to Install an AppleScript

I guess if neither you nor Rob know of anything better, I'll just bite the bullet and write one. I'll probably just publish it as a public Evernote note (accessible if you have the URL, but isn't found by search engines).

Hey JM,

That's very poor indeed.

Worth noting is:

But something more generic and clear is desirable.

The Apple Script-Menu is now enabled/disabled via the AppleScript Editor.app preferences.

I always recommend that people use FastScripts in preference to it though. As far as I know it will work with unlimited scripts in free-mode, but only 10 Hot-Keys will be allowed. Paid-mode enables unlimited global and app-specific Hot-Keys.

You could test that for me if you would — I don't want to disable my license and risk losing anything.

You want an up-to-date picture of the Script Editor, and you'll want to show what the Apple Script-Menu (or FastScripts) looks like in the Finder menu-bar.

I would also mention Keyboard Maestro at this point for people who might want to do more than run AppleScripts (and shell scripts).

I usually provide a script to open the scripts-folder, so I don't have to field too many questions — and the user has a nice simple little toy to play with in the Script Editor.

---------------------------------------------------------------------
# Assemble relevant folders & paths.
---------------------------------------------------------------------
set userLibraryFolder to path to library folder from user domain
set userAppleScriptFolder to (userLibraryFolder as text) & "Scripts:"

---------------------------------------------------------------------
# Open the user-scripts folder in the Finder
---------------------------------------------------------------------
tell application "Finder"
  activate
  try
    open (userAppleScriptFolder as alias)
  on error
    make new folder at userLibraryFolder with properties {name:"Scripts"}
  end try
end tell
---------------------------------------------------------------------

It's been a while since I've written this stuff up, but I've done it 300 times over 20 years — and yes it gets tedious.

I used to have a pretty good canned writeup before I got burgled some years ago.

-Chris

1 Like

That's very cool. It wouldn't take much more to turn it into a full installation script, allowing the user to select the script file and then put it in the right location.

The final step would be to turn it into an app.
Or am I reinventing the wheel here.
Should I just get a DMG creator?

Applets these days have to be code-signed to run on someone else's machine.

I used to send people double-clickable installers or drag&drop installers for things when they were computer-challenged, but I haven't messed with that for a few years.

I really need to renew my developer license, so I can code-sign apps — but the last time I tried Apple messed with me and ticked me off. I need to get over it and get on with it.

What specifically did you have in mind?

I own DropDMG and have kept it current pretty much since it came out, but it doesn't have any built-in install capability. To do that you'd have to build an installer pkg, and those probably have to be code-signed too.

I'm not sure how a applet on a DMG would behave. I suppose I could make up a dummy and send it to you to let you try.

-Chris

It's up to you. If you want to check it out, I'll be glad to test it for you.

But with all the code signing and stuff, I think I can just add a few lines of code to your script to allow the user to select the script file, and then the script can put it in the right place (or propose a place).

###Best Folder for Temp Files

What is the best location on the Mac to save temp files to from an AS or JXA?
I need this to work on everyone's Mac, not just mine.

I've done a bit of research, and there are a variety of suggestions.
The key to me is that I want a folder that will be automatically cleared on a frequent basis, so I can just create the temp file and don't have to worry about cleanup.

Can any of you confirm this, or recommend something different?

/tmp -- (which links to /private/tmp) is the standard *nix place to put files that you don't care about past the current run of the program.

The advantage with this adjustment is that in /tmp files older than a week is automatically deleted (and at every restart).

In AS how about:

path to temporary items from user domain

or JS:

var a = Application.currentApplication(),
	sa = (a.includeStandardAdditions = true && a),

	lstOptions = [
	// --> Path object
	sa.pathTo('temporary items', {
			from: 'user domain'
		}),

	// --> Unix string 
	sa.pathTo('temporary items', {
			from: 'user domain'
		}).toString(),

	// --> HFS string
	sa.pathTo('temporary items', {
			from: 'user domain',
			as: 'string'
		})
	];

lstOptions;
1 Like

Thanks, Rob.
That was one of the several I saw in my research.
But what was not, is not, clear is if this folder is auto-cleared?

Do you think this is better than /tmp ?
If so, why?

No particular view : -) if things do need to be cleared, I just tend to clear them.

In my use case I’m building a HTML file that needs to survive exit of the script for display purposes. So that’s why I need the auto-clear.

One solution to this is simply to have your script clear the file at the start. So yes, you’ll leave the file hanging around, but only one of them.

1 Like

This is what I usually do.

If I need to use reuse a temporary file in a workflow and don't want it rewritten by any old script I'll give it a unique name.

set curlDumpFile to (path to temporary items from user domain as text) & "curlDump.txt"
set tempFile to (path to temporary items from user domain as text) & "temp.txt"

The temp.txt file is used by any script when I don't care about the contents after the script is finished. Writing to it destroys any previous content — unless I deliberately append to the file.

Something like curlDump.txt I might want to be able to examine and do something with in-between the the times I write to it.

-Chris

Hey Chris, thanks again for a cool handler.

The handler works OK, but if you need to call it from inside a tell block, I found you need to use the "my" command, or AS throws an error.

The "my" command always works, so that's probably the best practice to use.

my setKMVar({varName:"Test1", varValue:"I am NOT nuts!"})

Hey JM,

I wouldn't call it a "best practice".

In a handler-call within a tell-block this would generally be:

my <hander-call>

or

<hander-call> of me

but

Tell me to …

or

Tell me
…
end tell

also work — and might be more clear in some cases.

I generally prefer the latter version, because I find it more readable.

These methods are only necessary to break you out of a tell-block or another handler and address the top-level of the script where the handlers are defined.

When you're already at the top-level of the script there's no need to reference it.

It is very easy to forget to add the routing "of me" or "my" to handler-calls when they're needed, and then of course you get an error.

------------------------------------------------------------
# Works fine at top-level of script.
y("Sometimes you feel like a nut!")

tell application "TextEdit"
  # TextEdit knows nothing of command y(b).
  # The handler-call y(…) must be told where to look for itself.  (Top-level of script.)
  # The avialable forms of re-scoping the call:
  
  tell me
    set theString to y("Sometimes you feel like a nut!")
  end tell
  
  tell me to set theString to y("Sometimes you feel like a nut!")
  
  set theString to my y("Sometimes you feel like a nut!")
  
  set theString to y("Sometimes you feel like a nut!") of me
  
end tell

------------------------------------------------------------
--» HANDLERS (can have dependencies on other handlers)
------------------------------------------------------------
on x(a)
  set upperCasedString to do shell script "echo " & (quoted form of a) & " | tr '[:lower:]', '[:upper:]'"
  return upperCasedString
end x
------------------------------------------------------------
on y(b)
  return x(b)
end y
------------------------------------------------------------

-Chris

Since using " my handlerFunction" always works, it seems to me that if one gets into the habit of always using "my", and therefore never fails, then it would be a "best practice".

I'd rather not have to remember when it is necessary and when it is not.

To each his own. :smile:

Rob,

Thanks again for your JXA examples.

I think I'm missing something basic, because when I try to run any of the kmVar scripts, I immediately get a runaway script -- spinning beachball, and have to force quit.

Also, I don't understand the purpose of enclosing all of the scripts in a function run( ).

Can you please point me in the right direction? :smile:
Thanks.

The kmVar scripts ?

Is it JXA you have in mind, or browser JavaScript ?

(Probably best to post an example action/macro)

More broadly, the first script to run when exploring any Javascript context - JXA or browser, is:

this

For JXA, for example, just create a Script Editor script consisting of that single key word and run it to see the result.

What you will see is that a JavaScript environment, name space, or context (call it what you will):

  1. Exposes rather a lot of global names
  2. Allows you complete freedom to pollute/confuse the global name space with any variables of your own, or be confused if you trip over an unexpected global variable name clash.

This is why it’s generally good practice to wrap your work in a containing function, localising your own variable declarations. JXA offers the standard osascript run() function (on run in AS), but a more general wrapping pattern, which will work in browser JavaScript or JXA is an immediate execution of an “anonymous” (ie unnamed) function.

(function () {
    var ...

    return ...
})();

Where the final pair of (potentially empty) parentheses immediately call the function that has just been defined.

(If you don’t do this, you may be inadvertently polluting the global name space with persistent name bindings, which may cause unexpected or unintended effects downstream)

Rob, thanks for the additional insight.
I have not seen the important point about always using a function clearly made anywhere else. I guess that is one, of many, area(s) that is much different from AppleScript.

As indicated in the link to your post in my quote, what I was talking about is is running exactly your code, in the above "[slightly expanded example][1]" post, in the Apple Script Editor.

When I just paste in your code, compile and run I get the spinning beachball, and have to force quit the Script Editor.
After some experimentation last night, I found I had to add these lines of code at the top, above your function run():

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

Then the script ran fine. :smile:

I noticed that you sometimes put current app statement inside of the function like this:

function run() {

  var a = Application.currentApplication(),
    sa = (a.includeStandardAdditions = true, a);

Several questions to help clear the fog in my head:
Running JXA in the Script Editor:

  1. Do you ever put any statements outside of the function run(), or is your code always inclosed within it?

  2. Is the function run() always executed without calling it?

  3. What is the advantage of creating/using the sa variable?
    (I'm sure there is one or you wouldn't have done it)

Thanks. I really appreciate your help and time.
Sorry for all the newbie questions, but now that I have upgraded to Yosemite I am trying to move from AppleScript to JXA as soon as I can.

Oh, that reminds me. Do you know of any books on JXA specifically?
We've discussed books on JS before, and there are a number of those available.
But in my search last night I could not find any dedicated to JXA.

TIA.
[1]: http://

  1. You could certainly use the pattern of treating the run() handler as a kind of main() function, with other functions declared outside (subject to the earlier caveats about keeping JavaScript’s global namespace simple).

  2. I think osascript does tend to treat unwrapped code as implicitly contained by a run handler. One reason, perhaps, for preferring the anonymous function call, though the stakes are certainly not high on short and simple scripts

  3. The sa variable just expresses a personal preference for functional rather than imperative patterns, and for avoiding the mutation of variables after the name declaration stage. The sa name helps to remind me that I am looking at an instance of Application in which .includeStandardAdditions has been set to true. Not a necessity – just one of several possible approaches to composing code and using names.

Thanks, Rob.

So is there ever a need, a use case, to put code outside of the run() handler?