Menus
One of the most common user interface elements for Apple systems is the popup menu on macOS, and its iOS counterpart, the list menu. The Form class in Omni Automation provides the Form.Field.Option sub-class so that users can select an option from a menu or menu list.
Menus (Form.Field.Option)
Instances of the form menu element are created using the new constructor with provided parameters that include the menu label (title), identifying key, and either an array of objects or strings as the display content for the menu:
Form.Field.Option | ||
01 | new Form.Field.Option( | |
02 | key:String, <-- identifying key used in the form values record | |
03 | displayName:String or null, <-- field label for the form dialog | |
04 | options:Array of Object, <-- an array of objects to display, or index values corresponding to the provided names | |
05 | names:Array of String or null, <-- an array of custom menu item titles | |
06 | selected:Object or null<-- the menu item object to be selected when the form dialog is displayed | |
07 | ) |
This constructor returns an instance of the Option Field class, allowing the user to pick from a list of option objects. A list of names may also be given, which must have the same length as the options array. If no names are given, the objects are converted to strings for display or their localized titles are used if available. An initially selected object (which must be a member of the options array) may also be given.
In summary, the source contents of the Option Field (menu) can be either an array of Omni application objects, or an array of menu item titles. Let’s examine each type of menu.
IMPORTANT: Recent Changes to the Form.Field.Option Class
Form.Field.Option now defaults to not allowing null values, but has two additional properties allowsNull and nullOptionTitle. These are not included in the constructor arguments, but can be set after creating the field. If allowsNull is true, then an option will be included to pick null (no value). If a nullOptionTitle is specified that will be used as the title, otherwise a default "No Value" string will be used.
The Form validate function previously was required to return a boolean value, but it may now return null. If there is no validation function specified or it returns null, some default per-field validation is performed. Currently all fields are considered valid except for option fields that don't allow null but haven't had a value set yet. This can occur if an option field is constructed without an initial selection.
Menu of Objects
The use of objects as the source for the menu’s contents is very useful and easy-to-create.
Menu of Objects | ||
01 | alignmentMenu = new Form.Field.Option( | |
02 | "horizontalAlignment", | |
03 | "H-Align", | |
04 | HorizontalAlignment.all, | |
05 | null, | |
06 | HorizontalAlignment.all[0] | |
07 | ) | |
08 | //-> [object Form.Field.Option: horizontalAlignment] |
The result of the form will be the HorizontalAlignment option object corresponding to the menu selection, which can then be used to change the value of the canvas rowAlignment property.
Form Result | ||
01 | //-> [object HorizontalAlignment: Right] |
The technique shown works the same with many other Omni application classes as well.
Menu of Strings (names)
The other type of Form.Field.Option uses an array of strings as the source content of the menu. In this example, the user selects a weekday from a list.
Menu of Strings (names) | ||
01 | let weekdays = ["Monday", | |
02 | weekdayMenu = new Form.Field.Option( | |
03 | "weekday", | |
04 | "Weekday", | |
05 | [0,1,2,3,4,5,6], | |
06 | weekdays, | |
07 | 0 | |
08 | ) | |
09 | //-> [object Form.Field.Option: weekday] |
The result of the form will be the object index value corresponding to the chosen menu item:
Form Result | ||
01 | //-> returns: 4 | |
02 | //-> use to get corresponding name: weekdays[4] |
Stroke Properties Form
The following example action displays a form whose elements are all menus, and can be used to change the stroke properties of the graphics selected in the frontmost document. Both menu creation options (objects and string) are used in the construction of the menu form elements.
Stroke Properties Form | ||
01 | /*{ | |
02 | "type": "action", | |
03 | "targets": ["omnigraffle"], | |
04 | "author": "Otto Automator", | |
05 | "identifier": "com.omni-automation.og.stroke-form", | |
06 | "description": "Applies chosen stroke settings to selected graphics.", | |
07 | "label": "Stroke Properties", | |
08 | "shortLabel": "Stroke" | |
09 | }*/ | |
10 | (() => { | |
11 | var action = new PlugIn.Action(function(selection, sender){ | |
12 | ||
13 | // DIALOG TITLE AND OK BUTTON TITLE | |
14 | let formPrompt = "Choose the stroke settings for the selected graphics:" | |
15 | let buttonTitle = "Continue" | |
16 | ||
17 | // CONSTRUCT THE FORM | |
18 | var form = new Form() | |
19 | ||
20 | let pointSizes = ["0.5","1","2","3","4","5","6","7","8","9","10","11","12"] | |
21 | let thicknessMenu = new Form.Field.Option( | |
22 | "strokeThickness", | |
23 | "Thickness", | |
24 | [0,1,2,3,4,5,6,7,8,9,10,11,12], | |
25 | pointSizes, | |
26 | 1 | |
27 | ) | |
28 | ||
29 | let firstJoinType = LineJoin.all[0] // LineJoin['Bevel'] | |
30 | let joinMenu = new Form.Field.Option( | |
31 | "strokeJoin", | |
32 | "Join Type", | |
33 | LineJoin.all, | |
34 | null, | |
35 | firstJoinType | |
36 | ) | |
37 | ||
38 | let firstStrokeType = StrokeType.all[0] // StrokeType['Single'] | |
39 | let typeMenu = new Form.Field.Option( | |
40 | "strokeType", | |
41 | "Stroke Type", | |
42 | StrokeType.all, | |
43 | null, | |
44 | firstStrokeType | |
45 | ) | |
46 | ||
47 | let firstStrokePattern = StrokeDash.all[0] // StrokeDash['Dash4on4off'] | |
48 | let patternMenu = new Form.Field.Option( | |
49 | "strokePattern", | |
50 | "Stroke Pattern", | |
51 | StrokeDash.all, | |
52 | null, | |
53 | firstStrokePattern | |
54 | ) | |
55 | ||
56 | form.addField(thicknessMenu) | |
57 | form.addField(typeMenu) | |
58 | form.addField(patternMenu) | |
59 | form.addField(joinMenu) | |
60 | ||
61 | // VALIDATE FORM CONTENT | |
62 | form.validate = function(formObject){ | |
63 | return true | |
64 | } | |
65 | ||
66 | // SHOWING THE FORM RETURNS A JAVASCRIPT PROMISE | |
67 | formPromise = form.show(formPrompt, buttonTitle) | |
68 | ||
69 | // PROCESS THE FORM RESULTS | |
70 | formPromise.then(function(formObject){ | |
71 | // RETRIEVE CHOSEN VAUES | |
72 | strokeThickness = formObject.values['strokeThickness'] | |
73 | strokeThickness = Number(pointSizes[strokeThickness]) | |
74 | strokeTypeValue = formObject.values['strokeType'] | |
75 | strokePatternValue = formObject.values['strokePattern'] | |
76 | strokeJoinValue = formObject.values['strokeJoin'] | |
77 | ||
78 | // PERFORM TASKS | |
79 | selection.graphics.forEach(function(graphic){ | |
80 | graphic.strokeThickness = strokeThickness | |
81 | graphic.strokeType = strokeTypeValue | |
82 | graphic.strokePattern = strokePatternValue | |
83 | graphic.strokeJoin = strokeJoinValue | |
84 | }) | |
85 | }) | |
86 | ||
87 | // PROCESS FORM CANCELLATION | |
88 | formPromise.catch(function(error){ | |
89 | console.log("form cancelled", error); | |
90 | }) | |
91 | ||
92 | }); | |
93 | ||
94 | // VALIDATE GRAPHIC(S) SELECTION | |
95 | action.validate = function(selection, sender){ | |
96 | return (selection.graphics.length > 0) | |
97 | }; | |
98 | ||
99 | return action; | |
100 | })(); |
Interactive Form
The next action example is a variation of the previous action. But instead of applying the chosen stroke settings after the form has been approved, the settings are applied to the selected graphic as the settings are changed by the user in the form dialog. Should the user decide to cancel the form, the stroke settings are returned to the values that existed before the form was displayed.
Interactive Stroke Properties Form | ||
01 | /*{ | |
02 | "type": "action", | |
03 | "targets": ["omnigraffle"], | |
04 | "author": "Otto Automator", | |
05 | "identifier": "com.omni-automation.og.stroke-form-live", | |
06 | "description": "Applies chosen stroke settings to selected graphics.", | |
07 | "label": "Stroke Properties (live)", | |
08 | "shortLabel": "Form" | |
09 | }*/ | |
10 | (() => { | |
11 | var action = new PlugIn.Action(function(selection, sender){ | |
12 | ||
13 | var selectedGraphic = selection.graphics[0] | |
14 | ||
15 | let storedStrokeThickness = selectedGraphic.strokeThickness | |
16 | let storedStrokeType = selectedGraphic.strokeType | |
17 | let storedStrokePattern = selectedGraphic.strokePattern | |
18 | let storedStrokeJoin = selectedGraphic.strokeJoin | |
19 | ||
20 | // DIALOG TITLE AND OK BUTTON TITLE | |
21 | let formPrompt = "Choose the stroke settings for the selected graphics:" | |
22 | let buttonTitle = "Continue" | |
23 | ||
24 | // CONSTRUCT THE FORM | |
25 | var form = new Form() | |
26 | ||
27 | initialValue = parseInt(storedStrokeThickness) | |
28 | initialIndex = (initialValue > 18) ? 18:initialValue | |
29 | let pointSizes = ["0.5","1","2","3","4","5","6","7","8","9", | |
30 | let thicknessMenu = new Form.Field.Option( | |
31 | "strokeThickness", | |
32 | "Thickness", | |
33 | [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18], | |
34 | pointSizes, | |
35 | initialIndex | |
36 | ) | |
37 | ||
38 | let joinMenu = new Form.Field.Option( | |
39 | "strokeJoin", | |
40 | "Join Type", | |
41 | LineJoin.all, | |
42 | null, | |
43 | storedStrokeJoin | |
44 | ) | |
45 | ||
46 | let typeMenu = new Form.Field.Option( | |
47 | "strokeType", | |
48 | "Stroke Type", | |
49 | StrokeType.all, | |
50 | null, | |
51 | storedStrokeType | |
52 | ) | |
53 | ||
54 | let patternMenu = new Form.Field.Option( | |
55 | "strokePattern", | |
56 | "Stroke Pattern", | |
57 | StrokeDash.all, | |
58 | null, | |
59 | storedStrokePattern | |
60 | ) | |
61 | ||
62 | form.addField(thicknessMenu) | |
63 | form.addField(typeMenu) | |
64 | form.addField(patternMenu) | |
65 | form.addField(joinMenu) | |
66 | ||
67 | // VALIDATE FORM CONTENT | |
68 | form.validate = function(formObject){ | |
69 | // RETRIEVE CHOSEN VAUES | |
70 | strokeThickness = formObject.values['strokeThickness'] | |
71 | strokeThickness = Number(pointSizes[strokeThickness]) | |
72 | strokeTypeValue = formObject.values['strokeType'] | |
73 | strokePatternValue = formObject.values['strokePattern'] | |
74 | strokeJoinValue = formObject.values['strokeJoin'] | |
75 | ||
76 | // APPLY SETTINGS | |
77 | selectedGraphic.strokeThickness = strokeThickness | |
78 | selectedGraphic.strokeType = strokeTypeValue | |
79 | selectedGraphic.strokePattern = strokePatternValue | |
80 | selectedGraphic.strokeJoin = strokeJoinValue | |
81 | ||
82 | return true | |
83 | } | |
84 | ||
85 | // SHOWING THE FORM RETURNS A JAVASCRIPT PROMISE | |
86 | formPromise = form.show(formPrompt, buttonTitle) | |
87 | ||
88 | // PROCESS THE FORM RESULTS | |
89 | formPromise.then(function(formObject){ | |
90 | // NOTHING TO DO. SETTINGS ALREADY APPLIED! | |
91 | }) | |
92 | ||
93 | // PROCESS FORM CANCELLATION | |
94 | formPromise.catch(function(error){ | |
95 | // RESTORE GRAPHIC’S ORIGINAL SETTINGS | |
96 | selectedGraphic.strokeThickness = storedStrokeThickness | |
97 | selectedGraphic.strokeType = storedStrokeType | |
98 | selectedGraphic.strokePattern = storedStrokePattern | |
99 | selectedGraphic.strokeJoin = storedStrokeJoin | |
100 | }) | |
101 | ||
102 | }); | |
103 | ||
104 | // VALIDATE GRAPHIC SELECTION | |
105 | action.validate = function(selection, sender){ | |
106 | return (selection.graphics.length == 1 ? true:false) | |
107 | }; | |
108 | ||
109 | return action; | |
110 | })(); |
Follow-On Menus
A Follow-On Menu is one whose appearance and/or contents change based-upon the selection of another menu in the form dialog. Follow-On menus are described in detail on this page.
This webpage is in the process of being developed. Any content may change and may not be accurate or complete at this time.