Scripting alternatives for TP3: AppleScript and JavaScript for Applications

UPDATED:

These examples were originally written in a pre-release version of the scripting API, and have now been updated to use the newer editor.selection.selectedItems property.


The native scripting language of TP3 is JavaScript, but you can run TP3 scripts either from AppleScript or (in Yosemite onwards) from JavaScript for Applications.

Each has its advantages:

  • launching TP3 JS from AppleScript may allow you to work in a more familiar environment, continuing to use AppleScript for integration with other applications
  • running it from JavaScript for Applications simplifies things a bit – just one language on the page, allowing proper syntax highlighting throughout while you are editing, and there is no need to escape special characters. (JS quoted within AppleScript needs a backslash before newlines and tabs, and requires care with the use of single and double quote marks)

( for bigger or more complex scripts, JavaScript for Applications, from El Capitan onwards, also gives you the use of Safari’s powerful JavaScript debugger, which does make it a lot easier to see exactly what is going on )

For reference, here is a translation, from AppleScript into JavaScript for Applications, of the Getting Started AppleScript example at https://jessegrosjean.gitbooks.io/taskpaper/content/scripting.html

Original AppleScript version, running in Script Editor:

Running the same TP3 code from JavaScript for Applications:

(The language selector at top left of Script Editor has been set to JavaScript)

JavaScript also lets us work through the items of a list with .forEach() (dropping some of the fiddlier details)

or, simplest of all, just translate one kind of list straight into another, with .map()

Code samples:

From AppleScript:

tell front document of application "TaskPaper"
    evaluate script "function(editor, options) {
  
    var items = editor.selection.selectedItems;
     
   var itemStrings = [];
     
   for (var i = 0; i < items.length; i++) {
      itemStrings.push(items[i].bodyString);
    }

    return itemStrings;
  }"
end tell

From JavaScript for Applications (JXA):

function TaskPaperContext(editor, options) {

  var items = editor.selection.selectedItems;
  
  var itemStrings = [];

  for (var i = 0; i < items.length; i++) {
    itemStrings.push(items[i].bodyString);
  }
  
  return itemStrings;
}

Application("TaskPaper").documents[0].evaluate({
  script: TaskPaperContext.toString()
})

from JXA using .forEach()

function TaskPaperContext(editor, options) {

  var itemStrings = [];

  editor.selection.selectedItems.forEach(
    function (item) {
      itemStrings.push(item.bodyString)
    }
  );

  return itemStrings;
}

Application("TaskPaper").documents[0].evaluate({
  script: TaskPaperContext.toString()
});

and finally, from JXA using ‘.map()’

function TaskPaperContext(editor, options) {

  return editor.selection.selectedItems.map(
    function (item) {
      return item.bodyString;
    }
  );
}

Application("TaskPaper").documents[0].evaluate({
  script: TaskPaperContext.toString()
})
2 Likes

Much cleaner example… I’m updating my docs now! :smile:

1 Like

I am trying to run the first example script from above (the one that runs from Applescript context), but it results in the following error.

Any idea what the problem is?

"TypeError: editor.getSelectedItems is not a function. (In 'editor.getSelectedItems()', 'editor.getSelectedItems' is undefined)


evaluateScript@file:///Applications/TaskPaper.app/Contents/Resources/dist/birch.js:50205:16

Use the Help > SDKRunner to debug"

Good catch … the Preview API was a little different at that time.

It would now be:

tell front document of application "TaskPaper"
	evaluate script "function(editor, options) {
  
    var items = editor.selection.selectedItems;
     
   var itemStrings = [];
     
   for (var i = 0; i < items.length; i++) {
      itemStrings.push(items[i].bodyString);
    }

    return itemStrings;
  }"
end tell

http://guide.taskpaper.com/OutlineEditor.html
http://guide.taskpaper.com/Selection.html

Thanks. So, possibly a related question.

I am trying to put 2 and 2 together by running a modified version of @jessegrosjean’s Javascript here:

Basic script to add selected text to TaskPaper 3 “Inbox” - #2 by jessegrosjean

but from the Applescript context using your example above.

Here is a portion of Jesse’s script that I am trying to run from the Applescript context (as a foundation for more to be added):

tell front document of application "TaskPaper"
evaluate script "function(editor, options) {

var TaskPaper = Application('TaskPaper')

}"
end tell

But, I get the following error:

"ReferenceError: Can't find variable: Application


evaluateScript@file:///Applications/TaskPaper.app/Contents/Resources/dist/birch.js:50205:16

Use the Help > SDKRunner to debug"

Is this also an API change, or am I not using evaluate correctly?

In case you are wondering, I am using the Applescript context because this will be a script that runs from within a FileMaker script, and FileMaker only appears to support Applescript at this time.

