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):

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; })();