Finding Items

Many scripts target either the selected items or one or more items whose properties meet specific criteria. The Omni Automation support in OmniFocus provides a variety of tools for identifying objects to process. This section covers the use of such scripting tools.

Built-In Search Functions

The Database class provides three generalized search functions for locating folders, projects, and tags that “smart match” the indicated string.

Here’s an example of calling the search function for tags whose title matches the provided string:

Matched Searches

tagsMatching("Auto-") //--> [[object Tag: Auto-Open], [object Tag: Auto-Close], [object Tag: Auto-Update]]
Focus Found Items

var win = document.windows[0] win.perspective = Perspective.BuiltIn.Projects win.focus = projectsMatching("Reno")

Find Items by ID

Every database object, such as a folder, project, task, or tag, has a unique identifier string that is associated with that object. An object’s identifier can be retrieved using the id property of the DatabaseObject class and the primaryKey property of the ObjectIdentifier class:

Retrieve ID of Named Folder

folderNamed("Home Renovation").id.primaryKey //--> "a0UDF6AROY8"

The Folder, Project, Task, and Tag classes include versions of the byIdentifier() class function for locating those object types through their identifier string:

The first example script uses the built-in URL support in OmniFocus to reveal the identified folder:

Reveal Folder by ID

var fldrID = "a0UDF6AROY8" var fldr = Folder.byIdentifier(fldrID) if(fldr){ var urlStr = "omnifocus:///folder/" + fldrID URL.fromString(urlStr).open() }

The second example script uses the selectObjects() method of the Window class to reveal the identified folder:

Reveal Folder by ID

var fldrID = "a0UDF6AROY8" var fldr = Folder.byIdentifier(fldrID) if(fldr){ var win = document.windows[0] win.perspective = Perspective.BuiltIn.Projects win.selectObjects([fldr]) }

Find Items by Name

If an object’s ID is not available to the script, then the object’s name can be used to locate the item, assuming the object name has some level of uniqueness.

To search the entire database for an object by name, use the byName() method with the “flattened” properties of the Database class:

NOTE: the byName() function returns only the first matching item (if any)

Reveal Named Project

var project = flattenedProjects.byName("Garage Roof") if(project){ var id = project.id.primaryKey URL.fromString("omnifocus:///task/" + id).open() }
Reveal and Add Tag to Task

var tag = flattenedTags.byName("Travel") var task = flattenedTasks.byName("Plan Vacation") if(tag && task){ task.addTag(tag) var id = task.id.primaryKey URL.fromString("omnifocus:///task/" + id).open() }

Here's a script that will return an object reference to a tag with a specific name, creating the tag if it does not exist:

Reference Named Tag

var tag = flattenedTags.byName("Conifer") || new Tag("Conifer")

To limit the “search” to the projects or folders within a specific folder, use the “flattened” properties of the Folder class:

Reveal Project within Specified Folder

var fldr = flattenedFolders.byName("Smith Q1") if(fldr){ var project = fldr.flattenedProjects.byName("Summary") var id = project.id.primaryKey URL.fromString("omnifocus:///task/" + id).open() }

To limit the “search” to the tasks within a specific project, use the flattenedTasks property of the Project class:

Flag a Task in a Specific Project

var project = flattenedProjects.byName("Personel Roster") if(project){ var task = project.flattenedTasks.byName("Peer Review") if(task){task.flagged = true} }

Find Items Using Time-Based Parameters

To identify items whose properties have values within a time range, use the “flattened” properties of the Database class with instances of the Formatter.Date class:

Tasks with Tomorrow Deferment

var fmatr = Formatter.Date.withStyle(Formatter.Date.Style.Short) var rangeStart = fmatr.dateFromString('1 day') var rangeEnd = fmatr.dateFromString('2 days') var matchedTasks = flattenedTasks.filter(task => { return task.deferDate >= rangeStart && task.deferDate < rangeEnd })
Tasks Due Next Week (Monday -> Friday)

var fmatr = Formatter.Date.withStyle(Formatter.Date.Style.Short) var rangeStart = fmatr.dateFromString('Monday') var rangeEnd = fmatr.dateFromString('Saturday') var matchedTasks = flattenedTasks.filter(task => { var taskDueDate = task.effectiveDueDate return taskDueDate >= rangeStart && taskDueDate < rangeEnd })

The value for the date formatter can written using any syntax supported by an OmniOutliner date field. For example:

Finding Items Based Upon Property Values

Sometimes scripts are designed to process an array of references to objects whose properties have specified values, such as tasks whose notes contain a specific phrase. In such cases, iterating the “flattened” properties of the Database class using the JavaScript filter() function may provide your scripts will the object arrays necessary to perform iterative processing of the located items:

Find/Process Tasks whose Notes Contain…

