Form Validation
Action Forms provide an mechanism for the user to provide the script with the data and decisions necessary for the script to accomplish its automation task. Because the form dialog incorporates the use of input and selection elements, it may be necessary to ensure that the data or selections fit within certain criteria before proceeding with the script’s execution.
The validate function of the Form class is used to control the enabling of the form’s approval button by checking the user provided data or selections. If the function returns a value of true, the button is enabled, otherwise the button is disabled by default.
The form validation function is called when the dialog is displayed and every time the user interacts with its form elements, such as when text is entered in a text input field or a checkbox is selected or deselected.
When the form validation function is called, it is passed a JavaScript object representing the form itself. The value of the form’s values property is a record of key:value pairs representing the identifier and current value for each of the fields in the form dialog. Omni Automation scripts parse this object record and use the retrieved data to determine the response provided by the function.
Form Instance Properties
fields (Array of Form.Field objects r/o) • The current Field instances in the form, which will be visible to the user entering input.
validate (Function or null) • A function to check whether the entered values are acceptable. The form to validate is passed as the argument and the function is expected to return a boolean result. If an Error is thrown, it’s message will be displayed in the form as the reason for validation failure. Note that the validation function may add or remove fields and update entries in the values object (which will cause the interface to be updated). This is called any time the user edits values, or a field is added or removed.
values (Object r/o) • An object with the collected values for each field, stored under the key for that field.
There are three options you can provide as the result of the form validation:
Returning a boolean value of true will cause the approval button to be enabled in the form dialog.
Returning a boolean value of false will cause the approval button to remain disabled in the form dialog.
The text of an error message thrown in the validation function will be displayed in the form dialog. This technique can be used to provide information to the user as to why the current data settings don’t meet the script’s requirements. (Throwing an error in the function is examined in more detail at the bottom of this page.)
Example Form Validation Action
The example OmniGraffle action detailed below, demonstrates the use of the Form class validate function to make sure that the user has interacted with the dialog to provide data or selections that meet the scripts criteria, which in this case is: a title, a date later than the current date, and approval of a checkbox.
NOTE: this example action doesn’t perform any actions, it is intended to be a simple example of form validation.
Form Validation | ||
01 | /*{ | |
02 | "type": "action", | |
03 | "targets": ["omnigraffle"], | |
04 | "author": "Otto Automator", | |
05 | "identifier": "com.omni-automation.og.form-validation", | |
06 | "description": "Displays an example form.", | |
07 | "label": "Form Validation Example", | |
08 | "shortLabel": "Validate" | |
09 | }*/ | |
10 | (() => { | |
11 | var action = new PlugIn.Action(function(selection, sender){ | |
12 | ||
13 | // DIALOG PROMPT AND OK BUTTON TITLE | |
14 | let formPrompt = "Enter a title and choose a date after today:" | |
15 | let buttonTitle = "Continue" | |
16 | ||
17 | // CONSTRUCT THE FORM | |
18 | var inputForm = new Form() | |
19 | ||
20 | // CREATE FORM ELEMENTS: TEXT INPUT, DATE INPUT, AND CHECKBOX | |
21 | textField = new Form.Field.String("title", "Title") | |
22 | dateField = new Form.Field.Date("date", "Date") | |
23 | checkbox = new Form.Field.Checkbox( | |
24 | "checkbox", | |
25 | "I accept the terms and conditions", | |
26 | false | |
27 | ) | |
28 | ||
29 | // ADD THE ELEMENTS TO THE FORM | |
30 | inputForm.addField(textField) | |
31 | inputForm.addField(dateField) | |
32 | inputForm.addField(checkbox) | |
33 | ||
34 | // DISPLAY THE FORM DIALOG | |
35 | formPromise = inputForm.show(formPrompt, buttonTitle) | |
36 | ||
37 | // VALIDATE FORM CONTENT | |
38 | inputForm.validate = function(formObject){ | |
39 | // EXTRACT VALUES FROM THE FORM’S VALUES OBJECT | |
40 | textValue = formObject.values['title'] | |
41 | //--> "Report" | |
42 | ||
43 | dateObject = formObject.values['date'] | |
44 | //--> "2019-01-19T08:00:00.000Z" | |
45 | ||
46 | checkValue = formObject.values['checkbox'] | |
47 | //--> true | |
48 | ||
49 | console.log(JSON.stringify(formObject.values)) | |
50 | //--> {"checkbox":true,"title":"Report","date":"2019-01-19T08:00:00.000Z"} | |
51 | ||
52 | // HAS TEXT BEEN ENTERED IN THE INPUT FIELD? | |
53 | textStatus = (textValue && textValue.length > 0) ? true:false | |
54 | ||
55 | // IS THE PROVIDED DATE LATER THAN TODAY? | |
56 | dateStatus = (dateObject && dateObject > new Date()) ? true:false | |
57 | ||
58 | // ALL CONDITIONS MUST BE TRUE TO VALIDATE | |
59 | validation = (textStatus && dateStatus && checkValue) ? true:false | |
60 | ||
61 | // RETURN THE VALIDATION STATUS | |
62 | return validation | |
63 | } | |
64 | }); | |
65 | ||
66 | return action; | |
67 | })(); |
The illustration below shows the logging statements in Console window during the user’s interaction with the dialog:
Displaying Error Messages in the Dialog
When designing your form dialogs, you may want to the provide the user with feedback if entered or selected data does not meet the criteria necessary for the script to perform its tasks correctly. You can provide feedback by returning an error string instead of a boolean value.
The example below would replace the ternary operator in line 56 of the example action shown above. If the user enters a date earlier than the current date, the form dialog window will display the error text at the bottom of the dialog.
Check with Error Display | ||
01 | // IS THE PROVIDED DATE LATER THAN TODAY? | |
02 | if (dateObject){ | |
03 | if (dateObject > new Date()){ | |
04 | return true | |
05 | } else { | |
06 | throw "ERROR: The provided date must be later than today." | |
07 | } | |
08 | } |
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. In the video below, the second menu (displaying the city names) is a follow-on menu to the one at the top of the form dialog:
While the creation of the form and its initial elements are done prior to the calling of the show() method, the logic and manipulation of the dialog elements occurs within the form’s validation function. Therefore, such code must include checks for the state of the various form elements, as the validation function gets called each time a form element is edited.
Note that while the contents of a menu element cannot be edited, the menu itself can be replaced by another whose contents are different than one it replaces. To delete a menu, the removeField(…) method is called on the form object using the form menu to be deleted as the method’s parameter.
The example OmniGraffle action (shown below) displays a form dialog whose second menu is a follow-on of the first (topmost) menu:
Follow-On Menus | ||
01 | /*{ | |
02 | "type": "action", | |
03 | "targets": ["omnigraffle"], | |
04 | "author": "Otto Automator", | |
05 | "identifier": "com.omni-automation.og.follow-on-menus", | |
06 | "description": "Displays a form with follow-on menus.", | |
07 | "label": "Follow-On Menus", | |
08 | "shortLabel": "Menus" | |
09 | }*/ | |
10 | (() => { | |
11 | var action = new PlugIn.Action(function(selection, sender){ | |
12 | ||
13 | // DIALOG PROMPT AND OK BUTTON TITLE | |
14 | let formPrompt = "Select a continent and a city:" | |
15 | let buttonTitle = "Continue" | |
16 | ||
17 | // CONSTRUCT THE FORM | |
18 | var inputForm = new Form() | |
19 | ||
20 | var continentMenu = new Form.Field.Option( | |
21 | "continent", | |
22 | "Continent", | |
23 | [0, 1, 2], | |
24 | ["Africa","Asia","Europe"], | |
25 | 0 | |
26 | ) | |
27 | ||
28 | let AfricanCities = ["Lagos, Nigeria","Cairo, Egypt","Nairobi, Kenya","Addis Ababa, Ethiopia"] | |
29 | let AfricanCitiesIndexes = [0, 1, 2, 3] | |
30 | let AsianCities = ["Toyko, Japan","Dehli, India","Shanghai, China","Karachi, Pakistan"] | |
31 | let AsianCitiesIndexes = [4, 5, 6, 7] | |
32 | let EuropeanCities = ["Istanbul, Turkey","London, England","Paris, France","Moscow, Russia"] | |
33 | let EuropeanCitiesIndexes = [8, 9, 10, 11] | |
34 | let allCities = AfricanCities.concat(AsianCities).concat(EuropeanCities) | |
35 | ||
36 | AfricanCitiesMenu = new Form.Field.Option( | |
37 | "city", | |
38 | "City", | |
39 | AfricanCitiesIndexes, | |
40 | AfricanCities, | |
41 | AfricanCitiesIndexes[0] | |
42 | ) | |
43 | ||
44 | // ADD THE ELEMENTS TO THE FORM | |
45 | inputForm.addField(continentMenu) | |
46 | inputForm.addField(AfricanCitiesMenu) | |
47 | ||
48 | // DISPLAY THE FORM DIALOG | |
49 | formPromise = inputForm.show(formPrompt, buttonTitle) | |
50 | ||
51 | // VALIDATE FORM CONTENT | |
52 | inputForm.validate = function(formObject){ | |
53 | continentIndex = formObject.values["continent"] | |
54 | cityIndex = formObject.values["city"] | |
55 | ||
56 | if(typeof cityIndex != "undefined"){ | |
57 | if (continentIndex == 0 && !AfricanCitiesIndexes.includes(cityIndex)){ | |
58 | inputForm.removeField(inputForm.fields[1]) | |
59 | } else if (continentIndex == 1 && !AsianCitiesIndexes.includes(cityIndex)){ | |
60 | inputForm.removeField(inputForm.fields[1]) | |
61 | } else if (continentIndex == 2 && !EuropeanCitiesIndexes.includes(cityIndex)){ | |
62 | inputForm.removeField(inputForm.fields[1]) | |
63 | } | |
64 | } | |
65 | ||
66 | if (formObject.fields.length == 1){ | |
67 | switch(continentIndex){ | |
68 | case 0: | |
69 | AfricanCitiesMenu = new Form.Field.Option( | |
70 | "city", | |
71 | "City", | |
72 | AfricanCitiesIndexes, | |
73 | AfricanCities, | |
74 | AfricanCitiesIndexes[0] | |
75 | ) | |
76 | inputForm.addField(AfricanCitiesMenu) | |
77 | break; | |
78 | case 1: | |
79 | AsianCitiesMenu = new Form.Field.Option( | |
80 | "city", | |
81 | "City", | |
82 | AsianCitiesIndexes, | |
83 | AsianCities, | |
84 | AsianCitiesIndexes[0] | |
85 | ) | |
86 | inputForm.addField(AsianCitiesMenu) | |
87 | break; | |
88 | case 2: | |
89 | EuropeanCitiesMenu = new Form.Field.Option( | |
90 | "city", | |
91 | "City", | |
92 | EuropeanCitiesIndexes, | |
93 | EuropeanCities, | |
94 | EuropeanCitiesIndexes[0] | |
95 | ) | |
96 | inputForm.addField(EuropeanCitiesMenu) | |
97 | } | |
98 | } | |
99 | return true | |
100 | } | |
101 | ||
102 | // PROCESS THE FORM RESULTS | |
103 | formPromise.then(function(formObject){ | |
104 | // RETRIEVE CHOSEN VAUES | |
105 | cityIndex = formObject.values["city"] | |
106 | chosenCity = allCities[cityIndex] | |
107 | ||
108 | // PERFORM TASKS | |
109 | encodedCityName = encodeURIComponent(chosenCity) | |
110 | urlStr = 'maps://maps.apple.com/?address=' + encodedCityName | |
111 | url = URL.fromString(urlStr) | |
112 | url.open() | |
113 | }) | |
114 | ||
115 | // PROCESS FORM CANCELLATION | |
116 | formPromise.catch(function(error){ | |
117 | console.log("form cancelled", error) | |
118 | }) | |
119 | ||
120 | }); | |
121 | ||
122 | return action; | |
123 | })(); |
This webpage is in the process of being developed. Any content may change and may not be accurate or complete at this time.