OmniPlan: Conference Example
The Omni Automation support in OmniPlan provides mechanisms for automating the creation and manipulation of projects and their elements, and for integrating external data into the project.
In the following example project concept, the script in the example OmniPlan action plug-in, alters an open project to become a session outline of a single-track conference, with each conference session as a task, and the speakers as resources assigned to the sessions. In a single-track conference, sessions follow each other rather than occurring concurrently.
In the video (below):
- The script displays a form prompting the user to provide key data: conference title, the conference length (in days), and the starting date of the conference.
- The script then imports and processes data from the user-selected JSON file containing information about the conference sessions.
- Various project objects — tasks/resources — are automatically created and assignments made, based on data read from a JSON file chosen by the user.
In addition to demonstrating the Omni Automation support in OmniPlan, this single example also highlights the use of standard Omni Automation scripting tools, such as action plug-ins, script-generated input forms, and file pickers, each of which is documented fully on this website.
The Imported Data
In this example, information regarding the individual conference sessions is stored in a JSON text file, which is chosen by the user and read and processed by the script. The data for the conference sessions is stored as an array of JSON objects with keys and corresponding values.
If you want to try the example yourself, you can install the action plug-in and DOWNLOAD the JSON data file, or copy the data by pressing the “Copy Data” button below and saving it to disk as a file with the “.json” file extension:
Conference Sessions Data (JSON)
[
{
"ID": "100",
"Speaker": "Boswell Darnce",
"Title": "Omni Automation and You",
"Email": "boswell.darnce@wbytz.com"
},
{
"ID": "101",
"Speaker": "Shelia Darnell",
"Title": "OmniGraffle Scripting Basics",
"Email": "shelia.darnell@wbytz.com"
},
{
"ID": "102",
"Speaker": "Shelia Darnell",
"Title": "OmniGraffle Advanced Scripting",
"Email": "shelia.darnell@wbytz.com"
},
{
"ID": "103",
"Speaker": "Warnell Snead",
"Title": "Automating Project Management",
"Email": "warnell.snead@wbytz.com"
},
{
"ID": "104",
"Speaker": "Tyne Reid",
"Title": "Simplifying Personal Management via Automation",
"Email": "tyne.reid@wbytz.com"
},
{
"ID": "105",
"Speaker": "Greg Sisus",
"Title": "All Scripting Begins with Outlines",
"Email": "greg.sisus@wbytz.com"
},
{
"ID": "200",
"Speaker": "Mary-Jeanne Wallaby",
"Title": "Scripting Between Apps",
"Email": "mary-jeanne.wallaby@wbytz.com"
},
{
"ID": "201",
"Speaker": "Warnell Snead",
"Title": "The Universal “Task|Action” Concept",
"Email": "warnell.snead@wbytz.com"
},
{
"ID": "202",
"Speaker": "Lanette Crosby",
"Title": "PlugIns, Actions, and Libraries",
"Email": "lanette.crosby@wbytz.com"
},
{
"ID": "203",
"Speaker": "Sidney Case",
"Title": "App-to-App Script Design",
"Email": "sidney.case@wbytz.com"
},
{
"ID": "204",
"Speaker": "Brent WIlliams",
"Title": "Integrating with Siri Shortcuts",
"Email": "brent.williams@wbytz.com"
},
{
"ID": "205",
"Speaker": "Mary-Jeanne Wallaby",
"Title": "“Omni-Interactive” HTML",
"Email": "mary-jeanne.wallaby@wbytz.com"
},
{
"ID": "300",
"Speaker": "Boswell Darnce",
"Title": "Integration with FileMaker Pro",
"Email": "boswell.darnce@wbytz.com"
},
{
"ID": "301",
"Speaker": "Link Evans",
"Title": "Populating Project Templates",
"Email": "link.evans@wbytz.com"
},
{
"ID": "302",
"Speaker": "Summer Wilderstone",
"Title": "Formatted Text in Outlines",
"Email": "summer.wilderstone@wbytz.com"
},
{
"ID": "303",
"Speaker": "Sam Roswell",
"Title": "Exporting Data from Documents",
"Email": "sam.roswell@wbytz.com"
},
{
"ID": "304",
"Speaker": "Tyne Reid",
"Title": "Multi-App Workflows",
"Email": "tyne.reid@wbytz.com"
},
{
"ID": "305",
"Speaker": "Molly Sturbil",
"Title": "Engineer Q&A ",
"Email": "molly.sturbil@wbytz.com"
}
]
The OmniPlan Action Plug-In
Below is the Omni Automation code for the action plug-in. It was created using the online OmniPlan Action Template Generator, which you can use to create your own action plug-ins for OmniPlan. Once the example action has been installed, the menu item “Single-Track Conference Template” will be available from the Automation menu in OmniPlan.
To install the plug-in, simply TAP|CLICK the “Download Plug-In” button and follow the instructions.
NOTE: A version of the plug-in that retrieves the conference data from a password-protected JSON file stored on this website, is documented and available for download. (LINK)
Conference Template Plug-In
/*{
"type": "action",
"targets": ["omniplan"],
"author": "Otto Automator",
"identifier": "com.omni-automation.omniplan.conference",
"version": "2.0",
"description": "Adapts the current project to manage a basic 1-track conference.",
"label": "Single-Track Conference Template",
"shortLabel": "Single-Track Conference"
}*/
(() => {
var action = new PlugIn.Action((selection, sender) => {
// CREATE FORM FOR GATHERING USER INPUT
var inputForm = new Form()
// CREATE TEXT FIELD AND DATE FIELD OBJECTS
conferenceTitleField = new Form.Field.String(
"conferenceTitle",
"Conference Title",
null
)
conferenceLengthMenu = new Form.Field.Option(
"conferenceLength",
"Length (in days)",
[1,2,3],
["1", "2", "3"],
1
)
startDateField = new Form.Field.Date(
"startDate",
"Start Date (Monday)",
null
)
// ADD THE FIELDS TO THE FORM
inputForm.addField(conferenceTitleField)
inputForm.addField(conferenceLengthMenu)
inputForm.addField(startDateField)
// PRESENT THE FORM TO THE USER
formPrompt = "Provide the conference title, length in days, and the conference start date (Monday):"
formPromise = inputForm.show(formPrompt,"Continue")
// VALIDATE THE USER INPUT. DAY MUST BE A MONDAY, AND TITLE MUST BE PROVIDED.
inputForm.validate = formObject => {
textValue = formObject.values["conferenceTitle"]
textStatus = (textValue && textValue.length > 0) ? true:false
startDateObject = formObject.values["startDate"]
startDateStatus = (startDateObject && startDateObject.getDay() === 1) ? true:false
validation = (textStatus && startDateStatus) ? true:false
return validation
}
// PROCESSING USING THE DATA EXTRACTED FROM THE FORM
formPromise.then(formObject => {
// TITLE
var conferenceTitle = formObject.values['conferenceTitle']
document.project.title = conferenceTitle
// START DATE. SET TIME OF THE CHOSEN DATE TO MIDNIGHT, AND APPLY DATE AS THE PROJECT’S START DATE.
var startDate = formObject.values['startDate']
startDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate(), 0, 0, 0)
actual.startDate = startDate
// CONFERENCE LENGTH IN DAYS
var conferenceLength = formObject.values['conferenceLength']
conferenceLength = parseInt(conferenceLength)
console.log(conferenceLength)
// CLEAR ALL TASKS AND RESOURCES
actual.rootTask.descendents().forEach(task => {task.remove()})
actual.rootResource.descendents().forEach(rsc => {rsc.remove()})
// SESSION PARAMETERS
sessionTimes = ["8:30","9:45","11:00","13:30","14:45","16:00"]
sessionDuration = Duration.elapsedHourMinSec(1, 0, 0)
// CREATE THE SESSIONS (TASKS) USING THE DAY/SESSION INDEXES TO DERIVE SESSION IDS (101, 203, 305, ETC.)
var dayIndex
for (dayIndex = 1; dayIndex < conferenceLength + 1; dayIndex++) {
var aDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + (dayIndex- 1) , 0, 0, 0)
sessionTimes.forEach((time, sessionIndex) => {
hoursMinutes = time.split(":")
hours = Number(hoursMinutes[0])
minutes = Number(hoursMinutes[1])
taskDateTime = new Date(aDate.getFullYear(), aDate.getMonth(), aDate.getDate(), hours, minutes, 0)
task = actual.rootTask.addSubtask()
// check for 4.x property, otherwise use 3.x property
if (task.manualStartDate !== undefined){
task.manualStartDate = taskDateTime
} else {
task.lockedStartDate = taskDateTime
}
task.duration = sessionDuration
task.title = String(dayIndex) + "0" + String(sessionIndex)
})
}
// CREATE INSTANCE OF FILEPICKER
var picker = new FilePicker()
picker.folders = false
picker.multiple = false
JSONtype = new FileType("public.json")
picker.types = [JSONtype]
// CHECK PLATFORM. IF iOS, SHOW PROMPT AS AN ALERT.
if(app.platformName === "iOS"){
msg = "Tap ”Continue” and in the forthcoming file picker dialog, select the JSON file containing the session data:"
var alert = new Alert("Select File", msg)
alert.addOption("Continue")
alert.show(function(result){
pickerPromise = picker.show()
})
} else if (app.platformName === "macOS"){
picker.message = "Select the JSON file containing the session data:"
pickerPromise = picker.show()
}
// PROCESS THE CHOSEN FILE
pickerPromise.then(urls => {
url = urls[0]
// READ/PROCESS THE CONTENTS OF THE CHOSEN JSON TEXT FILE
url.fetch(data => {
json = JSON.parse(data.toString())
json.forEach(sessionData => {
// EXTRACT DATA FOR EACH SESSION
speakerName = sessionData["Speaker"]
speakerEmail = sessionData["Email"]
sessionID = sessionData["ID"]
sessionTitle = sessionData["Title"]
// PROCESS EACH EXISTING TASK
tasks = actual.rootTask.subtasks
tasks.forEach(task => {
if (task.title === sessionID){
task.title = sessionID + ": " + sessionTitle
rsc = actual.resourceNamed(speakerName)
if(!rsc){
rsc = actual.rootResource.addMember()
rsc.type = ResourceType.staff
rsc.name = speakerName
rsc.costPerUse = Decimal.fromString("500.00")
rsc.email = speakerEmail
}
task.addAssignment(rsc)
}
})
})
// SAVE THE DOCUMENT
document.save()
})
})
})
});
action.validate = (selection, sender) => {
// validation code
// selection options: project, tasks, resources
return true
};
return action;
})();