Omni Automation Actions

In essence, actions are Omni Automation scripts delivered either as individual stand-alone plug-in files, or as components of Omni Automation bundle plug-ins. Both types of actions can be activated via the host application’s Automation menu, or via external scripts targeting the action object..

Actions differ from external Omni Automation scripts (those executed from a console or a link) in that the structure of the action is a JavaScript function that is called, rather than as an Omni Automation script that is run.

The following section details how to write Omni Automation actions for inclusion in Omni Automation bundle plug-ins. Details regarding writing solitary stand-alone action plug-ins are here.

The Action Code

An Omni Automation action is comprised of JavaScript functions placed within a self-invoking anonymous (unnamed) JavaScript arrow function. This anonymous (unnamed) function wrapper is used to minimize the chance of conflicts with other actions whose code statements contain variables using the same titles as those used in other actions.

(( ) => { // The action’s functions and statements are placed within the wrapping arrow function })( );

The following are the essential components for creating an Omni Automation action:

(() => { var action = new PlugIn.Action(function(selection, sender){ // action processing code goes here }); action.validate = function(selection, sender){ // perform any status checks and return a boolean value return true }; return action; })();

Example Action

The following is an example OmniGraffle action that when called will change the stroke color of all selected graphics to red.

(() => { var action = new PlugIn.Action(function(selection,sender){ selection.graphics.forEach(graphic => { graphic.strokeColor = Color.blue }) }); action.validate = function(selection){ return (selection.graphics.length > 0) }; return action; })();
var _ = function(){ var action = new PlugIn.Action(function(selection){ // if called externally (from script) then generate selection array if (typeof selection == 'undefined'){ // convert nodes into items nodes = document.editors[0].selectedNodes selectedItems = nodes.map(function(node){return node.object}) } else { selectedItems = selection.items } // action code }); action.validate = function(selection){ // validation check. For example, are items selected? // if called externally (from script) then generate selection array if (typeof selection == 'undefined'){ selNodesCount = document.editors[0].selectedNodes.length if(selNodesCount > 0){return true} else {return false} } else { if(selection.items.length > 0){return true} else {return false} } }; return action; }(); _;

The Action File

Actions are saved as JavaScript script files with the file extension of: .js They are placed at the top-level within the PlugIn bundle’s Resources folder. (see the plugins page for more detail)

Since the name of an action file also serves as its identifier, its file name should not contain spaces or special characters.

The Action Strings File

Localized titles and/or descriptions of an action are displayed to the user in three ways:

These localized labels/strings are determined by the values provided in the action.strings file, which is placed within the localized project folder within the bundle’s Resources folder. (see the plugins page for more detail)

"label" = "About this PlugIn…"; "shortLabel" = "About"; "mediumLabel" = "About PlugIn"; "longLabel" = "Information about this PlugIn";

The Action Icon File

Each action should have its own toolbar icon. Typically, these icons are image files (saved in PNG format) that are indicative of the type of task the action performs, like these:

text-align-center text-align-justified text-align-left text-align-right text-align-right text-align-right text-align-right

Save your toolbar icons as 48x48 PNG files with 144 pixel resolution, and place them within the plugin’s Resources folder.

Registering a PlugIn Action

In order to be available to the user, plugin actions must be registered by including them in the plugin’s manifest.json file. This file contains metadata about the plugin, as well an array of plugin actions (line 7), and an optional array of one or more libraries (line 17).

Each action element is comprised of a key:value pair for the action’s identifier (filename) (lines 9, 13) and the filename of its corresponding toolbar icon image file (lines 10, 14).

IMPORTANT: the value of the identifier key (lines 9, 13) for an action is the action file name without the name extension (.js).

{ "defaultLocale":"en", "identifier": "com.YourIdentifier.NameOfApp", "author": "Your Name or Organization", "description": "A collection of actions (scripts) for NameOfApp.", "version": "1.0", "actions": [ { "identifier": "myFirstActionFileName", "image": "1st-action-toolbar-icon.png" }, { "identifier": "mySecondActionFileName", "image": "2nd-action-toolbar-icon.png" } ], "libraries": [ { "identifier": "MyFirstLibraryFileName" }, { "identifier": "MySecondLibraryFileName" } ] }

Action: Working with the Selection

This example OmniGraffle action changes the color of the selected graphic objects in to red. Let’s examine its construction.

When the user clicks on the OmniGraffle Automation menu, this action’s validation function will be evaluated so that the application can determine whether or not to enable or disable the action’s menu item. The result of this function will be used for this determination. In the example below, the passed selection object will contain a reference to all the objects currently selected in the document. The code shown here determines the number of graphics the selection object contains. If one or more graphics are selected, the routine returns true. If no graphic objects are selected, the routine returns false.

TIP: If you want the action to only be available when a single graphic is selected, change the comparison to: (selection.graphics.length === 1)

(() => { var action = new PlugIn.Action(function(selection,sender){ if (typeof selection == 'undefined'){selection = document.windows[0].selection} var aFillColor = Color.RGB(1, 0, 0, 1) selection.graphics.forEach(function(aGraphic){ aGraphic.fillColor = aFillColor }) }); // result determines if the action menu item is enabled action.validate = function(selection){ // check to see if any graphics are selected if (typeof selection == 'undefined'){selection = document.windows[0].selection} return (selection.graphics.length > 0) }; return action; })();

Import Bundle Image Resource

Here’s another example of a PlugIn action. In this example, an image located within the PlugIn’s Resources folder, is placed onto the current canvas. NOTE: since the action does not require a selection object, there’s no need for generating a selection if the action is called externally.

(() => { var action = new PlugIn.Action(function(selection){ plugin = PlugIn.find("com.YourCompany.OG.PlugInName") var imageFileName = "ImageName.png" url = plugin.resourceNamed(imageFileName) url.fetch(function(data){ aRect = new Rect(0, 0, 72, 72) cnvs = document.windows[0].selection.canvas aGraphic = cnvs.addShape('Rectangle', aRect) aGraphic.strokeThickness = 0 aGraphic.shadowColor = null aGraphic.fillColor = null aGraphic.image = addImage(data) aGraphic.name = imageFileName imageSize = aGraphic.image.originalSize imgX = imageSize.width imgY = imageSize.height aGraphic.geometry = new Rect(aGraphic.geometry.x, aGraphic.geometry.y, imgX, imgY) document.windows[0].selection.view.select([aGraphic]) }) }); // routine determines if menu item is enabled action.validate = function(selection){ return true }; return action; })();

The “About this PlugIn…” action

Here’s an action that provides information about the bundle plug-in to which it belongs.

about-dialog
(() => { var action = new PlugIn.Action(function(selection,sender){ versNum = this.plugIn.version.versionString pluginName = this.plugIn.displayName pluginID = this.plugIn.identifier pluginLibraries = this.plugIn.libraries if (pluginLibraries.length != 0){ libraryNames = new Array() pluginLibraries.forEach(function(aLibrary){ libraryName = aLibrary.name libraryVersion = aLibrary.version.versionString displayString = libraryName + ' v' + libraryVersion libraryNames.push(displayString) }) libraryNamesString = "LIBRARIES:" libraryNamesString = libraryNamesString + '\n' + libraryNames.join('\n') } else { libraryNamesString = "This plugin has no libraries." } alertTitle = pluginName + ' v.' + versNum descriptionString = "A description of what your plugin does." companyURL = 'https://yourwebsite.com' alertMessage = "©2020 Your Company Name" + '\n' alertMessage = alertMessage + pluginID + '\n' alertMessage = alertMessage + companyURL + '\n' + '\n' alertMessage = alertMessage + descriptionString + '\n' + '\n' alertMessage = alertMessage + libraryNamesString var alert = new Alert(alertTitle, alertMessage) alert.show(function(result){}) }); // validation function determines if menu item is enabled action.validate = function(selection,sender){ return true }; return action; })();

Calling Plugin Actions from Omni Automation Scripts

PlugIn actions are available to be called by other actions and external Omni Automation scripts.

Getting a List of Actions in a PlugIn

Here’s a function that returns a list of the names of the actions contained by a specified PlugIn. The identifier of the target PlugIn is required. See PlugIn documentation for how to get the IDs of the installed PlugIns.

function getActionNamesInPlugIn(PlugInID){ var aPlugin = PlugIn.find(PlugInID) if (aPlugin == null){throw new Error("Plugin is not installed.")} var actionNames = aPlugin.actions.map(function(action){ return action.name }) return actionNames }

The result of the function will be an array of the names of the plug-in’s actions, like this: removeAllCanvases, addImageFromPlugin, addImageFromInternet, changeColorOfSelectedGraphics,switchToOmniOutliner, importTopicsToSlides, resizeToFitImageOriginalSize, aboutThisPlugin, setAspectRatioTo1to1, addMapOfUS, addCanvasForEveryMonth, showStencilNames, addBlueCircle, placeNotesOfSelectedSolid, addGraphicFromStencil

Validating an Action

plug-in actions may have a validation handler that determines whether or not the action is enabled in the plug-in menu. If an action does not have a validation function, the result of validating the action is always true. Validation is often based upon conditions in the document, such as whether there are selected graphics.

Omni Automation scripts can call an action’s validation handler to determine the status of the action. The following example function will return a value of true or false indicating the status of the action.

function validatePlugInAction(PlugInID, actionName){ var aPlugin = PlugIn.find(PlugInID) if (aPlugin == null){throw new Error("Plugin is not installed.")} var actionNames = aPlugin.actions.map(function(action){ return action.name }) if(actionNames.indexOf(actionName) == -1){ throw new Error("Action “" + actionName + "” is not in the PlugIn.") } else { return aPlugin.action(actionName).validate() } }

Executing an Action

Omni Automation scripts can run PlugIn actions by calling the perform() method on an action instance.

function performPlugInAction(PlugInID, actionName){ var aPlugin = PlugIn.find(PlugInID) if (aPlugin == null){throw new Error("Plugin is not installed.")} var actionNames = aPlugin.actions.map(function(action){ return action.name }) if(actionNames.indexOf(actionName) == -1){ throw new Error("Action “" + actionName + "” is not in the PlugIn.") } else { if(aPlugin.action(actionName).validate()){ aPlugin.action(actionName).perform() } else { throw new Error("The action “" + actionName + "” is not valid to run.") } } }

Adapting a Plug-In for Remote Execution

Actions called by remote scripts will have selection objects that are undefined. To adapt actions to be “callable” by remote scripts, check for the type of the selection object and if it is undefined, add a script statement for getting the current document selection. In the following example action for OmniOutliner, a list of selected items (rows) is generated if required:

((){ var action = new PlugIn.Action(function(selection){ // if called externally (from script) generate selection array if (typeof selection == 'undefined'){ // convert nodes into items nodes = document.editors[0].selectedNodes selectedItems = nodes.map((node) => {return node.object}) } else { selectedItems = selection.items } // action code … }); action.validate = function(selection){ // validation check. For example, are items selected? // if called externally (from script) generate selection array if (typeof selection == 'undefined'){ selNodesCount = document.editors[0].selectedNodes.length return (selNodesCount > 0) } else { return (selection.items.length > 0) } }; return action; })();

The Sender Parameter

The selection and sender parameters of a plug-in’s action handlers provide references to the items selected in the application interface, and a reference to the object that triggers the execution of the plug-in.

The value of the sender parameter can be one of three types:

Here are the properties of the MenuItem class:

Here are the properties of the ToolbarItem class:

The following example OmniFocus plug-in demonstrates a use of the sender parameter to change the text of the plug-in’s menu item based upon a property (flagged) of the selected task.

If no single task is selected, the menu appears as “Toggle Task Flag.” If a single task is selected, the menu will appear as either “Set Flag to Off” or “Set Flag to On” based upon the state of the flagged property of the selected task.

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.sender-example", "version": "1.0", "description": "An example plug-in showing how to use the sender parameter.", "label": "Toggle Task Flag", "shortLabel": "Toggle Flag" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code // selection options: tasks, projects, folders, tags, allObjects var task = selection.tasks[0] task.flagged = !task.flagged }); action.validate = function(selection, sender){ // validation code // selection options: tasks, projects, folders, tags, allObjects // sender can be: MenuItem, ToolbarItem, or undefined (remote script) if (selection.tasks.length === 1){ if (sender instanceof MenuItem){ var menuText = (selection.tasks[0].flagged)?'Set Flag to Off':'Set Flag to On' sender.label = menuText } return true } if (sender instanceof MenuItem){sender.label = 'Toggle Task Flag'} return false }; return action; })();
UNDER CONSTRUCTION

This webpage is in the process of being developed. Any content may change and may not be accurate or complete at this time.

DISCLAIMER