Omni Automation Libraries
For those wishing to automate their Omni applications, libraries are your best friend.
Omni Automation Libraries provide the means for storing, using, and sharing your favorite Omni Automation functions. They can make your automation tasks easier by dramatically shortening your scripts, and they enable the ability to share useful routines with other computers and tablets.
Script Libraries exist as JavaScript script files (.js) placed within the Resources folder in plugin bundles, OR as stand-alone single file plug-ins (see Library Plug-Ins below). The functions contained within a Omni Automation Library can be called from within the host plug-in’s actions, or by scripts executed outside of the host plugin.
This section describes the structure of an Omni Automation Library, how it is registered in its host plugin, and how it is referenced by plugin actions and external scripts.
The Structure of an Omni Automation Library
An Omni Automation Library is essentially a JavaScript script, comprised of a single function that is executed when the library is called (line 16). Within the main function (line 1 ~ line 15) a library object is created (line 2), and the various library functions are added to the library object (line 4, line 9). The main script finishes by returning the created library object populated with the handlers you added (line 14).
Omni Automation Library Structure | ||
01 | (() => { | |
02 | var myLibrary = new PlugIn.Library(new Version("0.1")); | |
03 | ||
04 | myLibrary.myFirstFunction = function(passedParameter){ | |
05 | // function code | |
06 | return myFirstFunctionResult | |
07 | } | |
08 | ||
09 | myLibrary.mySecondFunction = function(passedParameter){ | |
10 | // function code | |
11 | return mySecondFunctionResult | |
12 | } | |
13 | ||
14 | return myLibrary; | |
15 | })(); |
Libraries can contain as many functions as you find useful to include. The example shown above contains two library functions that can be called from within your plugins or scripts.
Library files are saved with the file extension: .js (which stands for JavaScript) and are placed in the Resources folder in the plugin bundle. IMPORTANT: it is best to avoid using spaces in the filenames of your libraries.
Registering an Omni Automation Library
In order to be available to the user, libraries 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).
Each library element is comprised of a key:value pair (lines 19, 22) for the library’s identifier, which is the library’s filename without the file extension.
Plugin Manifest JSON | ||
01 | { | |
02 | "defaultLocale":"en", | |
03 | "identifier": "com.YourIdentifier.NameOfApp", | |
04 | "author": "Your Name or Organization", | |
05 | "description": "A collection of actions (scripts) for NameOfApp.", | |
06 | "version": "1.0", | |
07 | "actions": [ | |
08 | { | |
09 | "identifier": "myFirstAction", | |
10 | "image": "toolbar-icon.png" | |
11 | }, | |
12 | { | |
13 | "identifier": "mySecondAction", | |
14 | "image": "toolbar-icon.png" | |
15 | } | |
16 | ], | |
17 | "libraries": [ | |
18 | { | |
19 | "identifier": "MyFirstLibraryFileName" | |
20 | }, | |
21 | { | |
22 | "identifier": "MySecondLibraryFileName" | |
23 | } | |
24 | ] | |
25 | } |
Calling Omni Automation Libraries within a Plugin Action
The example code shown below is of a plugin action. It is comprised of two sections: the validation code (line 14), and the execution code (line 4).
The validation section (line 14) is used to determine if the action should execute, based upon whether or not there is a selection in the frontmost document. If your action does not rely upon a selection, simply return true (15) as shown in the example.
Calling Libraries within an Action | ||
01 | (() => { | |
02 | var action = new PlugIn.Action(function(selection){ | |
03 | // reference libraries within host plugin | |
04 | var stencilLib = this.StencilLib; | |
05 | var shapeLib = this.ShapeLib; | |
06 | // call library functions using references | |
07 | var names = stencilLib.getStencilNames() | |
08 | aGraphic = shapeLib.shapeWithContentsOfArray(names) | |
09 | // select the created graphic | |
10 | document.windows[0].selection.view.select([aGraphic]) | |
11 | }); | |
12 | ||
13 | // value returned from validation function determines menu state | |
14 | action.validate = function(selection){ | |
15 | return true | |
16 | }; | |
17 | ||
18 | return action; | |
19 | })(); |
Loading a library is accomplished by simply appending the library identifier (filename) to the global object: this. In the example shown above two libraries are loaded into variables (line 4, 5).
Once the libraries have been loaded, any library functions can be called by appending the name of the function to the variable representing the host library (lines 7, 8).
Calling an Omni Automation Library within a Script
Calling into libraries within an external script is a slightly more involved process in that you must first identify the plugin that contains the library whose functions you wish to access.
In the example below, the plugin is located using the find method on the Plugin class, and the plugin’s identifier. If the plugin is installed, then an object reference to the plugin is returned and stored in a variable (line 2). If the plugin is not installed, a value of null will be returned and the next line in the script (line 3) will throw an error indicating its status as missing.
Once the plugin has been successfully identified, any libraries can be loaded using the library method on the plugin reference stored previously in a variable (lines 5, 7).
Once the library or libraries have been loaded, their functions can be accessed by appending the function name to the variable containing the loaded library (lines 9, 11).
Calling Libraries within a Script | ||
01 | // locate the plugin by identifier | |
02 | var aPlugin = PlugIn.find("com.YourIdentifier.NameOfApp") | |
03 | if (aPlugin == null){throw new Error("Plugin is not installed.")} | |
04 | // load a library identified by name | |
05 | var firstLibrary = aPlugin.library("NameOfFirstLibrary") | |
06 | // load a library identified by name | |
07 | var secondLibrary = aPlugin.library("NameOfSecondLibrary") | |
08 | // call a library function by name | |
09 | var aVariable = firstLibrary.nameOfLibraryFunction() | |
10 | // call a library function by name | |
11 | secondLibrary.nameOfLibraryFunction(aVariable) |
Example Library
As an example, here’s a library for working with OmniGraffle stencils (see below). It contains the following functions that can be called from within console scripts and web and Omni object-links:
NOTE: notice how library functions can call other functions in the library by preceding the function title with the “this” keyword: this.getStencilNames()
Stencil Library (bundle) | ||
001 | (() => { | |
002 | var StencilLib = new PlugIn.Library(new Version("1.0")); | |
003 | ||
004 | StencilLib.getLibraryFunctions = function(){ | |
005 | functionTitles = ["getLibraryFunctions()", "getStencilNames()", "isStencilInstalled(stencilName)", "getStencilObject(stencilName)", "countOfGraphicsInStencil(stencilName)", "getGraphicNamesInStencil(stencilName)", "isGraphicInStencil(graphicName,stencilName)", "importGraphicFromStencil(graphicName,stencilName)", "importAllGraphicsFromStencil(stencilName)"]; | |
006 | return "###### StencilLib Functions #####\n\t--> " + functionTitles.join("\n\t--> "); | |
007 | }; | |
008 | ||
009 | StencilLib.getStencilNames = function(){ | |
010 | return app.stencils.map(function(aStencil){return aStencil.name}); | |
011 | }; | |
012 | ||
013 | StencilLib.isStencilInstalled = function(stencilName){ | |
014 | stencilNames = this.getStencilNames(); | |
015 | var status = false; | |
016 | stencilNames.forEach(function(aName){ | |
017 | if (aName.localeCompare(stencilName) === 0){status = true}; | |
018 | }); | |
019 | return status; | |
020 | }; | |
021 | ||
022 | StencilLib.getStencilObject = function(stencilName){ | |
023 | if(this.isStencilInstalled(stencilName) === false){ | |
024 | displayErrorMessage("There is no stencil named: " + stencilName) | |
025 | } | |
026 | for(i = 0; i < app.stencils.length; i++){ | |
027 | if (app.stencils[i].name.localeCompare(stencilName) === 0){ | |
028 | return app.stencils[i] | |
029 | } | |
030 | } | |
031 | } | |
032 | ||
033 | StencilLib.countOfGraphicsInStencil = function(stencilName){ | |
034 | var targetStencil = this.getStencilObject(stencilName); | |
035 | var graphicCount; | |
036 | targetStencil.load(function(stencilRef){ | |
037 | console.log("Loaded stencil: “" + stencilRef.name + "”"); | |
038 | graphicCount = stencilRef.graphics.length; | |
039 | return graphicCount; | |
040 | }); | |
041 | if (graphicCount){ | |
042 | return graphicCount; | |
043 | } else { | |
044 | displayErrorMessage("Problem loading stencil: " + stencilName); | |
045 | }; | |
046 | }; | |
047 | ||
048 | StencilLib.getGraphicNamesInStencil = function(stencilName){ | |
049 | var targetStencil = this.getStencilObject(stencilName); | |
050 | var graphicNames; | |
051 | targetStencil.load(function(stencilRef){ | |
052 | console.log("Loaded stencil: “" + stencilRef.name + "”"); | |
053 | graphicNames = stencilRef.graphics.map(function(aGraphic){ | |
054 | return aGraphic.name; | |
055 | }); | |
056 | return graphicNames; | |
057 | }); | |
058 | if (graphicNames){ | |
059 | return graphicNames; | |
060 | } else { | |
061 | displayErrorMessage("Problem reading graphic names from stencil: " + stencilName); | |
062 | }; | |
063 | }; | |
064 | ||
065 | StencilLib.isGraphicInStencil = function(graphicName, stencilName){ | |
066 | var targetStencil = this.getStencilObject(stencilName); | |
067 | var graphicNames; | |
068 | targetStencil.load(function(stencilRef){ | |
069 | console.log("Loaded stencil: “" + stencilRef.name + "”"); | |
070 | graphicNames = stencilRef.graphics.map(function(aGraphic){ | |
071 | return aGraphic.name; | |
072 | }); | |
073 | return graphicNames; | |
074 | }); | |
075 | if (graphicNames){ | |
076 | qResult = (graphicNames.includes(graphicName)) ? true : false; | |
077 | return qResult | |
078 | } else { | |
079 | displayErrorMessage("Problem aquiring graphic names from stencil: " + stencilName); | |
080 | }; | |
081 | }; | |
082 | ||
083 | StencilLib.importGraphicFromStencil = function(graphicName,stencilName){ | |
084 | var targetStencil = this.getStencilObject(stencilName); | |
085 | targetStencil.load(function(s){ | |
086 | console.log("Loaded stencil: “" + s.name + "”") | |
087 | for(q = 0; q < s.graphics.length; q++){ | |
088 | aGraphicName = s.graphics[q].name | |
089 | if (aGraphicName != null){ | |
090 | if (aGraphicName.localeCompare(graphicName) == 0){ | |
091 | console.log("found graphic: “" + graphicName + "”") | |
092 | aStencilGraphic = s.graphics[q] | |
093 | cnvs = document.windows[0].selection.canvas | |
094 | rsltArray = cnvs.duplicate([aStencilGraphic]) | |
095 | aGraphic = rsltArray[0] | |
096 | aGraphic.geometry = new Rect(0, 0, aGraphic.geometry.width, aGraphic.geometry.height) | |
097 | document.windows[0].selection.view.select([aGraphic]) | |
098 | console.log("added graphic: “" + graphicName + "”") | |
099 | return aGraphic | |
100 | } | |
101 | } | |
102 | } | |
103 | displayErrorMessage("Graphic “" + graphicName + "” is not in stencil “" + stencilName + "”") | |
104 | }); | |
105 | }; | |
106 | ||
107 | StencilLib.importAllGraphicsFromStencil = function(stencilName){ | |
108 | var targetStencil = this.getStencilObject(stencilName); | |
109 | targetStencil.load(function(s){ | |
110 | console.log("Loaded stencil: “" + s.name + "”"); | |
111 | document.windows[0].selection.canvas.duplicate(s.graphics); | |
112 | }); | |
113 | }; | |
114 | ||
115 | return StencilLib; | |
116 | })(); | |
118 | ||
119 | function displayErrorMessage(errorString){ | |
120 | new Alert('ERROR', errorString).show(function(result){}) | |
121 | throw new Error(errorString) | |
122 | } |
Here’s an example of calling the previous example Stencil Library:
Example Library Script | ||
01 | aPlugin = PlugIn.find("com.NyhthawkProductions.OmniGraffle") | |
02 | stencilLib = aPlugin.library("StencilLib") | |
03 | stencilLib.importGraphicFromStencil('Tablet Mac','Google Material Design Icons') |
Library Plug-In
As with Plug-In actions, libraries can be created in a simple single-file Plug-In format that are installed in the application’s Plug-Ins folder. As with bundled Plug-In libraries, they are called from an action using their filename (without extension).
NOTE: An important difference between libraries that are elements of a Plug-In bundle, and those libraries that are single-file plug-ins, is the inclusion of identifying metadata at the top of the the single-file library (lines 2-7 below):
Single-File Library Template | ||
01 | // COPY & PASTE into a text editor app. EDIT & SAVE with “.omnijs” file extension. | |
02 | /*{ | |
03 | "type": "library", | |
04 | "targets": ["omnigraffle","omnioutliner"], | |
05 | "identifier": "com.youOrCompany.libraryName", | |
06 | "version": "1.0" | |
07 | }*/ | |
08 | (() => { | |
09 | var myLibrary = new PlugIn.Library(new Version("1.0")); | |
10 | ||
11 | myLibrary.myFirstFunction = function(passedParameter){ | |
12 | // function code | |
13 | return myFirstFunctionResult | |
14 | } | |
15 | ||
16 | myLibrary.mySecondFunction = function(passedParameter){ | |
17 | // function code | |
18 | return mySecondFunctionResult | |
19 | } | |
20 | ||
21 | return myLibrary; | |
22 | })(); |
Here's an example library for OmniFocus:
Example Library for OmniFocus | ||
01 | /*{ | |
02 | "type": "library", | |
03 | "targets": ["omnifocus"], | |
04 | "identifier": "com.omni-automation.of.exampleLibrary", | |
05 | "author": "Otto Automator", | |
06 | "version": "1.0", | |
07 | "description": "An example library for OmniFocus" | |
08 | }*/ | |
09 | (() => { | |
10 | var lib = new PlugIn.Library(new Version("1.0")); | |
11 | ||
12 | lib.countOfInbox = function(){ | |
13 | return inbox.length | |
14 | } | |
15 | ||
16 | lib.toggleFlagsForTasks = function(tasks){ | |
17 | tasks.forEach(task => { | |
18 | task.flagged = !task.flagged | |
19 | }) | |
20 | } | |
21 | ||
22 | return lib; | |
23 | })(); |
Here's how to call functions in the example library for OmniFocus. NOTE: the following script assumes the library was saved as: "omnifocus-lib.omnijs"
Calling Example Library Functions | ||
01 | var libFile = PlugIn.find("com.omni-automation.of.exampleLibrary") | |
02 | var lib = libFile.library("omnifocus-lib") | |
03 | lib.countOfInbox() //--> 5 |
Example: Stencil Library
Here’s an example solitary library file for working with stencils in OmniGraffle:
Stencil Library | ||
01 | /*{ | |
02 | "type": "library", | |
03 | "targets": ["omnigraffle"], | |
04 | "identifier": "com.omni-automation.libraries.StencilLib", | |
05 | "version": "1.0" | |
06 | }*/ | |
07 | (() => { | |
08 | var StencilLib = new PlugIn.Library(new Version("1.0")); | |
09 | ||
10 | StencilLib.getLibraryFunctions = function(){ | |
11 | functionTitles = ["getLibraryFunctions()", "getStencilNames()", "isStencilInstalled(stencilName)", "getStencilObject(stencilName)", "countOfGraphicsInStencil(stencilName)", "getGraphicNamesInStencil(stencilName)", "isGraphicInStencil(graphicName,stencilName)", "importGraphicFromStencil(graphicName,stencilName)", "importAllGraphicsFromStencil(stencilName)"]; | |
12 | return "###### StencilLib Functions #####\n\t--> " + functionTitles.join("\n\t--> "); | |
15 | }; | |
16 | ||
17 | StencilLib.getStencilNames = function(){ | |
18 | return app.stencils.map(function(aStencil){return aStencil.name}); | |
19 | }; | |
20 | ||
21 | StencilLib.isStencilInstalled = function(stencilName){ | |
22 | stencilNames = this.getStencilNames(); | |
23 | var status = false; | |
24 | stencilNames.forEach(function(aName){ | |
25 | if (aName.localeCompare(stencilName) === 0){status = true}; | |
26 | }); | |
27 | return status; | |
28 | }; | |
29 | ||
30 | StencilLib.getStencilObject = function(stencilName){ | |
31 | if(this.isStencilInstalled(stencilName) === false){ | |
32 | displayErrorMessage("There is no stencil named: " + stencilName) | |
33 | } | |
34 | for(i = 0; i < app.stencils.length; i++){ | |
35 | if (app.stencils[i].name.localeCompare(stencilName) === 0){ | |
36 | return app.stencils[i] | |
37 | } | |
38 | } | |
39 | } | |
40 | ||
41 | StencilLib.countOfGraphicsInStencil = function(stencilName){ | |
42 | var targetStencil = this.getStencilObject(stencilName); | |
43 | var graphicCount; | |
44 | targetStencil.load(function(stencilRef){ | |
45 | console.log("Loaded stencil: “" + stencilRef.name + "”"); | |
46 | graphicCount = stencilRef.graphics.length; | |
47 | return graphicCount; | |
48 | }); | |
49 | if (graphicCount){ | |
50 | return graphicCount; | |
51 | } else { | |
52 | displayErrorMessage("Problem loading stencil: " + stencilName); | |
53 | }; | |
54 | }; | |
55 | ||
56 | StencilLib.getGraphicNamesInStencil = function(stencilName){ | |
57 | var targetStencil = this.getStencilObject(stencilName); | |
58 | var graphicNames; | |
59 | targetStencil.load(function(stencilRef){ | |
60 | console.log("Loaded stencil: “" + stencilRef.name + "”"); | |
61 | graphicNames = stencilRef.graphics.map(function(aGraphic){ | |
62 | return aGraphic.name; | |
63 | }); | |
64 | return graphicNames; | |
65 | }); | |
66 | if (graphicNames){ | |
67 | return graphicNames; | |
68 | } else { | |
69 | displayErrorMessage("Problem reading graphic names from stencil: " + stencilName); | |
70 | }; | |
71 | }; | |
72 | ||
73 | StencilLib.isGraphicInStencil = function(graphicName, stencilName){ | |
74 | var targetStencil = this.getStencilObject(stencilName); | |
75 | var graphicNames; | |
76 | targetStencil.load(function(stencilRef){ | |
77 | console.log("Loaded stencil: “" + stencilRef.name + "”"); | |
78 | graphicNames = stencilRef.graphics.map(function(aGraphic){ | |
79 | return aGraphic.name; | |
80 | }); | |
81 | return graphicNames; | |
82 | }); | |
83 | if (graphicNames){ | |
84 | qResult = (graphicNames.includes(graphicName)) ? true : false; | |
85 | return qResult | |
86 | } else { | |
87 | displayErrorMessage("Problem aquiring graphic names from stencil: " + stencilName); | |
88 | }; | |
89 | }; | |
90 | ||
91 | StencilLib.importGraphicFromStencil = function(graphicName,stencilName){ | |
92 | var targetStencil = this.getStencilObject(stencilName); | |
93 | targetStencil.load(function(s){ | |
94 | console.log("Loaded stencil: “" + s.name + "”") | |
95 | for(q = 0; q < s.graphics.length; q++){ | |
96 | aGraphicName = s.graphics[q].name | |
97 | if (aGraphicName != null){ | |
98 | if (aGraphicName.localeCompare(graphicName) == 0){ | |
99 | console.log("found graphic: “" + graphicName + "”") | |
100 | aStencilGraphic = s.graphics[q] | |
101 | cnvs = document.windows[0].selection.canvas | |
102 | rsltArray = cnvs.duplicate([aStencilGraphic]) | |
103 | aGraphic = rsltArray[0] | |
104 | aGraphic.geometry = new Rect(0, 0, aGraphic.geometry.width, aGraphic.geometry.height) | |
105 | document.windows[0].selection.view.select([aGraphic]) | |
106 | console.log("added graphic: “" + graphicName + "”") | |
107 | return aGraphic | |
108 | } | |
109 | } | |
110 | } | |
111 | displayErrorMessage("Graphic “" + graphicName + "” is not in stencil “" + stencilName + "”") | |
112 | }); | |
113 | }; | |
114 | ||
115 | StencilLib.importAllGraphicsFromStencil = function(stencilName){ | |
116 | var targetStencil = this.getStencilObject(stencilName); | |
117 | targetStencil.load(function(s){ | |
118 | console.log("Loaded stencil: “" + s.name + "”"); | |
119 | document.windows[0].selection.canvas.duplicate(s.graphics); | |
120 | }); | |
121 | }; | |
122 | ||
123 | return StencilLib; | |
124 | })(); | |
126 | ||
127 | function displayErrorMessage(errorString){ | |
128 | new Alert('ERROR', errorString).show(function(result){}) | |
129 | throw new Error(errorString) | |
130 | } |
To access the library functions, use the value of the library plugin identifier attribute to locate the plugin, and then use the library name (in this case “StencilLib”) to store a reference to the library. The library functions can be called on the stored reference, like this example that calls the library function for returning a list of the functions it contains:
Calling the Library | ||
01 | aPlugin = PlugIn.find("com.omni-automation.libraries.StencilLib") | |
02 | stencilLib = aPlugin.library("StencilLib") | |
03 | stencilLib.getLibraryFunctions() |
The “name” used as the parameter the library loading call is the “file name” (without name extension) of the library, whether it is a library within a plug-in bundle, or a solitary library file.