Built-In Search Functions

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

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

Finding|Processing Items with the Apply(…) Function

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 page 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.

folderNamed("Fall Festival").apply(item => { if (item instanceof Folder){console.log("F • " + item.name)} if (item instanceof Project){console.log("P • " + item.name)} })
var folderTasks = new Array() folderNamed("Folder A").projects.forEach(project => { if(project.task.hasChildren){ project.task.flattenedTasks.forEach(task => { folderTasks.push(task) }) } }) folderTasks.forEach(task => { if ( // comparison code goes here task.name.includes("-1") && task.completed === false ){ // process task here task.flagged = true } })

NOTE: More examples of finding items using the “flattened” properties of the Database class are included at the bottom of this page.

Inbox Tasks

To iterate all tasks in the Inbox hierarchy — even those tasks within other tasks — use the apply(…) function:

inbox.apply((task)=>{ if (task.taskStatus === Task.Status.Available){ // processing statements go here } })
taskNames = new Array() inbox.apply(task => taskNames.push(task.name)) console.log(taskNames)
omnifocus://localhost/omnijs-run?script=try%7BtaskNames%20%3D%20new%20Array%28%29%0Ainbox%2Eapply%28%28task%29%3D%3E%7B%0A%09taskNames%2Epush%28task%2Ename%29%0A%7D%29%0Aconsole%2Elog%28taskNames%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

All Inbox tasks matching a specified name:

var targetTaskName = "SUB-TASK" var targetTasks = new Array() inbox.apply(function(task){ if (task.name == targetTaskName ){ targetTasks.push(task) } }) console.log(targetTasks)

ApplyResult Class

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.

Here’s an example script that returns the ApplyResult.Stop property to identify the first instance of an Inbox task specified by name:

var targetTaskName = "My Secondary Task" var targetTask = null inbox.apply(function(task){ // stop the iteration process when a match is found if (task.name == targetTaskName ){ targetTask = task return ApplyResult.Stop } }) if(targetTask){console.log(targetTask)}

Here’s a variation of the previous script, for locating a task in the Inbox hierarchy matched by identifier:

var targetTaskID = "jm86R3Q3F3I" var targetTask = null inbox.apply(function(task){ // stop the iteration process when a match is found if (task.id.primaryKey == targetTaskID){ targetTask = task return ApplyResult.Stop } }) if(targetTask){console.log(targetTask)}

To locate a specific class instance in the entire database, iterate the value of one of the “flattened” database properties. For example, locate a task by its unique identifier:

var allTasks = flattenedTasks var targetID = "p4IB9vvQDcj" var targetTask = null for (i = 0; i < allTasks.length; i++){ task = allTasks[i] if (task.id.primaryKey === targetID){ targetTask = task break } } if(targetTask){console.log(targetTask)}
var targetTasks = new Array() inbox.apply(function(task){ targetTasks.push(task) return ApplyResult.SkipChildren }) console.log(targetTasks)
var targetTasks = new Array() inbox.apply(function(task){ targetTasks.push(task) return ApplyResult.SkipPeers }) console.log(targetTasks)

Using the SkipChildren property to iterate just the top-level Inbox items:

var targetTasks = new Array() inbox.apply(function(task){ targetTasks.push(task) return ApplyResult.SkipChildren }) console.log(targetTasks)

Folders and Projects

Folders and Projects are elements found in the library extension of the Database class. When the apply() function is used to iterate the entire contents of the library tree, use the instanceof specifier to distinguish between the two.

library.apply(function(item){ if (item instanceof Project){ console.log(item.name) } })
library.apply(function(item){ if (item instanceof Folder){ console.log(item.name) } })

A script example using the ApplyResult.Stop property to get the tasks of a project specified by name:

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){ tasks = targetProject.task.children }
var targetTaskID = "jm86R3Q3F3I" var targetTask = null inbox.apply(function(task){ // stop the iteration process when a match is found if (task.id.primaryKey == targetTaskID){ targetTask = task return ApplyResult.Stop } }) if(targetTask){console.log(targetTask)}

Locating projects within the entire hierarchy of a specified folder:

var projectList = new Array() folderNamed("Fall Festival").apply(item => { if (item instanceof Project){projectList.push(item)} })

Tags

The apply(…) function is also useful for iterating the entire heirarchy of tags, including tag groups.

targetTagName = "Michigan" var targetTag = null tags.apply(function(tag){ if(tag.name == targetTagName){ targetTag = tag return ApplyResult.Stop } }) tag = targetTag || new Tag(targetTagName)

Project Tasks

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)

The “flattened” Database Properties

Using the “flattened” properties of the Database class (flattenedFolders, flattenedProjects, flattenedTasks, flattenedTags) scripts can iterate all instances of the specified class in the entire database. For example, the following script iterates every task in the database:

Here's an example of using the “flattened” properties with the JavaScript filter() method to generate an array of references to all tasks whose status is available:

matches = flattenedTasks.filter(function(item){ return item.taskStatus === Task.Status.Available })

And a variation of the previous script that filters all tasks for those that are available, and then processes each of the matched tasks:

flattenedTasks.filter(function(item){ return item.taskStatus === Task.Status.Available }).forEach(task => { // processing statements })

And here is a script example that filters and processes all tasks whose name begins with a specific word. (NOTE: The optional use of the toUpperCase() function is ensure the string comparision is case-insensitive.)

targetString = "three" matches = flattenedTasks.filter(function(item){ return item.name.toUpperCase().startsWith(targetString.toUpperCase()) }).forEach(task => { // processing statements })

Tag Tasks whose Notes Contain

Here’s an Omni Automation action that will tag tasks whose notes field contains the specified string:

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.add-tag-if-note-contains", "version": "1.0", "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" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code // selection options: tasks, projects, folders, tags var textInputField01 = new Form.Field.String( "tagTitleInput", "Tag title", null ) var textInputField02 = new Form.Field.String( "searchStringInput", "Search for", null ) var inputForm = new Form() inputForm.addField(textInputField01) inputForm.addField(textInputField02) var formPrompt = "Enter tag title and search string:" var buttonTitle = "Continue" formPromise = inputForm.show(formPrompt,buttonTitle) inputForm.validate = function(formObject){ var inputText01 = formObject.values['tagTitleInput'] var inputText01Status = (!inputText01)?false:true var inputText02 = formObject.values['searchStringInput'] var inputText02Status = (!inputText02)?false:true // ALL CONDITIONS MUST BE TRUE TO VALIDATE var validation = (inputText01Status && inputText02Status) ? true:false return validation } formPromise.then(function(formObject){ var targetTagName = formObject.values['tagTitleInput'] var targetString = formObject.values['searchStringInput'] console.log('tagTitleInput: ',targetTagName) console.log('searchStringInput: ',targetString) var targetTag = null tags.apply(function(tag){ if(tag.name == targetTagName){ targetTag = tag return ApplyResult.Stop } }) var tag = targetTag || new Tag(targetTagName) flattenedTasks.filter(function(item){ return item.note.includes(targetString) }).forEach(task => { task.addTag(tag) }) }) formPromise.catch(function(err){ console.error("form cancelled", err.message) }) }); action.validate = function(selection, sender){ // validation code // selection options: tasks, projects, folders, tags return true }; return action; })();

Push Out All Deferred Projects

Here’s an example plug-in that uses the flattenedProjects property and the filter() function to locate and adjust all deferred projects:

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.push-out-all-deferred-projects", "version": "1.2", "description": "This action will push out all deferred projects the entered number of days.", "label": "Push Out All Deferred Projects", "shortLabel": "Push Out All Deferred Projects" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code // selection options: tasks, projects, folders, tags, allObjects var maxValue = 180 var minValue = 1 var textInputField = new Form.Field.String( "textInput", String("(" + minValue + "-" + maxValue + ")"), null ) var checkboxSwitch = new Form.Field.Checkbox( "shouldPushDueDates", "Push existing due dates", true ) var inputForm = new Form() inputForm.addField(textInputField) inputForm.addField(checkboxSwitch) var formPrompt = "Enter number of days:" var buttonTitle = "Continue" formPromise = inputForm.show(formPrompt,buttonTitle) inputForm.validate = function(formObject){ inputText = formObject.values['textInput'] if (!inputText) {return false} var isnum = /^[0-9]+$/i.test(inputText) if (isnum){ var intValue = parseInt(inputText) return ((intValue <= maxValue && intValue >= minValue) ? true:false) } return false } formPromise.then(function(formObject){ var textValue = formObject.values['textInput'] var intValue = parseInt(textValue) console.log("intValue: ",intValue) var shouldPushDueDates = formObject.values['shouldPushDueDates'] var deferredProjects = flattenedProjects.filter(p => { return p.deferDate != null }) var cal = Calendar.current var dc = new DateComponents() dc.day = intValue deferredProjects.forEach(project => { if(shouldPushDueDates){ var currentDueDate = project.dueDate if(currentDueDate){ var newDueDate = cal.dateByAddingDateComponents(currentDueDate,dc) project.dueDate = newDueDate } } var currentDeferDate = project.deferDate var newDeferDate = cal.dateByAddingDateComponents(currentDeferDate,dc) project.deferDate = newDeferDate }) }) formPromise.catch(function(err){ console.error("form cancelled", err.message) }) }); action.validate = function(selection, sender){ // validation code // selection options: tasks, projects, folders, tags, allObjects return (flattenedProjects.filter(p => {return p.deferDate != null}).length > 0) }; return action; })();

DISCLAIMER