Projects

A project is a task made up of multiple items, referred to in OmniFocus as actions. Projects are typically more complex than individual action items, and can include several related actions. The Projects perspective displays all of your projects in a list, which can be grouped into folders to create hierarchy.

Project Properties

As with most scriptable objects, instances of the Project class have properties that define their use and purpose. Here are the properties of a project:

New Project

Instances of the Project class are created using the standard JavaScript new item constructor, with the name of the new project as the parameter passed into the function:

project = new Project("My Top-Level Project") new Task("First Project Task", project) new Task("Second Project Task", project)
omnifocus://localhost/omnijs-run?script=try%7Bproject%20%3D%20new%20Project%28%22My%20Top%2DLevel%20Project%22%29%0Anew%20Task%28%22First%20Project%20Task%22%2C%20project%29%0Anew%20Task%28%22Second%20Project%20Task%22%2C%20project%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
top-level-project

And here is an example of creating a folder containing multiple new projects:

masterFolder = new Folder("Master Project") new Project("Preparation", masterFolder) new Project("Build", masterFolder) new Project("Finish",masterFolder)
omnifocus://localhost/omnijs-run?script=try%7BmasterFolder%20%3D%20new%20Folder%28%22Master%20Project%22%29%0Anew%20Project%28%22Preparation%22%2C%20masterFolder%29%0Anew%20Project%28%22Build%22%2C%20masterFolder%29%0Anew%20Project%28%22Finish%22%2CmasterFolder%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
new-folder-with-projects

Project Types

Projects are distinguished by their type, which varies based on how actions inside the project must be completed. Project type also affects how actions within the project show up according to the perspective’s View options.

By default, new projects have parallel actions.

To create a new project containing sequential actions, set the sequential property of the project’s root task to true:

project = new Project("Sequential Project") project.task.sequential = true
omnifocus://localhost/omnijs-run?script=try%7Bproject%20%3D%20new%20Project%28%22Sequential%20Project%22%29%0Aproject%2Etask%2Esequential%20%3D%20true%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

To create a new project with single actions (list), set the value of the project’s containsSingletonActions property to true:

project = new Project("Sequential Project") project.containsSingletonActions = true
omnifocus://localhost/omnijs-run?script=try%7Bproject%20%3D%20new%20Project%28%22Sequential%20Project%22%29%0Aproject%2Etask%2Esequential%20%3D%20true%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

Identifying Existing Project

Projects can be identified by examining the value of their properties. A common example being the identification of projects by their name or title. The projectNamed(…) function of the Database class provides a mechanism for locating the first occurence of a project with a specified name, located at the top-level of the database or host container. If no matching projects are found, a value of null is returned.

projectNamed("My Top-Level Project") //--> [object Project: My Top-Level Project] projectNamed("Build") //--> null folderNamed("Master Project").projectNamed("Finish") //--> [object Project: Finish]

 01  Searching the top-level of the database library for a project specifed by name.

 03  The search finds no matching projects and the function returns a value of: null

 05  Using the folderNamed(…) function of the Database class to locate a project within a top-level folder.

As shown in the script above, projects can be stored within folder hierarchies. The projectNamed(…) method searches the top-level library directory. To search througout the library folder hierarchies, use the apply(…) function of the Library, as in the following example script that retrieves the titles of all projects in the folder hierarchy:

projectNames = new Array() library.apply(function(item){ if (item instanceof Project){projectNames.push(item)} }) console.log(projectNames)
omnifocus://localhost/omnijs-run?script=try%7BprojectNames%20%3D%20new%20Array%28%29%0Alibrary%2Eapply%28function%28item%29%7B%0A%09if%20%28item%20instanceof%20Project%29%7BprojectNames%2Epush%28item%29%7D%0A%7D%29%0Aconsole%2Elog%28projectNames%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

Project.Status

When planning and subsequently reviewing a project, it can be useful to give it a status to indicate whether work is progressing or plans have changed.

TIP: Dropped and completed items accumulate in your database over time. If you find that things are becoming unwieldy, archiving can help lighten the load.

With Omni Automation you can change the status of a project by changing the value of the project’s status property to one of the options of the Project.Status class:

project = projectNamed("PROJECT A") project.status = Project.Status.OnHold

Scanning the top-level projects:

projects.forEach((project)=>{ if(project.status == Project.Status.Active){ project.status = Project.Status.OnHold } })
omnifocus://localhost/omnijs-run?script=try%7Bprojects%2EforEach%28%28project%29%3D%3E%7B%0A%09if%28project%2Estatus%20%3D%3D%20Project%2EStatus%2EActive%29%7B%0A%09%09project%2Estatus%20%3D%20Project%2EStatus%2EOnHold%0A%09%7D%0A%7D%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