The Application object is present in the JavaScript for Automation and AppleScript context, but its not an object known to TaskPaper’s internal JavaScript context.

What do you want the Application object to give you in this particular case ? You may be able to:

  • Get the data/effects directly from the editor object that is available in the TaskPaper evaluation context, or
  • Do some JXA / Applescript work outside that context, and pass the results in through the options object.

Maybe the simplest thing would be to work through a small example ? Anything particular that you have in mind for the next stage ?

As far as what I am trying to achieve, FileMaker can output text to an Applescript variable, represented by theText in the script below. I am trying to pass that variable into a modified version of Jesse’s script so the text from FileMaker can be added to TaskPaper.

This code gives an idea of what I am trying to do, but it does not work.

set theText to "Hello, World!"

tell front document of application "TaskPaper"
	evaluate script "function(editor, options) {

var TaskPaper = Application('TaskPaper')

// Send it to TaskPaper Inbox
TaskPaper.documents[0].evaluate({
	script: TPContext.toString(),
	withOptions: {text: theText }
});

function TPContext(editor, options) {
	var outline = editor.outline;
	var inbox = outline.evaluateItemPath('//Inbox:')[0];
	var items = ItemSerializer.deserializeItems(options.text, outline, ItemSerializer.TEXTMimeType);

	if (!inbox) {
		inbox = outline.createItem('Inbox:');
		outline.root.insertChildrenBefore(inbox, outline.root.firstChild);
	}
	
	inbox.insertChildrenBefore(items, inbox.firstChild);
}

  }"
end tell

It returns the error that I was asking about in my previous post;

"ReferenceError: Can't find variable: Application


evaluateScript@file:///Applications/TaskPaper.app/Contents/Resources/dist/birch.js:50205:16

	Use the Help > SDKRunner to debug"

Any help is appreciated!

Just needs a bit of disentangling I think, and may illustrate the relative ease of doing it all from JavaScript rather than nesting JS in AS.

You have got one evaluate nested inside another there. All we need is one, feeding it any data through the options object.

First from JavaScript:


function TPContext(editor, options) {
    var outline = editor.outline;
    var inbox = outline.evaluateItemPath('//Inbox:')[0];
    var items = ItemSerializer.deserializeItems(options.text, outline, ItemSerializer.TEXTMimeType);

    if (!inbox) {
        inbox = outline.createItem('Inbox:');
        outline.root.insertChildrenBefore(inbox, outline.root.firstChild);
    }
    
    inbox.insertChildrenBefore(items, inbox.firstChild);
}


var theText = "Hello World from JS!";

// Send it to TaskPaper Inbox
Application("TaskPaper").documents[0].evaluate({
    script: TPContext.toString(),
    withOptions: {text: theText }
});

and then, for contrast, from AppleScript:

set strFn to "
function TPContext(editor, options) {
    var outline = editor.outline;
    var inbox = outline.evaluateItemPath('//Inbox:')[0];
    var items = ItemSerializer.deserializeItems(options.text, outline, ItemSerializer.TEXTMimeType);

    if (!inbox) {
        inbox = outline.createItem('Inbox:');
        outline.root.insertChildrenBefore(inbox, outline.root.firstChild);
    }
    
    inbox.insertChildrenBefore(items, inbox.firstChild);
}
"


set theText to "hello again from AS !"

tell front document of application "TaskPaper"
    evaluate script strFn with options {|text|:theText}
end tell
1 Like

PS and, of course, if the evaluated function returns a value, that data gets passed back to the AppleScript or JavaScript for Automation context (as long as it is of a simple data type that is understood by both sides – apart from numbers strings and booleans, you can also pass back simple JS objects (parsed in AppleScript as records)).

function TPContext(editor, options) {
    var outline = editor.outline;
    var inbox = outline.evaluateItemPath('//Inbox:')[0];
    var items = ItemSerializer.deserializeItems(options.text, outline, ItemSerializer.TEXTMimeType);

    if (!inbox) {
        inbox = outline.createItem('Inbox:');
        outline.root.insertChildrenBefore(inbox, outline.root.firstChild);
    }
    
    inbox.insertChildrenBefore(items, inbox.firstChild);
    
    return true;
}


var theText = "Hello World from JS!";

// Send it to TaskPaper Inbox and get back a result
varReturned = Application("TaskPaper").documents[0].evaluate({
    script: TPContext.toString(),
    withOptions: {text: theText }
});

varReturned // true
1 Like

Thank you very much, @complexpoint! This works and is very helpful in helping me understand this all a bit more. Now please allow me some time to wrap my head around it. :wink:

1 Like

( Updated the ‘hello world’ AppleScript and JavaScript examples at the start of this thread for the release version of the TaskPaper 3 API )