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.
projectsMatching(search: String) → (Array of Project) • Returns each existing Project that Smart Matches the given search. The result will be in the same order and have the same projects as would be found when searching this string in the Quick Open window.
foldersMatching(search: String) → (Array of Folder) • Returns each existing Folder that Smart Matches the given search. The result will be in the same order and have the same folders as would be found when searching this string in the Quick Open window.
tagsMatching(search: String) → (Array of Tag) • Returns each existing Tag that Smart Matches the search. The result will be in the same order and have the same tags as would be found when searching this string in the Quick Open window.
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")
Finding Inbox items ready to process by using the filter() function:
List Inbox Items Ready to Process
inboxItems = inbox.filter(task => {
return (
task.taskStatus === Task.Status.DueSoon ||
task.taskStatus === Task.Status.Available
)
})
Selecting the first Inbox item:
Select First Inbox Item
win = document.windows[0]
win.focus = []
win.perspective = Perspective.BuiltIn.Inbox
inboxItems = inbox.filter(task => {
return (
task.taskStatus === Task.Status.DueSoon ||
task.taskStatus === Task.Status.Available
)
})
if(inboxItems.length > 0){
tree = win.content
node = tree.nodeForObject(inboxItems[0])
tree.reveal([node])
tree.select([node])
}
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:
Folder.byIdentifier(identifier: String) → (Folder or null) • Returns the Folder with the specified identifier, or null if no such folder exists.
Tag.byIdentifier(identifier: String) → (Tag or null) • Returns the Tag with the specified identifier, or null if no such tag exists.
Task.byIdentifier(identifier: String) → (Task or null) • Returns the Task with the specified identifier, or null if no such task exists.
Project.byIdentifier(identifier: String) → (Project or null) • Returns the Project with the specified identifier, or null if no such project exists.
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:
flattenedProjects (ProjectArray r/o) • Returns a flat array of all projects in the database, sorted by their order in the database.
flattenedTasks (TaskArray r/o) • Returns a flat array of all tasks in the database, including inbox items, root tasks for projects, task groups and individual tasks. Tasks are sorted by their order in the database, with the inbox preceeding the library.
flattenedFolders (FolderArray r/o) • Returns a flat array of all folders in the database, sorted by their order in the database.
flattenedTags (TagArray r/o) • Returns a flat array of all tags in the database, sorted by their order in the database.
flattenedSections (SectionArray r/o) • Returns a flat array of all folders and project in the database, sorted by their order in the database.
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:
flattenedProjects (ProjectArray r/o) • Returns a flat array of all projects in this folder, sorted by their order in the database.
flattenedFolders (FolderArray r/o) • Returns a flat array of all folders in this folder, sorted by their order in the database.
flattenedSections (SectionArray r/o) • Returns a flat array of all folders and project in this folder, sorted by their order in the database.
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:
flattenedTasks (TaskArray r/o) • Returns a flat array of all tasks contained within this Project’s root Task. Tasks are sorted by their order in the database.
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:
- 2d, –3w, 1h, 1y1m, and so on • Relative dates and times put the date at a certain amount of time from right now. Negative numbers represent times in the past.
- 2 days, –3 weeks, 1 hour, 1 year 1 month, and so on • You can use the full names of units too.
- yesterday, tomorrow, tonight, next thursday, last month, this friday, and so on • You can refer to relative dates using common words. “This”, “next”, and “last” have specific meanings: this friday always means the Friday in this week, next friday always means the Friday in the next week, and last friday always means the Friday in last week, regardless of what day today is. Other units work in the same way.
- september, fri, 2019, and so on • If you enter the name of a specific time period, the date will be at its beginning. So september means September first.
- 5/23/08 10a, 9.30.09 2:00 PM, and so on • You can use the short date format as defined in your Language & Region system preferences.
- 2w sat, 4d @ 5p, mon 6a, aug 6 tue 5p, and so on • Mix the available formats however you like.
- now, 9, 14:00, tom, and so on • Omni’s date parser makes its best guess at things like bare numbers, times, and word fragments.
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.
SkipChildren (ApplyResult r/o) • The descendants of the current item are skipped.
SkipPeers (ApplyResult r/o) • The unvisited peers of the current item are skipped.
Stop (ApplyResult r/o) • The call to apply terminates with no further items being visited.
all (Array of ApplyResult) • An array of all items of this enumeration. Often used when creating a Form instance.
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.
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;
})();