Scanning the entire folder and project hierarchy:

onHoldProjects = new Array() library.apply(function(item){ if (item instanceof Project && item.status == Project.Status.OnHold){ onHoldProjects.push(item) } }) if (onHoldProjects){ document.windows[0].perspective = Perspective.BuiltIn.Projects document.windows[0].focus = onHoldProjects }
omnifocus://localhost/omnijs-run?script=try%7BonHoldProjects%20%3D%20new%20Array%28%29%0Alibrary%2Eapply%28function%28item%29%7B%0A%09if%20%28item%20instanceof%20Project%20%26%26%20item%2Estatus%20%3D%3D%20Project%2EStatus%2EOnHold%29%7B%0A%09%09onHoldProjects%2Epush%28item%29%0A%09%7D%0A%7D%29%0Aif%20%28onHoldProjects%29%7B%0A%09document%2Ewindows%5B0%5D%2Eperspective%20%3D%20Perspective%2EBuiltIn%2EProjects%20%0A%09document%2Ewindows%5B0%5D%2Efocus%20%3D%20onHoldProjects%0A%7D%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

Revealing Projects

Projects are elements of the database, and like other elements possesses a unique identifer string to represent the project internally within the application. You can use the object ID and the built-in URL support of OmniFocus to reveal any project(s).