flattenedTasks.filter(task => { return task.note.includes("Identifying Phrase") }).forEach(task => { // process each “found” task })
Focus Sequential Projects whose Status is On Hold

var matchedProjects = flattenedProjects.filter(project => { return project.sequential === true && project.status === Project.Status.OnHold }) if(matchedProjects.length > 0 && app.platformName === "macOS"){ var win = document.windows[0] win.perspective = Perspective.BuiltIn.Projects win.focus = matchedProjects }

NOTE: In the above script, the focus property of the Window class is only supported on macOS, getting or setting this on iOS results in an error.

Here's a script that identifies available tasks that are due today:

Available Tasks Due Today

// AVAILABLE TASKS DUE TODAY fmatr = Formatter.Date.withStyle(Formatter.Date.Style.Short) rangeStart = fmatr.dateFromString('today') rangeEnd = fmatr.dateFromString('tomorrow') tasksToProcess = flattenedTasks.filter(task => { return ( task.effectiveDueDate > rangeStart && task.effectiveDueDate < rangeEnd && task.taskStatus === Task.Status.DueSoon ) }) if(tasksToProcess.length === 0){ console.error("NO TASKS DUE TODAY") } else { tasksToProcess }

Finding|Processing Items with the apply(…) Function

(NOTE: As shown previously in this topic, the implementation of the “flattened” properties for the OmniFocus container classes has made the iteration of object hierarchies a simpler process. However the apply() function remains a viable method for parsing object trees and does offer the unique ability to stop iterative processing when specific conditions have been met. Continuing the section…)

It is common for scripts to perform their automation tasks by first locating a set of objects that meet a specified set of criteria, and then process the found items. This section contains examples of using the apply(…) function to process object hierarchies to locate and process items in the OmniFocus database.

The apply(…) function can be called the database properties: inbox, library, and tags and on instances of the Folder, Tag, and Task classes.

By default, the apply(…) method when called on a database property with hierarchical content, will iterate every item of the heirarchy, processing each item with the processing code you provide. Optionally, you can use the properties of the ApplyResult class to indicate if certain types of iterated items should be ignored, or if the iteration process should stop.

First Flagged Inbox Task with Children

var targetTask = null inbox.apply(task => { // stop the iteration process when a match is found if (task.children.length > 0 && flagged === true){ targetTask = task return ApplyResult.Stop } }) if(targetTask){console.log(targetTask)}

Iterating the tasks and sub-tasks of a project:

Iterating Tasks of a Project

project = document.windows[0].selection.projects[0] project.task.apply(tsk => { console.log(tsk) })

Search Library Content for Task by Name

Search all projects for a task by name.

Find Task by Name in Library

document.windows[0].persperctive = Perspective.BuiltIn.Projects var targetTask = null var targetTaskName = "Title of Task to Find" tree = document.windows[0].content tree.rootNode.apply(node => { var item = node.object if(item instanceof Task && item.name === targetTaskName){ targetTask = item return ApplyResult.Stop } }) if(targetTask){console.log(targetTask)}

Tag Tasks whose Notes Contain

This action will add the provided tag to every task whose note field contains the provided string.

Action Interface
Tag Tasks whose Notes Contain

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.add-tag-if-note-contains", "version": "1.3", "description": "This action will add the provided tag to every task whose note field contains the provided string.", "label": "Add Tag if Note Contains", "shortLabel": "Add Tag if Note Contains", "paletteLabel": "Tag if Note", "image":"tag.fill" }*/ (() => { const action = new PlugIn.Action(async function(selection, sender){ textInputField01 = new Form.Field.String( "tagTitleInput", "Tag Title", null ) textInputField02 = new Form.Field.String( "searchStringInput", "Search notes for", null ) checkSwitchField = new Form.Field.Checkbox( "ignoreCaseInSearch", "Case-insensitive Search", true ) inputForm = new Form() inputForm.addField(textInputField01) inputForm.addField(textInputField02) inputForm.addField(checkSwitchField) inputForm.validate = function(formObject){ inputText01 = formObject.values['tagTitleInput'] inputText01Status = (!inputText01)?false:true inputText02 = formObject.values['searchStringInput'] inputText02Status = (!inputText02)?false:true // ALL CONDITIONS MUST BE TRUE TO VALIDATE validation = (inputText01Status && inputText02Status) ? true:false return validation } formPrompt = "Enter tag and search string:" buttonTitle = "Continue" formObject = await inputForm.show(formPrompt, buttonTitle) targetTagName = formObject.values['tagTitleInput'] targetString = formObject.values['searchStringInput'] ignoreCaseInSearch = formObject.values['ignoreCaseInSearch'] console.log('tagTitleInput: ',targetTagName) console.log('searchStringInput: ',targetString) console.log('ignoreCaseInSearch: ',ignoreCaseInSearch) var targetTag = flattenedTags.byName(targetTagName) if(!targetTag){targetTag = new Tag(targetTagName)} if (ignoreCaseInSearch){ targetString = targetString.toUpperCase() var filteredTasks = flattenedTasks.filter(item => { return item.note.toUpperCase().includes(targetString) }) } else { var filteredTasks = flattenedTasks.filter(item => { return item.note.includes(targetString) }) } if (filteredTasks.length === 0){ let alertTitle = "NO MATCHES" let alertMessage = `“${targetString}” was not found in the notes of any tasks.` new Alert(alertTitle, alertMessage).show() } else { filteredTasks.forEach(task => { task.addTag(targetTag) }) URL.fromString("omnifocus:///tag/" + targetTag.id.primaryKey).open() } }); action.validate = function(selection, sender){ return true }; return action; })();