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:
Transfer data from one Omni application to another for processing in the targeted application. For example, creating a new outline in OmniOutliner populated with data about the selected OmniFocus tasks.
Extract data from a targeted Omni application for use in the originating Omni application. For example, using the data in the current OmniOutliner document to create a table in OmniGraffle.
Transfer data from an Omni application to another Omni application for processing in the targeted application, then returning data to the originating application for processing in the originating application. For example, exporting the tasks of an OmniPlan project to OmniFocus and inserting links in the OmniPlan to their corresponding tasks created in OmniFocus.
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:
tellScript(app:String, script:String, arg:Object or null) → (URL or null) • Creates a percent-encoded script URL to invoke the given Omni Automation script on the targeted Omni application. The result of this function is appropriate for use with the call() function of the URL class. IMPORTANT: this function has been superseded by the tellFunction() function, which should be used instead.
tellFunction(app:String, function name:String, arg:Object or null) → (URL or null) • Creates a percent-encoded script URL to invoke the given Omni Automation JavaScript function on the targeted Omni application. The result of this function is appropriate for use with the call() function of the URL class.
Here is an example of the use of the tellFunction() function:
Omni Automation Script URL
function targetAppFunction(){console.log("test");return "completed"}
targetAppName = "omnigraffle"
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)();]
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.
call(success:Function, failure:Function or null) → (passed script result/object or error) • Invoke an x-callback-url API end-point, with the callback functions being invoked when a reply is received. When a reply is received, the parameters of that URL are decoded as JSON, or left as String values if not valid JSON, and stored as properties of a result object. 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 failure callback is always passed the object will all the result parameters, typically errorCode and errorMessage.
Here is an example of using the call() function with the optional error handling:
The call() Function with Result Object
function targetAppFunction(){console.log("test");return "completed"}
targetAppName = "omnigraffle"
scriptURL = URL.tellFunction(targetAppName, targetAppFunction)
scriptURL.call(reply => {
// PROCESS RESULTS OF SCRIPT
if (typeof reply === "object"){
reply = reply["result"]
}
console.log(reply)
}, err => {
console.error(err.errorCode, err.errorMessage)
})
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","omnioutliner"],
"author": "Otto Automator",
"identifier": "com.omni-automation.ex.tellfunction",
"version": "1.2",
"description": "A testing script for the tellFunction() function of the URL class",
"label": "tellFunction()",
"shortLabel": "tellFunction()",
"paletteLabel": "tellFunction()",
"image": "arrow.up.circle"
}*/
(() => {
const action = new PlugIn.Action(function(selection, sender){
targetAppName = "omnioutliner"
// THE FUNCTION TO BE EXECUTED ON THE TARGET APP
function targetAppFunction(){
try {
// SCRIPT CODE TO EXECUTE IN TARGET APP
console.log("Passed function has been executed")
return "completed"
}
catch(error){
console.error("An error occurred.")
throw error
}
}
// CREATE SCRIPT URL WITH FUNCTION
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){
return true
};
return action;
})();
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","omnioutliner"],
"author": "Otto Automator",
"identifier": "com.omni-automation.ex.tellfunction-object",
"version": "1.2",
"description": "A testing script for the tellFunction() function of the URL class",
"label": "tellFunction(object)",
"shortLabel": "tellFunction(object)"
"paletteLabel": "tellFunction(object)"
"image": "arrow.up.circle.fill"
}*/
(() => {
const action = new PlugIn.Action(function(selection, sender){
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 {
stringValue = arg["stringValueKey"]
console.log(stringValue)
numericValue = arg["numericValueKey"]
console.log(numericValue)
arrayValue = arg["arrayValueKey"]
arrayValue.forEach(item => {
console.log(item)
})
dateValueString = arg["dateValueKey"]
console.log(Date(dateValueString))
return "completed"
}
catch(error){
console.error("An error occurred.")
throw error
}
}
// CREATE SCRIPT URL WITH FUNCTION
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){
return true
};
return action;
})();
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.1",
"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",
"paletteLabel": "Copy to OmniPlan",
"image": "doc.on.doc.fill"
}*/
(() => {
const action = new PlugIn.Action(function(selection, sender){
now = new Date()
today = Calendar.current.startOfDay(now)
targetFunctionArgument = new Array()
selection.tasks.forEach(function(OFtask){
OFtaskTitle = OFtask.name
OFtaskID = OFtask.id.primaryKey
OFtaskLink = "omnifocus:///task/" + OFtaskID
OFtaskNote = OFtask.note
if (OFtaskNote){
OFtaskNote = (OFtaskNote.length > 0) ? OFtaskNote + "\n" + OFtaskLink : OFtaskLink
} else {
OFtaskNote = OFtaskLink
}
OFtaskDueDate = OFtask.dueDate
OFtaskDueDate = (OFtaskDueDate != null) ? OFtaskDueDate : today
// CREATE TASK DATA OBJECT
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) => {
OFTask = selectedTasks[index]
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){
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.
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
}