×

URL: Call

The strength of Omni’s productivity suite of applications becomes apparent when they interact with each other through the use of Omni Automation. Imagine these scenarios, where through the use of scripts you can:

The following documentation details the use of the Omni Automation functions that enable direct communication between Omni applications. Materials regarding the interaction of Omni application with 3rd-party applications is documented elsewhere on this site.

Script Encoding Functions

Omni Automation uses script URLs to send Omni Automation scripts between its applications and from web-based documentation to its applications. As an example, this website often presents links and buttons that when triggered will execute Omni Automation example scripts on your behalf.

Omni app-to-app scripts incorporate the use of specialized functions that are used to convert a JavaScript function into an Omni Automation script URL that when called will pass the function to the targeted Omni application for execution.

Here are the two functions of the URL class designed to be used for Omni app-to-app communication:

Here is an example of the use of the tellFunction() function:

Omni Automation Script URL


function targetAppFunction(){console.log("test");return "completed"} var targetAppName = "omnigraffle" var scriptURL = URL.tellFunction(targetAppName, targetAppFunction) console.log(scriptURL) //--> [object URL: omnigraffle://localhost/omnijs-run?script=(function%20targetAppFunction()%7Bconsole.log(%22test%22);return%20%22completed%22%7D)();]

 1  The JavaScript function to be executed by the targeted application. In this example the function code simply logs an entry to the targeted app’s console and returns the text value: completed

 2  The lowercase name of the Omni application to execute the passed function.

 3  A script URL is generated using the tellFunction() function of the URL class. The targeted app name and the name of the function are passed into the function as its parameters.

 4  The resulting script URL object is logged to the console.

 5  The script URL object, which also reveals the URL string. Note that the resulting encoded function is automatically suffixed with with an open and close pair of parens “( );” which will cause the function to be executed when the script is passed to the target Omni application.

The call() Function

Once the script URL has been generated by the tellFunction() function, the call() function is used to send the script to the targeted Omni application for execution. In addition, the call() function adapts the script URL to include an x-callback-url that returns the script result (or error) to the originating application for processing.

Here is an example of using the call() function without the optional error handling:

The call() Function with Result Object


