Plug-In Forms: Date Input (Form.Field.Date)
Omni Automation Plug-Ins designed for applications like OmniFocus and OmniPlan often deal with dates and ranges of time, and so may request the user to identify specific dates and times. The Form.Field.Date class is used to create and display date input fields that enable the user to specify the parameters for the plug-in to process.
Constructor
new Form.Field.Date(key: String, displayName: String or null, value: Date or null, formatter: Formatter.Date or null) → (Form.Field.Date) • Returns a new Date field, optionally with an initial value, and optionally a date formatter. If no formatter is specified, a default one will be created that follows the user’s date formatting preferences to display and determine component ordering when parsing dates. Relative dates like “1d”, “tomorrow”, “now” can also be entered.
Returns a new Date input field, optionally with an initial value. Custom date formatting for the field can be set using the Formatter.Date (Date Formatter) class (LINK).
Date Input Syntax
The value for the date input field can written using any syntax supported by an OmniOutliner date field. For example:
- 2d, –3w, 1h, 1y1m, and so on • Relative dates and times put the date at a certain amount of time from right now. Negative numbers represent times in the past.
- 2 days, –3 weeks, 1 hour, 1 year 1 month, and so on • You can use the full names of units too.
- yesterday, tomorrow, tonight, next thursday, last month, this friday, and so on • You can refer to relative dates using common words. “This”, “next”, and “last” have specific meanings: this friday always means the Friday in this week, next friday always means the Friday in the next week, and last friday always means the Friday in last week, regardless of what day today is. Other units work in the same way.
- september, fri, 2019, and so on • If you enter the name of a specific time period, the date will be at its beginning. So september means September first.
- 5/23/08 10a, 9.30.09 2:00 PM, and so on • You can use the short date format as defined in your Language & Region system preferences.
- 2w sat, 4d @ 5p, mon 6a, aug 6 tue 5p, and so on • Mix the available formats however you like.
- now, 9, 14:00, tom, and so on • Omni’s date parser makes its best guess at things like bare numbers, times, and word fragments.
Basic Date Input
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 remote scripts.Date Input: No Default Date, Default Formatting
dateInput = new Form.Field.Date(
"indicatedDate",
"Start Date",
null,
null
)
Providing a default value for the input:
Date Input: Provided Date (Tommorrow)
tomorrow = new Date(new Date().setHours(24,0,0,0))
dateInput = new Form.Field.Date(
"indicatedDate",
"Start Date",
tomorrow,
null
)
Custom date formatting for the field can be set using the Formatter.Date (Date Formatter) class (LINK).
Date Input: Provided Formatted Date
fmtr= Formatter.Date.withStyle(
Formatter.Date.Style.Long,
Formatter.Date.Style.Short
)
tomorrow = new Date(new Date().setHours(24,0,0,0))
dateInput = new Form.Field.Date(
"indicatedDate",
"Start Date",
tomorrow,
fmtr
)
Date Validation Functions
Forms that contain date input fields usually implement checking functions in the form validation function to ensure that the date chosen by the user meets the parameters required by the plug-in processing.
NOTE: Date comparison functions can be found in the Date class documentation (LINK) and the Plug-In Library documentation (LINK).
Day from Next Week
function dateOccursNextWeek(dateToCheck){
fmatr = Formatter.Date.withStyle(Formatter.Date.Style.Short)
weekStart = fmatr.dateFromString('next week')
dc = new DateComponents()
dc.day = 7
followingWeek = Calendar.current.dateByAddingDateComponents(weekStart, dc)
return (dateToCheck >= weekStart && dateToCheck < followingWeek)
}
inputForm = new Form()
dateInput = new Form.Field.Date(
"indicatedDate",
"Date",
null,
null
)
formPrompt = "Enter a date from next week:"
buttonTitle = "Continue"
inputForm.addField(dateInput)
inputForm.show(formPrompt, buttonTitle)
inputForm.validate = function(formObject){
dateToCheck = formObject.values["indicatedDate"]
if (!dateToCheck){return false}
if (!dateOccursNextWeek(dateToCheck)){
throw "Date must be in the next week"
}
return true
}
And here’s the same script placed within in a plug-in wrapper:
Plug-In: Enter Date from Next Week
/*{
"type": "action",
"targets": ["omnifocus","omnigraffle","omniplan","omnioutliner"],
"author": "Otto Automator",
"identifier": "com.omni-automation.all.chosen-day-next-week",
"version": "1.1",
"description": "Prompts the user to Select a Date that occurs in the following week.",
"label": "Enter Day Next Week",
"shortLabel": "Enter Day Next Week",
"paletteLabel": "Enter Day Next Week",
"image": "calendar"
}*/
(() => {
function dateOccursNextWeek(dateToCheck){
fmatr = Formatter.Date.withStyle(Formatter.Date.Style.Short)
weekStart = fmatr.dateFromString('next week')
dc = new DateComponents()
dc.day = 7
followingWeek = Calendar.current.dateByAddingDateComponents(weekStart, dc)
return (dateToCheck >= weekStart && dateToCheck < followingWeek)
}
const action = new PlugIn.Action(async function(selection, sender){
inputForm = new Form()
dateInput = new Form.Field.Date(
"indicatedDate",
"Date",
null
)
inputForm.addField(dateInput)
inputForm.validate = function(formObject){
var dateToCheck = formObject.values["indicatedDate"]
if (!dateToCheck){return false}
if (!dateOccursNextWeek(dateToCheck)){
throw "Date must be in the next week"
}
return true
}
formPrompt = "Enter a date from next week:"
buttonTitle = "Continue"
formObject = await inputForm.show(formPrompt, buttonTitle)
console.log(formObject.values["indicatedDate"])
});
action.validate = function(selection, sender){
return true
};
return action;
})();
Start and End Dates
Here’s an example presenting two date input fields for entering the start and end dates for an event. The validation function ensures that the entered end date is greater than the start date.
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()
}
}
})();
Choose Day from Next Week (menu)
As an alternative to using date input fields, forms can present dates to users in menus, as in this example for selecting a day in the upcoming week:
Choose Day from Next Week
(async () => {
try {
fmtr = Formatter.Date.withStyle(Formatter.Date.Style.Full)
weekStart = fmtr.dateFromString("next week")
dc = new DateComponents()
menuItems = new Array()
menuItems.push(fmtr.stringFromDate(weekStart))
var i;
for (i = 1; i < 7; i++) {
dc.day = i
var date = Calendar.current.dateByAddingDateComponents(weekStart, dc)
menuItems.push(fmtr.stringFromDate(date))
}
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 a day from next week:"
buttonTitle = "Continue"
formObject = await inputForm.show(formPrompt,buttonTitle)
menuIndex = formObject.values['menuElement']
chosenDateString = menuItems[menuIndex]
chosenDate = fmtr.dateFromString(chosenDateString)
console.log(chosenDate)
}
catch(err){
if(!err.causedByUserCancelling){
new Alert(err.name, err.message).show()
}
}
})();
Select a Date (menus)
In this second example of date input alternatives, the user can choose any date by selecting from a series of of dynamic follow-on menus:
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()
}
}
})();