var targetProjectName = "My Project" var targetProject = null library.apply(function(item){ // stop the iteration process when a match is found if (item instanceof Project && item.name == targetProjectName){ targetProject = item return ApplyResult.Stop } }) if(targetProject){ projID = targetProject.id.primaryKey urlStr = "omnifocus:///task/" + projID URL.fromString(urlStr).call(reply => {}) }

Here’s a variation of the previous script that reveals all projects with a specified name:

var targetProjectName = "My Project" var targetProjects = new Array() library.apply(function(item){ if (item instanceof Project && item.name == targetProjectName){ targetProjects.push(item) } }) if(targetProjects){ IDs = targetProjects.map(project => {return project.id.primaryKey}) urlStr = "omnifocus:///task/" + IDs.join(",") URL.fromString(urlStr).call(reply => {}) }

See the “Finding Items” topic for more examples of using the apply(…) function.

Project Tasks

Every instance of the Project class has a root task, represented by the value of the project’s task property.

By default, new projects have parallel actions. To create a project with sequential tasks, set the value of the root task’s sequential property to true:

project = new Project("My Project") project.task.sequential = true new Task("Task One", project) new Task("Task Two", project) new Task("Task Three", project)

To create a project with single actions, set the value of the projects’s containsSingletonActions property to true:

project = new Project("My Project") project.containsSingletonActions = true new Task("Task One", project) new Task("Task Two", project) new Task("Task Three", project)

You can use the hasChildren and children properties of the root task to access an array of all of the tasks contained in the project, as in this script that gather reference4s to all tasks in all projects:

var masterTaskArray = new Array() library.apply(function(item){ if (item instanceof Project){ if (item.task.hasChildren){ masterTaskArray.push(item.task.children) } } }) console.log(masterTaskArray)

Add an iteration of the Inbox, and here’s a script for getting references to ALL TASKS in the database:

var masterTaskArray = new Array() library.apply(function(item){ if (item instanceof Project){ if (item.task.hasChildren){ masterTaskArray.push(item.task.children) } } }) inbox.apply((task)=>{ masterTaskArray.push(task) }) console.log(masterTaskArray)

Project Tags

In OmniFocus, a tag represents an association that a task has to the world around it. A tag could be a person, place, or thing most relevant to completion of a project, or it could represent a mindset most applicable to execution of an action item.

An item can have as many tags as you find useful, and there is no specific purpose assigned to them; use tags to assign priority, group items by energy level or required equipment, or don’t use them at all.

The Tags perspective displays a list of your tags in the sidebar, and a list of all your actions grouped by the tags they belong to in the outline.

The following script examples demonstrate the use of tags with projects.

Retrieving Tags Assigned to a Project

To list the tags assigned to a project, the tags property of the project’s root task is used:

project = projectNamed("PROJECT A") tagNames = project.task.tags.map((tag)=>{return tag.name}) console.log(tagNames)

 01  The target top-level project is identified using the projectNamed(…) method.

 02  The value of the tags property of the project‘s root task returns an array of object references to the tags assigned to the project. The standard JavaScript map(…) function is used to retreive an array of the titles of the tag objects.

 03  The resulting array of tag names is written to the scripting console.

Adding Tags

Existing database tags are assigned to a project by calling the addTag(…) function, passing in a reference to the tag object as the function parameter.

project = projectNamed("PROJECT A") if(!tagNamed("Critical")){new Tag("Critical")} project.task.addTag(tagNamed("Critical"))

 01  A top-level project is identified by name using the projectNamed(…) function, which returns an object reference to the first matching project, or a value of null if no match is found.

 02  If a specific tag does not exist, one is created.

 03  A tag is assigned to the project by calling the addTag(…) function on the project’s root task and passing in a tag reference to the function.

Removing Tags

Tags can be “unassigned” from projects using the removeTag(…) function, which takes a tag reference as its passed-in parameter. NOTE: the unassigned tag is simply unassigned from the project, it is not deleted from the database.

The following script example, clears all tags assigned to a specified project:

project = projectNamed("PROJECT A") project.task.tags.forEach((tag)=>{project.task.removeTag(tag)})

 01  The target top-level project is identified using the projectNamed(…) function, and the resulting object reference is stored in the variable: project

 02  The value of the tags property of the project‘s root task returns an array of object references to the tags assigned to the project. The standard JavaScript forEach(…) function is used to iterate and unassign the assigned tag objects.

Flagging Projects

Projects can be “flagged” by setting the value of project’s root task flagged property to true:

project = projectNamed("My Top-Level Project") if(project){project.task.flagged = true}
/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.move-selected-projects-into-folder", "version": "1.0", "description": "Move the selected projects into a new top-level folder.", "label": "Move Selected Projects into New Folder", "shortLabel": "Move Projects" }*/ var _ = function(){ var action = new PlugIn.Action(function(selection, sender){ // selection options: tasks, projects, folders, tags // CONSTRUCT THE FORM var inputForm = new Form() // CREATE FORM ELEMENTS: TEXT INPUT textField = new Form.Field.String("folderName", null, null) // ADD THE ELEMENTS TO THE FORM inputForm.addField(textField) // DIALOG PROMPT AND OK BUTTON TITLE let formPrompt = "Enter the name for the new top-level folder:" let buttonTitle = "Continue" // DISPLAY THE FORM DIALOG formPromise = inputForm.show(formPrompt, buttonTitle) // VALIDATE FORM CONTENT inputForm.validate = function(formObject){ // EXTRACT VALUES FROM THE FORM’S VALUES OBJECT textValue = formObject.values['folderName'] return ((textValue) ? true:false) } // PERFORM PROCESSES USING FORM DATA formPromise.then(function(formObject){ textValue = formObject.values['folderName'] moveSections(selection.projects, new Folder(textValue)) }) // PROCESS FORM CANCELLATION formPromise.catch(function(error){ console.log("form cancelled", error) }) }); action.validate = function(selection, sender){ // selection options: tasks, projects, folders, tags return (selection.projects.length > 0) }; return action; }(); _;

Packing List Plug-In

To demonstrate how the project-related properties and functions can be used to create automation tools, here are some action plug-ins for you to examine and install.

The first example action plug-in incorporates the use of Action Forms to present user input controls for creating and displaying a packing list based upon the title and date parameters indicated by the user:

packing-list-input packing-list

Note that the script includes code that determines the appropriate number of certain clothing items to pack, based on the length (in days) of the planned trip.

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.packing-list-for-date", "version": "1.5", "description": "Creates a project of single-actions used as a packing list.", "label": "Packing List for Trip", "shortLabel": "Packing List" }*/ var _ = function(){ var action = new PlugIn.Action(function(selection, sender) { var inputForm = new Form() taskNameField = new Form.Field.String( "tripTitle", "Title", "My Trip" ) departureDateField = new Form.Field.Date( "departureDate", "Leave", null ) returnDateField = new Form.Field.Date( "returnDate", "Return", null ) inputForm.addField(taskNameField) inputForm.addField(departureDateField) inputForm.addField(returnDateField) formPromise = inputForm.show("Enter the trip title and travel dates:","Continue") inputForm.validate = function(formObject){ currentDateTime = new Date() departureDateObject = formObject.values["departureDate"] departureDateStatus = (departureDateObject && departureDateObject > currentDateTime) ? true:false returnDateObject = formObject.values["returnDate"] returnDateStatus = (returnDateObject && returnDateObject > departureDateObject) ? true:false textValue = formObject.values["tripTitle"] textStatus = (textValue && textValue.length > 0) ? true:false validation = (textStatus && departureDateStatus && returnDateStatus) ? true:false return validation } formPromise.then(function(formObject){ tripTitle = formObject.values['tripTitle'] var StartDate = formObject.values['departureDate'] var EndDate = formObject.values['returnDate'] var tripDuration = (Date.UTC(EndDate.getFullYear(), EndDate.getMonth(), EndDate.getDate()) - Date.UTC(StartDate.getFullYear(), StartDate.getMonth(), StartDate.getDate())) / 86400000; projectName = "Packing List for " + tripTitle var project = projectNamed(projectName) || new Project(projectName) project.status = Project.Status.Active project.containsSingletonActions = true packingItems = ["Meds","Toothbrush","Toothpaste","Floss","Razor","Shaving Gel","Hair Brush","Deodorant","Underwear","Socks","Shirts","Pants","Belt"] packingItems1PerDay = ["Underwear","Socks","Shirts"] packingItems1Per2Day = ["Pants"] packingItems.forEach((packingItem)=>{ var amount = (packingItems1PerDay.includes(packingItem)) ? Number(tripDuration) : 1 if (packingItems1PerDay.includes(packingItem)){ amount = Number(tripDuration) } else if (packingItems1Per2Day.includes(packingItem)){ amount = Number(tripDuration) / 2 amount = (amount < 1) ? 1 : Math.ceil(amount) } else { amount = 1 } suffix = (amount > 1) ? ` (${amount})` : "" task = project.taskNamed(packingItem + suffix) || new Task(packingItem + suffix, project) task.dueDate = StartDate //task.note = "" }) }) formPromise.catch(function(err){ console.log("form cancelled", err.message) }) }); action.validate = function(selection, sender) { // validation code // selection options: tasks, projects, folders, tags return true }; return action; }(); _;

Moving Projects Plug-In

Here’s an action plug-in that will move the selected projects into a folder created using the user-provided name. There is an option (checkbox) for indicating that the folder name should be unique and not in current use by any other folder in the database. NOTE: the folder title comparisons are case-insensitive.

move-projects-into-new-folder
/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.move-selected-projects-into-folder", "version": "1.6", "description": "Move the selected projects into a new top-level folder.", "label": "Move Selected Projects into New Folder", "shortLabel": "Move Projects" }*/ var _ = function(){ var action = new PlugIn.Action(function(selection, sender){ // selection options: tasks, projects, folders, tags // CONSTRUCT THE FORM var inputForm = new Form() // CREATE FORM ELEMENTS: TEXT INPUT, CHECKBOX textField = new Form.Field.String("folderName", null, null) checkSwitchField = new Form.Field.Checkbox( "ensureUniqueName", "Ensure folder name is unique", false ) // ADD THE ELEMENTS TO THE FORM inputForm.addField(textField) inputForm.addField(checkSwitchField) // GET THE NAMES OF ALL FOLDERS var folderNames = new Array() library.apply(item => { if (item instanceof Folder){folderNames.push(item.name)} }) // DIALOG PROMPT AND OK BUTTON TITLE let formPrompt = "Enter name for new top-level folder:" let buttonTitle = "Continue" // DISPLAY THE FORM DIALOG formPromise = inputForm.show(formPrompt, buttonTitle) // VALIDATE FORM CONTENT inputForm.validate = function(formObject){ // EXTRACT VALUES FROM THE FORM’S VALUES OBJECT var textValue = formObject.values['folderName'] var checkboxValue = formObject.values['ensureUniqueName'] if (!textValue){return false} if (!checkboxValue){return true} if (checkboxValue){ folderNames.forEach(function(name){ if (name.toLowerCase() === textValue.toLowerCase()){ throw "ERROR: That folder name is in use." } }) return true } } // PERFORM PROCESSES USING FORM DATA formPromise.then(function(formObject){ textValue = formObject.values['folderName'] folder = new Folder(textValue) moveSections(selection.projects, folder) // SHOW THE FOLDER fldID = folder.id.primaryKey urlStr = "omnifocus:///folder/" + fldID URL.fromString(urlStr).call(reply => {}) }) // PROCESS FORM CANCELLATION formPromise.catch(function(error){ console.log("form cancelled", error) }) }); action.validate = function(selection, sender){ // selection options: tasks, projects, folders, tags return (selection.projects.length > 0) }; return action; }(); _;
UNDER CONSTRUCTION

This webpage is in the process of being developed. Any content may change and may not be accurate or complete at this time.

DISCLAIMER