Plug-In Forms: Examples

Well-designed Omni Automation actions often prompt and query the user to provide data, or make a selection, choice, or decision as to how the script should proceed. The Form class enables scripts to gather information from the user, by presenting dialogs containing data expressed using standard interface elements: text input fields, date pickers, option menus, and checkboxes.

The following example forms address common scripting scenarios and can be easily added to your Omni Automaton Plug-In Templates and customized to suit the individual script requirements.

NOTE: This page contains interactive script examples that are executed only with your approval. See the section on Script Security for details about allowing the execution of external scripts, like those included in this website.

TOPICS: Text InputOptions Menu/ListDate Input

 

Text Input

Plug-in Forms using instances of the Form.Field.String class (text input fields). (documentation)

Basic Text InputURL InputEnter OmniFocus TagText with ApprovalMultiple Text InputsText Input is IntegerInput is Integer within RangeAlpha-Numeric InputValid eMail Address

 

Basic Text Input

In this form example, the user is prompted to provide text input. The form’s validation function ensures that the approval button will not be enabled without the user first providing such input.

single-text-input
(async () => { try { textInputField = new Form.Field.String( "textInput", "Field Label", null ) inputForm = new Form() inputForm.addField(textInputField) inputForm.validate = function(formObject){ inputText = formObject.values['textInput'] return ((!inputText)?false:true) } formPrompt = "Form prompt:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) textValue = formObject.values['textInput'] console.log('textValue: ',textValue) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Single Text Input
  

(async () => { try { textInputField = new Form.Field.String( "textInput", "Field Label", null ) inputForm = new Form() inputForm.addField(textInputField) inputForm.validate = function(formObject){ inputText = formObject.values['textInput'] return ((!inputText)?false:true) } formPrompt = "Form prompt:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) textValue = formObject.values['textInput'] console.log('textValue: ',textValue) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
 

URL Input Form

This form presents a single text input field that accepts a text string that can be made into a valid URL object:

url-input-dialog
(async () => { try { // CREATE FORM FOR GATHERING USER INPUT inputForm = new Form() // CREATE TEXT FIELD textField = new Form.Field.String( "textInput", null, null ) // ADD THE FIELDS TO THE FORM inputForm.addField(textField) // VALIDATE THE USER INPUT inputForm.validate = function(formObject){ textValue = formObject.values["textInput"] textStatus = (textValue && textValue.length > 0) ? true:false schema = [ "http://", "https://", "mailto:", "omnioutliner://", "omniplan://", "omnigraffle://", "omnifocus://" ] if (textStatus){ var i; for (i = 0; i < schema.length; i++) { if (textValue.startsWith(schema[i], 0) && URL.fromString(textValue)){return true} } } return false } // PRESENT THE FORM TO THE USER formPrompt = "Enter the full URL:" formObject = await inputForm.show(formPrompt,"Continue") // PROCESSING USING THE DATA EXTRACTED FROM THE FORM textValue = formObject.values["textInput"] url = URL.fromString(textValue) console.log(url) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
URL Input
  

(async () => { try { // CREATE FORM FOR GATHERING USER INPUT inputForm = new Form() // CREATE TEXT FIELD textField = new Form.Field.String( "textInput", null, null ) // ADD THE FIELDS TO THE FORM inputForm.addField(textField) // VALIDATE THE USER INPUT inputForm.validate = function(formObject){ textValue = formObject.values["textInput"] textStatus = (textValue && textValue.length > 0) ? true:false schema = [ "http://", "https://", "mailto:", "omnioutliner://", "omniplan://", "omnigraffle://", "omnifocus://" ] if (textStatus){ var i; for (i = 0; i < schema.length; i++) { if (textValue.startsWith(schema[i], 0) && URL.fromString(textValue)){return true} } } return false } // PRESENT THE FORM TO THE USER formPrompt = "Enter the full URL:" formObject = await inputForm.show(formPrompt,"Continue") // PROCESSING USING THE DATA EXTRACTED FROM THE FORM textValue = formObject.values["textInput"] url = URL.fromString(textValue) console.log(url) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
 

Enter an Existing OmniFocus Tag

Here’s an example form containing a single text input field in which the user enters the title of an existing tag.

enter-existing-of-tag
(async () => { try { tagNames = flattenedTags.map(tag => tag.name) if (tagNames.length === 0){throw Error("No tags in database")} textInputField = new Form.Field.String( "textInput", "Tag", null ) checkSwitchField = new Form.Field.Checkbox( "checkboxSwitch", "Create tag if not existing", false ) inputForm = new Form() inputForm.addField(textInputField) inputForm.addField(checkSwitchField) inputForm.validate = function(formObject){ inputText = formObject.values['textInput'] shouldCreateTag = formObject.values['checkboxSwitch'] if(!inputText){return false} if(shouldCreateTag){return true} return (tagNames.includes(inputText)) } formPrompt = "Enter the title of an existing tag:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) tagTitle = formObject.values['textInput'] shouldCreateTag = formObject.values['checkboxSwitch'] targetTag = null tags.apply(tag => { if(tag.name === tagTitle){ targetTag = tag return ApplyResult.Stop } }) if(!targetTag && shouldCreateTag){var targetTag = new Tag(tagTitle)} console.log('tag:',targetTag) // PROCESS STATEMENTS } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
OmniFocus: Enter Name of Existing Tag
  

(async () => { try { tagNames = flattenedTags.map(tag => tag.name) if (tagNames.length === 0){throw Error("No tags in database")} textInputField = new Form.Field.String( "textInput", "Tag", null ) checkSwitchField = new Form.Field.Checkbox( "checkboxSwitch", "Create tag if not existing", false ) inputForm = new Form() inputForm.addField(textInputField) inputForm.addField(checkSwitchField) inputForm.validate = function(formObject){ inputText = formObject.values['textInput'] shouldCreateTag = formObject.values['checkboxSwitch'] if(!inputText){return false} if(shouldCreateTag){return true} return (tagNames.includes(inputText)) } formPrompt = "Enter the title of an existing tag:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) tagTitle = formObject.values['textInput'] shouldCreateTag = formObject.values['checkboxSwitch'] targetTag = null tags.apply(tag => { if(tag.name === tagTitle){ targetTag = tag return ApplyResult.Stop } }) if(!targetTag && shouldCreateTag){var targetTag = new Tag(tagTitle)} console.log('tag:',targetTag) // PROCESS STATEMENTS } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
 

Text Input with Mandatory Checkbox

In this example form, the dialog cannot be approved without the user providing text and selecting the provided checkbox.

text-input-with-mandatory-checkbox
(async () => { try { textInputField = new Form.Field.String( "textInput", "Field Label", null ) checkSwitchField = new Form.Field.Checkbox( "checkboxSwitch", "I accept the standard terms and conditions", null ) inputForm = new Form() inputForm.addField(textInputField) inputForm.addField(checkSwitchField) inputForm.validate = function(formObject){ var inputText = formObject.values['textInput'] var textStatus = ((!inputText)?false:true) var checkboxStatus = formObject.values['checkboxSwitch'] return (textStatus && checkboxStatus) } formPrompt = "Form prompt:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) textValue = formObject.values['textInput'] console.log('textValue: ',textValue) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Text Input with Mandatory Checkbox
  

(async () => { try { textInputField = new Form.Field.String( "textInput", "Field Label", null ) checkSwitchField = new Form.Field.Checkbox( "checkboxSwitch", "I accept the standard terms and conditions", null ) inputForm = new Form() inputForm.addField(textInputField) inputForm.addField(checkSwitchField) inputForm.validate = function(formObject){ var inputText = formObject.values['textInput'] var textStatus = ((!inputText)?false:true) var checkboxStatus = formObject.values['checkboxSwitch'] return (textStatus && checkboxStatus) } formPrompt = "Form prompt:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) textValue = formObject.values['textInput'] console.log('textValue: ',textValue) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
 

Multiple Mandatory Text Inputs

In this example, the user must supply textual input in all of the provided fields before the approval button will be enabled. NOTE: to change a text field to “optional input” simply remove its status variable from the validation statement on line 38.

multiple-text-input
(async () => { try { textInputField01 = new Form.Field.String( "textInput01", "Field Label 1", null ) textInputField02 = new Form.Field.String( "textInput02", "Field Label 2", null ) textInputField03 = new Form.Field.String( "textInput03", "Field Label 3", null ) inputForm = new Form() inputForm.addField(textInputField01) inputForm.addField(textInputField02) inputForm.addField(textInputField03) inputForm.validate = function(formObject){ inputText01 = formObject.values['textInput01'] inputText01Status = (!inputText01)?false:true inputText02 = formObject.values['textInput02'] inputText02Status = (!inputText02)?false:true inputText03 = formObject.values['textInput03'] inputText03Status = (!inputText03)?false:true // ALL CONDITIONS MUST BE TRUE TO VALIDATE validation = (inputText01Status && inputText02Status && inputText03Status) ? true:false return validation } formPrompt = "Form prompt:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) textValue01 = formObject.values['textInput01'] textValue02 = formObject.values['textInput02'] textValue03 = formObject.values['textInput03'] console.log('textInput01: ',textValue01) console.log('textInput02: ',textValue02) console.log('textInput03: ',textValue03) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Multiple Mandatory Text Inputs
  

(async () => { try { textInputField01 = new Form.Field.String( "textInput01", "Field Label 1", null ) textInputField02 = new Form.Field.String( "textInput02", "Field Label 2", null ) textInputField03 = new Form.Field.String( "textInput03", "Field Label 3", null ) inputForm = new Form() inputForm.addField(textInputField01) inputForm.addField(textInputField02) inputForm.addField(textInputField03) inputForm.validate = function(formObject){ inputText01 = formObject.values['textInput01'] inputText01Status = (!inputText01)?false:true inputText02 = formObject.values['textInput02'] inputText02Status = (!inputText02)?false:true inputText03 = formObject.values['textInput03'] inputText03Status = (!inputText03)?false:true // ALL CONDITIONS MUST BE TRUE TO VALIDATE validation = (inputText01Status && inputText02Status && inputText03Status) ? true:false return validation } formPrompt = "Form prompt:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) textValue01 = formObject.values['textInput01'] textValue02 = formObject.values['textInput02'] textValue03 = formObject.values['textInput03'] console.log('textInput01: ',textValue01) console.log('textInput02: ',textValue02) console.log('textInput03: ',textValue03) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
 

Text Input is Integer

In this example, the user must enter an integer (whole number):

(async () => { try { textInputField = new Form.Field.String( "textInput", null, null ) inputForm = new Form() inputForm.addField(textInputField) inputForm.validate = function(formObject){ inputText = formObject.values['textInput'] if (!inputText){return false} var isnum = /^[0-9]+$/i.test(inputText) return isnum } formPrompt = "Enter a whole number:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) textValue = formObject.values['textInput'] intValue = parseInt(textValue) console.log('intValue:',intValue) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Text Input is Integer
  

(async () => { try { textInputField = new Form.Field.String( "textInput", null, null ) inputForm = new Form() inputForm.addField(textInputField) inputForm.validate = function(formObject){ inputText = formObject.values['textInput'] if (!inputText){return false} var isnum = /^[0-9]+$/i.test(inputText) return isnum } formPrompt = "Enter a whole number:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) textValue = formObject.values['textInput'] intValue = parseInt(textValue) console.log('intValue:',intValue) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
 

Text Input is Integer within Range

In this example, the user must enter an integer (whole number) that falls between the specified range, before the approval button is enabled. You can set the range by changing the values of the first two statements of the form.

integer-input
(async () => { try { minValue = 1 maxValue = 34 textInputField = new Form.Field.String( "textInput", String("(" + minValue + "-" + maxValue + ")"), null ) inputForm = new Form() inputForm.addField(textInputField) inputForm.validate = function(formObject){ inputText = formObject.values['textInput'] if (!inputText) {return false} var isnum = /^[0-9]+$/i.test(inputText) if (isnum){ var intValue = parseInt(inputText) return ((intValue <= maxValue && intValue >= minValue) ? true:false) } return false } formPrompt = "Enter a whole number:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) textValue = formObject.values['textInput'] intValue = parseInt(textValue) console.log('intValue:', intValue) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Input is Integer within Range
  

(async () => { try { minValue = 1 maxValue = 34 textInputField = new Form.Field.String( "textInput", String("(" + minValue + "-" + maxValue + ")"), null ) inputForm = new Form() inputForm.addField(textInputField) inputForm.validate = function(formObject){ inputText = formObject.values['textInput'] if (!inputText) {return false} var isnum = /^[0-9]+$/i.test(inputText) if (isnum){ var intValue = parseInt(inputText) return ((intValue <= maxValue && intValue >= minValue) ? true:false) } return false } formPrompt = "Enter a whole number:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) textValue = formObject.values['textInput'] intValue = parseInt(textValue) console.log('intValue:', intValue) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
 

Text Input with Only Alpha-Numeric Characters

In this form example, the text input field will only accept alpha numeric characters (a-z A-Z and 0-9).

alpha-numeric-input
(async () => { try { textInputField = new Form.Field.String( "textInput", "(a-z, 0-9)", null ) inputForm = new Form() inputForm.addField(textInputField) inputForm.validate = function(formObject){ textValue = formObject.values['textInput'] if(!textValue){return false} return (/^[a-z0-9]+$/i.test(textValue)) } formPrompt = "Enter an alpha-numeric string:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) textValue = formObject.values['textInput'] console.log("textValue:",textValue) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Require Alpha-Numeric Input
  

(async () => { try { textInputField = new Form.Field.String( "textInput", "(a-z, 0-9)", null ) inputForm = new Form() inputForm.addField(textInputField) inputForm.validate = function(formObject){ textValue = formObject.values['textInput'] if(!textValue){return false} return (/^[a-z0-9]+$/i.test(textValue)) } formPrompt = "Enter an alpha-numeric string:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) textValue = formObject.values['textInput'] console.log("textValue:",textValue) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
 

Enter Valid eMail Address

In this form example, the approval button will only become enabled when a fully-formed email address is entered in the text input field. For example: bob123@omniapps.uk or carla486@outlinerworld.com

email-address-input
(async () => { try { textInputField = new Form.Field.String( "textInput", null, null ) inputForm = new Form() inputForm.addField(textInputField) inputForm.validate = function(formObject){ var textValue = formObject.values['textInput'] if(!textValue){return false} if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(textValue)){ return true } else { throw "ERROR: not a valid eMail address." } } formPrompt = "Enter an eMail address:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) textValue = formObject.values['textInput'] console.log("textValue: ",textValue) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Enter Valid eMail Address
  

(async () => { try { textInputField = new Form.Field.String( "textInput", null, null ) inputForm = new Form() inputForm.addField(textInputField) inputForm.validate = function(formObject){ var textValue = formObject.values['textInput'] if(!textValue){return false} if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(textValue)){ return true } else { throw "ERROR: not a valid eMail address." } } formPrompt = "Enter an eMail address:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) textValue = formObject.values['textInput'] console.log("textValue: ",textValue) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
 

Options Menu/List

Plug-in Forms using instances of the Form.Field.Option class (option menus or lists). (documentation)

Basic Options Menu/List(OmniFocus) Projects with Tag(OmniFocus) Perspectives MenuSelect Date (Follow-On Menus)Filtered Menu Form

 

Basic Options Menu

The following form will display a single options menu:

basic-options-form
(async () => { try { menuItems = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"] menuIndexes = [0,1,2,3,4,5,6] menuElement = new Form.Field.Option( "menuElement", null, menuIndexes, menuItems, 0 ) inputForm = new Form() inputForm.addField(menuElement) inputForm.validate = function(formObject){ return true } formPrompt = "Choose one of the items:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) menuIndex = formObject.values['menuElement'] chosenItem = menuItems[menuIndex] console.log('Chosen item:', chosenItem) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Basic Options Menu/List
  

(async () => { try { menuItems = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"] menuIndexes = [0,1,2,3,4,5,6] menuElement = new Form.Field.Option( "menuElement", null, menuIndexes, menuItems, 0 ) inputForm = new Form() inputForm.addField(menuElement) inputForm.validate = function(formObject){ return true } formPrompt = "Choose one of the items:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) menuIndex = formObject.values['menuElement'] chosenItem = menuItems[menuIndex] console.log('Chosen item:', chosenItem) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
 

(OmniFocus) Menu/List of Projects with Specific Tag

In the following form example, a selection menu is displayed that lists projects that are tagged with a pre-specified tag.

projects-with-tag-menu

To use the form, change the name of the target tag from “Camera-Ready” to the title of the tag you wish to specify:

(async () => { try { targetTagName = "Camera-Ready" targetTag = null tags.apply(function(tag){ if(tag.name == targetTagName){ targetTag = tag return ApplyResult.Stop } }) if (!targetTag){ errMsg1 = `There is no tag titled: ”${targetTagName}”` throw new Error(errMsg1) } projectsWithTag = targetTag.projects if (projectsWithTag.length === 0){ errMsg2 = `No projects are tagged with: ”${targetTagName}”` throw new Error(errMsg2) } projectsWithTag.sort((a, b) => { var x = a.name.toLowerCase() var y = b.name.toLowerCase() if (x < y) {return -1} if (x > y) {return 1} return 0 }) projectNames = projectsWithTag.map(project => project.name) menuIndexes = projectNames.map((name, index) => index) projectsMenu = new Form.Field.Option( "projectMenu", null, menuIndexes, projectNames, 0 ) inputForm = new Form() inputForm.addField(projectsMenu) inputForm.validate = function(formObject){ return true } formPrompt = `Choose a “${targetTagName}” project:` buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) projectIndex = formObject.values['projectMenu'] chosenProject = projectsWithTag[projectIndex] console.log(chosenProject) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
(OmniFocus) Projects with Specified Tag
  

(async () => { try { targetTagName = "Camera-Ready" targetTag = null tags.apply(function(tag){ if(tag.name == targetTagName){ targetTag = tag return ApplyResult.Stop } }) if (!targetTag){ errMsg1 = `There is no tag titled: ”${targetTagName}”` throw new Error(errMsg1) } projectsWithTag = targetTag.projects if (projectsWithTag.length === 0){ errMsg2 = `No projects are tagged with: ”${targetTagName}”` throw new Error(errMsg2) } projectsWithTag.sort((a, b) => { var x = a.name.toLowerCase() var y = b.name.toLowerCase() if (x < y) {return -1} if (x > y) {return 1} return 0 }) projectNames = projectsWithTag.map(project => project.name) menuIndexes = projectNames.map((name, index) => index) projectsMenu = new Form.Field.Option( "projectMenu", null, menuIndexes, projectNames, 0 ) inputForm = new Form() inputForm.addField(projectsMenu) inputForm.validate = function(formObject){ return true } formPrompt = `Choose a “${targetTagName}” project:` buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) projectIndex = formObject.values['projectMenu'] chosenProject = projectsWithTag[projectIndex] console.log(chosenProject) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
 

(OmniFocus) Menu of Perspectives

This plug-in will display a form containing a list of the built-in perspectives from which the user selects the one to be displayed.

perspectives-menu

This form example demonstrates the use of any array of objects rather than an array of strings (names) as the source for the menu/list items. The result of the user’s selection is an object.

(async () => { try { perspectives = Perspective.BuiltIn.all.concat(Perspective.Custom.all) pnames = perspectives.map(p => p.name) pindexes = pnames.map((p, idx) => idx) perspectiveMenu = new Form.Field.Option( "perspective", "Perspective", pindexes, pnames, 0 ) inputForm = new Form() inputForm.addField(perspectiveMenu) inputForm.validate = function(formObject){ return true } formPrompt = "Choose the perspective:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) chosenPerspectiveIdx = formObject.values['perspective'] chosenPerspective = perspectives[chosenPerspectiveIdx] document.windows[0].perspective = chosenPerspective } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
(OmniFocus) Show Chosen Perspective
  

(async () => { try { perspectives = Perspective.BuiltIn.all.concat(Perspective.Custom.all) pnames = perspectives.map(p => p.name) pindexes = pnames.map((p, idx) => idx) perspectiveMenu = new Form.Field.Option( "perspective", "Perspective", pindexes, pnames, 0 ) inputForm = new Form() inputForm.addField(perspectiveMenu) inputForm.validate = function(formObject){ return true } formPrompt = "Choose the perspective:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt,buttonTitle) chosenPerspectiveIdx = formObject.values['perspective'] chosenPerspective = perspectives[chosenPerspectiveIdx] document.windows[0].perspective = chosenPerspective } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
 

Select a Date (follow-on menus)

In this example of date input alternatives, the user can choose any date by selecting from a series of of dynamic follow-on menus.

A “follow-on” menu or list is one whose content changes based upon the selection of a previous menu. In this example form, the “Day” menu is a follow-on menu to the “Month” menu. See the Form Validation section further explanation about follow-on menus.

(⬇ see below ) The form on macOS:

select-date-from-menus
(async () => { try { locale = new Intl.Collator().resolvedOptions().locale.toLowerCase() monthNames = [] for (i = 0; i < 12; i++) { var objDate = new Date() objDate.setDate(1) objDate.setMonth(i) monthName = objDate.toLocaleString(locale,{month:"long"}) monthNames.push(monthName) } now = new Date() currentYear = now.getFullYear() yearNames = new Array() yearIndexes = new Array() for (i = 0; i < 4; i++) { yearNames.push(String(currentYear + i)) yearIndexes.push(i) } dayCount = new Date(currentYear, 1, 0).getDate() dayIndexes = new Array() dayIndexStrings = new Array() for (var i = 0; i < dayCount; i++){ dayIndexes.push(i) dayIndexStrings.push(String(i + 1)) } inputForm = new Form() yearMenu = new Form.Field.Option( "yearMenu", "Year", yearIndexes, yearNames, 0 ) currentYearIndex = 0 monthMenu = new Form.Field.Option( "monthMenu", "Month", [0,1,2,3,4,5,6,7,8,9,10,11], monthNames, 0 ) currentMonthIndex = 0 dayMenu = new Form.Field.Option( "dayMenu", "Day", dayIndexes, dayIndexStrings, 0 ) inputForm.addField(yearMenu) inputForm.addField(monthMenu) inputForm.addField(dayMenu) inputForm.validate = function(formObject){ yearMenuIndex = formObject.values["yearMenu"] chosenYear = parseInt(yearNames[yearMenuIndex]) monthMenuIndex = formObject.values["monthMenu"] if(monthMenuIndex != currentMonthIndex || yearMenuIndex != currentYearIndex){ inputForm.removeField(inputForm.fields[2]) currentMonthIndex = monthMenuIndex currentYearIndex = yearMenuIndex } if (formObject.fields.length == 2){ dayCount = new Date(chosenYear, monthMenuIndex + 1, 0).getDate() dayIndexes = new Array() dayIndexStrings = new Array() for (var i = 0; i < dayCount; i++){ dayIndexes.push(i) dayIndexStrings.push(String(i + 1)) } dayMenu = new Form.Field.Option( "dayMenu", "Day", dayIndexes, dayIndexStrings, 0 ) inputForm.addField(dayMenu) } return true } formPrompt = "Select the date:" buttonTitle = "OK" formObject = await inputForm.show(formPrompt, buttonTitle) yearMenuIndex = formObject.values["yearMenu"] yearName = yearNames[yearMenuIndex] monthMenuIndex = formObject.values["monthMenu"] monthName = monthNames[monthMenuIndex] dayMenuIndex = formObject.values["dayMenu"] dayIndexString = dayIndexStrings[dayMenuIndex] targetDate = new Date(monthName + " " + dayIndexString + " " + yearName) console.log(targetDate) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Select a Date (menus)
  

(async () => { try { locale = new Intl.Collator().resolvedOptions().locale.toLowerCase() monthNames = [] for (i = 0; i < 12; i++) { var objDate = new Date() objDate.setDate(1) objDate.setMonth(i) monthName = objDate.toLocaleString(locale,{month:"long"}) monthNames.push(monthName) } now = new Date() currentYear = now.getFullYear() yearNames = new Array() yearIndexes = new Array() for (i = 0; i < 4; i++) { yearNames.push(String(currentYear + i)) yearIndexes.push(i) } dayCount = new Date(currentYear, 1, 0).getDate() dayIndexes = new Array() dayIndexStrings = new Array() for (var i = 0; i < dayCount; i++){ dayIndexes.push(i) dayIndexStrings.push(String(i + 1)) } inputForm = new Form() yearMenu = new Form.Field.Option( "yearMenu", "Year", yearIndexes, yearNames, 0 ) currentYearIndex = 0 monthMenu = new Form.Field.Option( "monthMenu", "Month", [0,1,2,3,4,5,6,7,8,9,10,11], monthNames, 0 ) currentMonthIndex = 0 dayMenu = new Form.Field.Option( "dayMenu", "Day", dayIndexes, dayIndexStrings, 0 ) inputForm.addField(yearMenu) inputForm.addField(monthMenu) inputForm.addField(dayMenu) inputForm.validate = function(formObject){ yearMenuIndex = formObject.values["yearMenu"] chosenYear = parseInt(yearNames[yearMenuIndex]) monthMenuIndex = formObject.values["monthMenu"] if(monthMenuIndex != currentMonthIndex || yearMenuIndex != currentYearIndex){ inputForm.removeField(inputForm.fields[2]) currentMonthIndex = monthMenuIndex currentYearIndex = yearMenuIndex } if (formObject.fields.length == 2){ dayCount = new Date(chosenYear, monthMenuIndex + 1, 0).getDate() dayIndexes = new Array() dayIndexStrings = new Array() for (var i = 0; i < dayCount; i++){ dayIndexes.push(i) dayIndexStrings.push(String(i + 1)) } dayMenu = new Form.Field.Option( "dayMenu", "Day", dayIndexes, dayIndexStrings, 0 ) inputForm.addField(dayMenu) } return true } formPrompt = "Select the date:" buttonTitle = "OK" formObject = await inputForm.show(formPrompt, buttonTitle) yearMenuIndex = formObject.values["yearMenu"] yearName = yearNames[yearMenuIndex] monthMenuIndex = formObject.values["monthMenu"] monthName = monthNames[monthMenuIndex] dayMenuIndex = formObject.values["dayMenu"] dayIndexString = dayIndexStrings[dayMenuIndex] targetDate = new Date(monthName + " " + dayIndexString + " " + yearName) console.log(targetDate) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();

(⬇ see below ) The form on iPadOS:

choose-date-from-menus

Appointment Time Form

Here is an example form for choosing the next upcoming workday/time slot:

appointment interface appointment confirmation
(async () => { try { menu1Items = ["Monday","Tuesday","Wednesday","Thursday","Friday"] menu1Indexes = new Array() menu1Items.forEach((item, index) => menu1Indexes.push(index)) menu2Items = ["9:00 AM", "9:15 AM", "9:30 AM", "9:45 AM", "10:00 AM", "10:15 AM", "10:30 AM", "10:45 AM", "11:00 AM", "11:15 AM", "11:30 AM", "11:45 AM", "12:00 PM", "12:15 PM", "12:30 PM", "12:45 PM", "1:00 PM", "1:15 PM", "1:30 PM", "1:45 PM", "2:00 PM", "2:15 PM", "2:30 PM", "2:45 PM", "3:00 PM", "3:15 PM", "3:30 PM", "3:45 PM", "4:00 PM", "4:15 PM", "4:30 PM", "4:45 PM"] menu2Indexes = new Array() menu2Items.forEach((item, index) => menu2Indexes.push(index)) menu1Element = new Form.Field.Option( "menu1Element", null, menu1Indexes, menu1Items, 0 ) menu2Element = new Form.Field.Option( "menu2Element", null, menu2Indexes, menu2Items, 0 ) inputForm = new Form() inputForm.addField(menu1Element) inputForm.addField(menu2Element) inputForm.validate = function(formObject){ return true } formPrompt = "Select a day and time:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) menu1Index = formObject.values['menu1Element'] chosen1Item = menu1Items[menu1Index] menu2Index = formObject.values['menu2Element'] chosen2Item = menu2Items[menu2Index] chosen2Item = chosen2Item.replace(' ', '') fmatr = Formatter.Date.withStyle( Formatter.Date.Style.Full, Formatter.Date.Style.Short ) dateObj = fmatr.dateFromString(`${chosen1Item} @ ${chosen2Item}`) dateStr = fmatr.stringFromDate(dateObj) // CONFIRMATION ALERT alertTitle = "Form Result" alertMessage = "Your appointment is for:\n\n" + dateStr new Alert(alertTitle, alertMessage).show() } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Select Next Upcoming Appointment Time
  

(async () => { try { menu1Items = ["Monday","Tuesday","Wednesday","Thursday","Friday"] menu1Indexes = new Array() menu1Items.forEach((item, index) => menu1Indexes.push(index)) menu2Items = ["9:00 AM", "9:15 AM", "9:30 AM", "9:45 AM", "10:00 AM", "10:15 AM", "10:30 AM", "10:45 AM", "11:00 AM", "11:15 AM", "11:30 AM", "11:45 AM", "12:00 PM", "12:15 PM", "12:30 PM", "12:45 PM", "1:00 PM", "1:15 PM", "1:30 PM", "1:45 PM", "2:00 PM", "2:15 PM", "2:30 PM", "2:45 PM", "3:00 PM", "3:15 PM", "3:30 PM", "3:45 PM", "4:00 PM", "4:15 PM", "4:30 PM", "4:45 PM"] menu2Indexes = new Array() menu2Items.forEach((item, index) => menu2Indexes.push(index)) menu1Element = new Form.Field.Option( "menu1Element", null, menu1Indexes, menu1Items, 0 ) menu2Element = new Form.Field.Option( "menu2Element", null, menu2Indexes, menu2Items, 0 ) inputForm = new Form() inputForm.addField(menu1Element) inputForm.addField(menu2Element) inputForm.validate = function(formObject){ return true } formPrompt = "Select a day and time:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) menu1Index = formObject.values['menu1Element'] chosen1Item = menu1Items[menu1Index] menu2Index = formObject.values['menu2Element'] chosen2Item = menu2Items[menu2Index] chosen2Item = chosen2Item.replace(' ', '') fmatr = Formatter.Date.withStyle( Formatter.Date.Style.Full, Formatter.Date.Style.Short ) dateObj = fmatr.dateFromString(`${chosen1Item} @ ${chosen2Item}`) dateStr = fmatr.stringFromDate(dateObj) // CONFIRMATION ALERT alertTitle = "Form Result" alertMessage = "Your appointment is for:\n\n" + dateStr new Alert(alertTitle, alertMessage).show() } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
 

Filtered Menu

In this form, the contents of the menu is determined by filtering using the entered text. Designed for use in OmniFocus, the form filters existing projects using the entered string, and presents the results in a menu:

Filtered Menu
(async () => { try { // CREATE FORM FOR GATHERING USER INPUT inputForm = new Form() // CREATE TEXT FIELD textField = new Form.Field.String( "textInput", "Search", null ) // CREATE MENU popupMenu = new Form.Field.Option( "menuItem", "Results", [], [], null ) popupMenu.allowsNull = true popupMenu.nullOptionTitle = "0 items" // ADD THE FIELDS TO THE FORM inputForm.addField(textField) inputForm.addField(popupMenu) // VALIDATE THE USER INPUT inputForm.validate = function(formObject){ var textInput = formObject.values["textInput"] if(textInput !== currentValue){ currentValue = textInput // remove popup menu if (inputForm.fields.length === 2){ inputForm.removeField(inputForm.fields[1]) } } if(inputForm.fields.length === 1){ // search using provided string if (!textInput){var searchResults = []} else { var searchResults = projectsMatching(textInput) } var searchResultNames = searchResults.map(item => item.name) var searchResultIDs = searchResults.map(item => item.id.primaryKey) var popupMenu = new Form.Field.Option( "menuItem", "Results", searchResultIDs, searchResultNames, null ) popupMenu.allowsNull = true popupMenu.nullOptionTitle = String(searchResults.length + " items") inputForm.addField(popupMenu) return false } if(!textInput){return false} if(inputForm.fields.length === 2){ menuValue = formObject.values["menuItem"] if(menuValue === undefined || String(menuValue) === "null"){return false} return true } } // PRESENT THE FORM TO THE USER currentValue = null formPrompt = "Enter a project title:" formObject = await inputForm.show(formPrompt,"Continue") // PROCESSING USING THE DATA EXTRACTED FROM THE FORM projectID = formObject.values["menuItem"] projectObj = Project.byIdentifier(projectID) console.log(projectObj) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Filtered Menu Form (OmniFocus)
  

(async () => { try { // CREATE FORM FOR GATHERING USER INPUT inputForm = new Form() // CREATE TEXT FIELD textField = new Form.Field.String( "textInput", "Search", null ) // CREATE MENU popupMenu = new Form.Field.Option( "menuItem", "Results", [], [], null ) popupMenu.allowsNull = true popupMenu.nullOptionTitle = "0 items" // ADD THE FIELDS TO THE FORM inputForm.addField(textField) inputForm.addField(popupMenu) // VALIDATE THE USER INPUT inputForm.validate = function(formObject){ var textInput = formObject.values["textInput"] if(textInput !== currentValue){ currentValue = textInput // remove popup menu if (inputForm.fields.length === 2){ inputForm.removeField(inputForm.fields[1]) } } if(inputForm.fields.length === 1){ // search using provided string if (!textInput){var searchResults = []} else { var searchResults = projectsMatching(textInput) } var searchResultNames = searchResults.map(item => item.name) var searchResultIDs = searchResults.map(item => item.id.primaryKey) var popupMenu = new Form.Field.Option( "menuItem", "Results", searchResultIDs, searchResultNames, null ) popupMenu.allowsNull = true popupMenu.nullOptionTitle = String(searchResults.length + " items") inputForm.addField(popupMenu) return false } if(!textInput){return false} if(inputForm.fields.length === 2){ menuValue = formObject.values["menuItem"] if(menuValue === undefined || String(menuValue) === "null"){return false} return true } } // PRESENT THE FORM TO THE USER currentValue = null formPrompt = "Enter a project title:" formObject = await inputForm.show(formPrompt,"Continue") // PROCESSING USING THE DATA EXTRACTED FROM THE FORM projectID = formObject.values["menuItem"] projectObj = Project.byIdentifier(projectID) console.log(projectObj) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
 

Date Input

Plug-in Forms using instances of the Form.Field.Date class. (Documentation)

Date after TodayStart End Dates

 

Enter Date after Today

In the following example form, the user is prompted to provide a date after today.

date-after-today

NOTE: Date fields will accept a variety of shortcut values. Reference the page on Date Input Fields to learn all of the available date referencing options.

(async () => { try { today = new Date(new Date().setHours(0,0,0,0)) tomorrow = new Date(today.setDate(today.getDate() + 1)) dateInputField = new Form.Field.Date( "dateInput", null, null ) inputForm = new Form() inputForm.addField(dateInputField) inputForm.validate = function(formObject){ dateInput = formObject.values["dateInput"] return (dateInput && dateInput >= tomorrow) } formPrompt = "Enter a date/time after today:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) dateInput = formObject.values["dateInput"] console.log(dateInput) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Date After Today
  

(async () => { try { today = new Date(new Date().setHours(0,0,0,0)) tomorrow = new Date(today.setDate(today.getDate() + 1)) dateInputField = new Form.Field.Date( "dateInput", null, null ) inputForm = new Form() inputForm.addField(dateInputField) inputForm.validate = function(formObject){ dateInput = formObject.values["dateInput"] return (dateInput && dateInput >= tomorrow) } formPrompt = "Enter a date/time after today:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) dateInput = formObject.values["dateInput"] console.log(dateInput) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
 

Start and End Dates

The following example presents two date input fields for entering start and end dates:

select-date-from-menus
(async () => { try { fmatr = Formatter.Date.withStyle(Formatter.Date.Style.Short) currentDateObject = fmatr.dateFromString('today') inputForm = new Form() startDateField = new Form.Field.Date( "startDate", "Start Date", currentDateObject ) endDateField = new Form.Field.Date( "endDate", "End Date", null ) inputForm.addField(startDateField) inputForm.addField(endDateField) inputForm.validate = function(formObject){ startDateObject = formObject.values["startDate"] if (!startDateObject){return false} startDateStatus = (startDateObject >= currentDateObject) if(!startDateStatus){throw "Start date must be midnight today or later"} endDateObject = formObject.values["endDate"] if (!endDateObject){return false} endDateStatus = (endDateObject > startDateObject) if(!endDateStatus){throw "End date must be greater than start date"} return (startDateStatus && endDateStatus) } formPrompt = "Enter the start and end dates:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) startDate = formObject.values['startDate'] endDate = formObject.values['endDate'] console.log("startDate", startDate) console.log("endDate", endDate) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();
Start and End Dates
  

(async () => { try { fmatr = Formatter.Date.withStyle(Formatter.Date.Style.Short) currentDateObject = fmatr.dateFromString('today') inputForm = new Form() startDateField = new Form.Field.Date( "startDate", "Start Date", currentDateObject ) endDateField = new Form.Field.Date( "endDate", "End Date", null ) inputForm.addField(startDateField) inputForm.addField(endDateField) inputForm.validate = function(formObject){ startDateObject = formObject.values["startDate"] if (!startDateObject){return false} startDateStatus = (startDateObject >= currentDateObject) if(!startDateStatus){throw "Start date must be midnight today or later"} endDateObject = formObject.values["endDate"] if (!endDateObject){return false} endDateStatus = (endDateObject > startDateObject) if(!endDateStatus){throw "End date must be greater than start date"} return (startDateStatus && endDateStatus) } formPrompt = "Enter the start and end dates:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) startDate = formObject.values['startDate'] endDate = formObject.values['endDate'] console.log("startDate", startDate) console.log("endDate", endDate) } catch(err){ if(!err.causedByUserCancelling){ new Alert(err.name, err.message).show() } } })();