function targetAppFunction(){console.log("test");return "completed"} var targetAppName = "omnigraffle" var scriptURL = URL.tellFunction(targetAppName, targetAppFunction) scriptURL.call(reply => { // PROCESS RESULTS OF SCRIPT if (typeof reply === "object"){ reply = reply["result"] } console.log(reply) })

 1  The JavaScript function to be executed by the targeted application. In this example the function code simply logs an entry to the targeted app’s console and returns the text value: completed

 2  The lowercase name of the Omni application to execute the passed function.

 3  A script URL is generated using the tellFunction() function of the URL class. The targeted app name and the name of the function are passed into the function as its parameters.

 4  The call() function is executed with the URL instance with a single call-back function as the sole parameter for the function. Any result returned by the executed script will be passed into the call-back function as its parameter.

 6  The script result is logged in the originating app’s console. In this example, a successful script execution will return the string: completed

IMPORTANT: For a successful reply, if the result object has one property, its value is passed as the first argument to the success function. If there are zero or more than one parameters, the full object is passed as the first argument. In both cases, the success function is passed a second argument that is the full object of parameters.

The tellFunction() Function

The tellFunction() function of the URL class provides a very efficient mechanism for performing actions in another Omni application, and optionally returning the results of those actions back to the Omni application originating the Omni Automation app-to-app script.

To use this function, you include a JavaScript function for the targeted Omni application, in the Omni Automation action script for the Omni application hosting the action. The tellFunction() function in your hosted action will automatically convert the included target function into a percent-encoded string that will be included in a script URL executed by the call() function of the URL class. The encoded script will be translated and the passed function executed by the targeted Omni application. Optionally, any results of the function can be returned to the originating application for further use by the hosted script.

In addition, the passed function has the option to receive input through the inclusion of a single passed-in argument that may be either a string, number, date, array, or object. The only requirement for the passed argument is that it be compatible with the JSON.stringify() function, which is how the passed function arguments are made ready for inclusion in the script URL generated by the tellFunction() method.

In the following example action, which targets the OmniOutliner application from any of the other Omni apps, the tellFunction() function is used to pass a function (that will be executed in OmniOutliner) that has no input argument. Note that the result of the passed function (line 20: in this example, a simple message) is returned to the originating script for processing.

In the example, the passed function simply logs a message in the targeted application’s console and returns a message to the originating script, that logs the returned message in the originating app’s console.

Note that the passed function in the example action incorporates the use of the try, catch, and throw statements in order to return an error message to the originating application. Any error message is processed by the second call-back function parameter in the call() function.

App-to-App Function with No Passed Arguments
 

/*{ "type": "action", "targets": ["omnigraffle","omnifocus","omniplan"], "author": "Otto Automator", "identifier": "com.omni-automation.ex.tellfunction", "version": "1.1", "description": "A testing script for the tellFunction() function of the URL class", "label": "tellFunction()", "shortLabel": "tellFunction()" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code var targetAppName = "omnioutliner" // THE FUNCTION TO BE EXECUTED ON THE TARGET APP function targetAppFunction(){ try { // SCRIPT CODE TO EXECUTE console.log("Passed function has been executed") return "completed" } catch(error){ console.error("An error occurred.") throw error } } // CREATE SCRIPT URL WITH FUNCTION var scriptURL = URL.tellFunction( targetAppName, targetAppFunction, null ) // CALL THE SCRIPT URL, PROCESS RESULTS OR ERROR scriptURL.call(function(reply){ // PROCESS RESULTS OF SCRIPT if (typeof reply === "object"){ reply = reply["result"] } console.log(reply) }, function(err){ // PROCESS SCRIPT ERROR new Alert("SCRIPT ERROR", err.errorMessage).show() console.error(error) }) }); action.validate = function(selection, sender){ // validation code return true }; return action; })();

(⬇ see below ) The OmniOutliner console after the action has been run:

omnioutliner-console-01

(⬇ see below ) The OmniFocus console after the action has been run:

omnifocus-console-01

The tellFunction() Function: Object Argument

With the tellFunction() method, the passed function has the option to receive input (before it is passed) through the inclusion of a single passed-in argument that may be either a string, number, date, array, or object. The only requirement for the passed argument is that it be compatible with the JSON.stringify() function, which is how the passed function arguments are made ready for inclusion in the script URL generated by the tellFunction() method.

In many actions implementing a passed data argument, the data is generated dynamically by the script and may represent the contents of objects and elements in the document of the host application.

In the following example, the argument for the passed function is a JavaScript object containing a series of key:value data pairs, with the values being: a string, a number, and an array of strings (lines 18—23). The created object is then added as the last parameter of the tellFunction() function. (line 39)

Note that the passed function in the example action incorporates the use of the try, catch, and throw statements in order to return an error message to the originating application. Any error message is processed by the second call-back function parameter in the call() function.

App-to-App Function with Object Argument
 

/*{ "type": "action", "targets": ["omnigraffle","omnifocus","omniplan"], "author": "Otto Automator", "identifier": "com.omni-automation.ex.tellfunction-object", "version": "1.1", "description": "A testing script for the tellFunction() function of the URL class", "label": "tellFunction(object)", "shortLabel": "tellFunction(object)" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code var targetAppName = "omnioutliner" // OPTIONAL ARGUMENT MAY BE: string, number, date, array, or object var targetFunctionArgument = { "stringValueKey": "HOW NOW BROWN COW", "numericValueKey": 1234, "arrayValueKey": ["Sal","Sue","Carl","Wanda"], "dateValueKey": new Date() } // THE FUNCTION TO BE EXECUTED ON THE TARGET APP function targetAppFunction(arg){ try { var stringValue = arg["stringValueKey"] console.log(stringValue) var numericValue = arg["numericValueKey"] console.log(numericValue) var arrayValue = arg["arrayValueKey"] arrayValue.forEach(item => { console.log(item) }) var dateValueString = arg["dateValueKey"] console.log(Date(dateValueString)) return "completed" } catch(error){ console.error("An error occurred.") throw error } } // CREATE SCRIPT URL WITH FUNCTION var scriptURL = URL.tellFunction( targetAppName, targetAppFunction, targetFunctionArgument ) // CALL THE SCRIPT URL, PROCESS RESULTS OR ERROR scriptURL.call(function(reply){ // PROCESS RESULTS OF SCRIPT if (typeof reply === "object"){ reply = reply["result"] } console.log(reply) }, function(err){ // PROCESS SCRIPT ERROR new Alert("SCRIPT ERROR", err.errorMessage).show() console.error(err) }) }); action.validate = function(selection, sender){ // validation code return true }; return action; })();

(⬇ see below ) The OmniOutliner console showing logs of the passed data:

app-to-app-tellfunction-oo

(⬇ see below ) The console showing the resulting message of the action:

omnifocus-console-01

Copy Selected OmniFocus Tasks to OmniPlan

In the following example, data is gathered about the selected OmniFocus tasks and then set to OmniPlan to recreate the tasks in the current project. In this case, the argument for the passed function is a JavaScript array of JavaScript objects, with each object containing the specifics of a selected OmniFocus task. The transfered data is then processed in sequence to construct new tasks in OmniPlan.

Example Argument Data


[{"OFtaskTitle":"TASK ONE","OFtaskNote":"omnifocus:///task/gRAODj8WNwd","OFtaskDueDate":"2020-03-26T00:00:00.000Z"},{"OFtaskTitle":"TASK TWO","OFtaskNote":"omnifocus:///task/iakN8cX6A5u","OFtaskDueDate":"2020-03-16T07:00:00.000Z"},{"OFtaskTitle":"TASK THREE","OFtaskNote":"omnifocus:///task/hKtVOc-4SXV","OFtaskDueDate":"2020-03-16T07:00:00.000Z"}]

Here is the example plug-in:

Copy Selected OmniFocus Tasks to OmniPlan
 

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.op.copy-tasks-to-omniplan", "version": "1.0", "description": "This action will create copies of the selected tasks in the current OmniPlan project.", "label": "Copy Selected Tasks to OmniPlan", "shortLabel": "Copy to OmniPlan" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code // selection options: tasks, projects, folders, tags var now = new Date() var today = Calendar.current.startOfDay(now) var targetFunctionArgument = new Array() selection.tasks.forEach(function(OFtask){ var OFtaskTitle = OFtask.name var OFtaskID = OFtask.id.primaryKey var OFtaskLink = "omnifocus:///task/" + OFtaskID var OFtaskNote = OFtask.note if (OFtaskNote){ OFtaskNote = (OFtaskNote.length > 0) ? OFtaskNote + "\n" + OFtaskLink : OFtaskLink } else { OFtaskNote = OFtaskLink } var OFtaskDueDate = OFtask.dueDate OFtaskDueDate = (OFtaskDueDate != null) ? OFtaskDueDate : today // CREATE TASK DATA OBJECT var taskDataObj = { "OFtaskTitle":OFtaskTitle, "OFtaskNote":OFtaskNote, "OFtaskDueDate":OFtaskDueDate } targetFunctionArgument.push(taskDataObj) }) console.log(JSON.stringify(targetFunctionArgument)) function targetAppFunction(arg){ try { var OPTaskLinks = new Array() arg.forEach(taskDataObj => { OPtask = actual.rootTask.addSubtask() OPtaskLink = "omniplan:///task/" + OPtask.uniqueID OPtask.title = taskDataObj["OFtaskTitle"] OPtask.note = taskDataObj["OFtaskNote"] OPtask.endNoLaterThanDate = new Date(taskDataObj["OFtaskDueDate"]) OPTaskLinks.push(OPtaskLink) }) return OPTaskLinks } catch(error){ console.log("An error occurred.") throw error } } var scriptURL = URL.tellFunction( "omniplan", targetAppFunction, targetFunctionArgument ) scriptURL.call(result => { // PROCESS RESULTS OF SCRIPT console.log("targetAppFunction result: ", result) var selectedTasks = selection.tasks result.forEach((OPTaskLink, index) => { var OFTask = selectedTasks[index] var OFTaskNote = OFTask.note if (OFTaskNote){ if(OFTaskNote.length > 0){ OFTask.note = OFTaskNote + "\n" + OPTaskLink } else { OFTask.note = OPTaskLink } } else { OFTask.note = OPTaskLink } }) }, function(error){ // PROCESS SCRIPT ERROR new Alert("SCRIPT ERROR", error).show() console.error(error) }) }); action.validate = function(selection, sender){ // validation code // selection options: tasks, projects, folders, tags return (selection.tasks.length > 0) }; return action; })();

Calling 3rd-Party Applications

Here is an example of using the call() function to target a 3rd-party application is documented in this plug-in that creates a new note in the Bear application using the contents of the clipboard in the OmniFocus application.

Sequential Call-Backs

Because of script security protocols where factored scripts can be allowed to execute without subsequent approval, it is possible to use the tellFunction() function to sequentially process an array of objects.

In the following example, new rows are created in the current OmniOutliner document for each of the tasks selected in OmniFocus, with a link to each created row added to the notes field of its related task.

To setup the use of the plug-in, first select a single task and execute the plug-in. In the script security dialog in OmniOutliner, enable the option to allow the script to run without challenge in subsequent executions. (see below)

The plug-in will now execute without interruptions.

security dialog
Sequential Call-Backs
 

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.new-rows-for-tasks", "version": "1.1", "description": "A plug-in that performs sequential call-backs.", "label": "New Rows for Tasks", "shortLabel": "Rows for Tasks", "paletteLabel": "Rows for Tasks", "image": "gearshape.fill" }*/ (() => { const action = new PlugIn.Action(function(selection, sender){ targetAppName = "omnioutliner" // TARGET APP FUNCTION function makeNewOOEntry(taskRecord){ row = rootItem.addChild() row.topic = taskRecord["name"] + " • " + taskRecord["link"] row.note = taskRecord["note"] rowID = row.identifier console.log(row.topic + ": " + rowID) return "omnioutliner:///open?row=" + rowID } selection.tasks.forEach(task => { let targetFunctionArgument = { "name" : task.name, "link" : "omnifocus:///task/" + task.id.primaryKey, "note" : task.note } // CREATE SCRIPT URL WITH FUNCTION let scriptURL = URL.tellFunction( targetAppName, makeNewOOEntry, targetFunctionArgument ) // EXECUTE THE SCRIPT URL AND PROCESS RESULT scriptURL.call(result => { if(task.note.length > 0){ task.appendStringToNote("\n" + result) } else { task.appendStringToNote(result) } }) }) }); action.validate = function(selection, sender){ // validation code return (selection.tasks.length > 0) }; return action; })();

Asynchronous Version

Not all code-running environments are the same. Some script environments (aside from application plug-ins and the Automation console), like Shortcuts actions and Voice Control commands, are highly “time-optimized” and may “ignore” functions that require other actions to complete before finishing, like the call() function.

In these instances, use an asynchronous version of the call() handler to ensure the function is completed before the script ends.

This is accomplished by “wrapping” the function in an explicit JavaScript Promise. Using a Promise instance alerts the script environment that the code may take a while to complete and to wait for that to complete.

In the following example the variable url contains the URL instance to call, and the keywords async and await indicate that the wrapping function is asynchronous:

Wrapping call() Function in a Promise


asyncCall = async (url) => { callPromise = new Promise((callResolve, callReject) => { url.call((reply) => { // processing statements using reply go here // when done, pass reply to the resolve function callResolve(reply); }, (err) => { // if applicable, handle the error // when done, pass error to the rejection function callReject(err); }); }); // notifies the script to wait for the call to complete return await callPromise }