The Outline
In OmniFocus for macOS and OmniFocus 4.x for iOS/iPadOS, the materials displayed in the “Content View” are organized as an outline.
An outline is a hierarchical structure comprised of ordered nodes, each of which may contain other nodes, each of which may contain other nodes, and so on.
A node is an organizational wrapper for an instance of the OmniFocus DatedObject class: folder, project, tag, or task.
In essence, outlines are used to display ordered content in a relational manner. The following documentation details how to access the displayed elements of the content outline and control the manner in which they are displayed.
The Two Trees
The Window class has two properties whose values are hierarchical collections of ordered nodes, content and sidebar.
Instance Properties of DocumentWindow Class (subset)
content (ContentTree or null) • The tree of nodes representing the content area of the window.
sidebar (SidebarTree or null) • The tree of nodes representing the sidebar of the window.
focus (SectionArray or null) • The Folders and Projects that the window is focusing on, limiting the sidebar to show only these items. Available on iOS/iPadOS in OmniFocus 4.x.
selection (Selection r/o) • The current selection in the window.
perspective (Perspective.BuiltIn or Perspective.Custom or null) • The perspective shown in the window.
Focus Specified Project
project = flattenedProjects.byName("My To Do List")
if(project){
window = document.windows[0]
window.perspective = Perspective.BuiltIn.Projects
window.focus = [project]
}
Instance Functions of DocumentWindow Class (subset)
selectObjects(objects: Array of DatabaseObject) → ( ) • Clears the current selection and then selects the given objects, if present in the current perspective of this window. On iOS, if objects contains more than one object, this will put the outline view into edit mode to accomodate multiple selection.
The Tree Class (outline)
The value of the content and sidebar properties of the DocumentWindow class are instances of the Tree class. The following details the instance properties and functions of this class:
Instance Properties
rootNode (TreeNode r/o) • Returns the rootNode of the Editor (outline).
selectedNodes (Array of TreeNode r/o) • Returns the list of selected TreeNodes, in the order they appear in the tree.
Instance Functions
nodeForObject(object: Object) → (TreeNode or null) • Returns the TreeNode that represents the object in this Tree, or null if it cannot be found (possibly filtered out). This function is used to derive the node that wraps a task, project, tag, or folder.
nodesForObjects(object: Array of Object) → (Array of TreeNode) • Returns an array of TreeNodes for the objects that are currently in the Tree, according to the same filters as nodeForObject(). The size of the resulting node array may be smaller (even empty) than the passed in objects array.
reveal(nodes: Array of TreeNode) → ( ) • Ensures the ancestor nodes of all the specified nodes are expanded.
select(nodes: Array of TreeNode, extending: Boolean or null) → ( ) • Selects the specified TreeNodes that are visible (nodes with collapsed ancestors cannot be selected). If extending is true, the existing selection is not cleared.
copyNodes(nodes: Array of TreeNode, to: Pasteboard) → ( ) • Writes a serialized version of the nodes to the specified pasteboard.
paste(from: Pasteboard, parentNode: TreeNode or null, childIndex: Number or null) → ( ) • Attempts to read a serialized version of nodes from the pasteboard and create new items at the specified location in the receiver. If a parent node is not specified, then the root node of the receiver is assumed. If a childIndex is not specified, any new children are placed at the end of the parent’s existing children.
Reveal and Select Specified Inbox Task
window = document.windows[0]
window.perspective = Perspective.BuiltIn.Inbox
var targetTask = null
inbox.apply(task => {
if(task.name === "My Special Task"){
targetTask = task
return ApplyResult.Stop
}
})
if(targetTask){
tree = window.content
node = tree.nodeForObject(targetTask)
tree.reveal([node])
tree.select([node])
}
The TreeNode Class (node)
The TreeNode class represents the physical display of the individual database objects as nodes in the outline tree.
TreeNode Instance Properties
canCollapse (Boolean r/o) • Returns true if this TreeNode can be collapsed.
canExpand (Boolean r/o) • Returns true if this TreeNode can be expanded.
childCount (Number r/o) • Returns the number of children directly under this node.
children (Array of TreeNode r/o) • Returns the array of children that are visible under this node, according to any filtering that is being done, and in the order specified by any sorting rules that have been established.
index (Number r/o) • Returns the index of this TreeNode among its siblings, or zero for the rootNode.
isExpanded (Boolean r/o) • Returns true if this TreeNode is currently expanded.
isNoteExpanded (Boolean r/o) • Returns true if the note of this TreeNode is currently expanded.
isRevealed (Boolean r/o) • Returns true if the TreeNode is the rootNode or all of its ancestor nodes are expanded.
isRootNode (Boolean r/o) • Returns true if this node is the rootNode of its tree.
isSelectable (Boolean r/o) • Returns true if this TreeNode can be selected. The rootNode cannot be selected, nor can nodes that aren’t revealed.
isSelected (Boolean) • Set to true if this TreeNode is in the list of selected nodes for its tree. Attempting to set this to true will do nothing if the node is not revealed (or is the root node).
level (Number r/o) • Returns the nesting level of the TreeNode, relative to the root of the tree. The rootNode of an Outline has level zero, its children have level one, and so on. Note that if only a portion of the model is being shown, this level may not match the level of the underlying object.
object (Object r/o) • The model object which this node wraps.
parent (TreeNode or null r/o) • Returns the TreeNode that contains this node, or null if this is the rootNode.
rootNode (TreeNode r/o) • Returns the root TreeNode for the tree that this node belongs to.
TreeNode Instance Functions
childAtIndex(childIndex: Number) → (TreeNode) • Returns the child node at the given index.
expand(completely: Boolean or null) → ( ) • Attempts to expand the TreeNode. If completely is passed, all the child nodes will be expanded as they allow.
collapse(completely: Boolean or null) → ( ) • Attempts to collapse the TreeNode. If completely is passed, all the child nodes will be collapse as they allow.
expandNote(completely: Boolean or null) → ( ) • Attempts to expand the inline note of the TreeNode. If completely is passed, all the child node notes will be expanded.
collapseNote(completely: Boolean or null) → ( ) • Attempts to collapse the inline note of the TreeNode. If completely is passed, all the child node notes will be collapsed.
reveal() → ( ) • Expands all the ancestors of the TreeNode.
apply(function: Function) → ( ) • Calls the supplied function for each TreeNode in the receiver (including the receiver), passing that node as the single argument.
Expand Displayed Nodes
tree = document.windows[0].content
tree.rootNode.children.forEach(node => node.expand(true))
Collapse Displayed Nodes
tree = document.windows[0].content
tree.rootNode.children.forEach(node => node.collapse(true))
The control of the display of notes is independant of the expansion or contraction of the node display:
Expand All Notes
tree = document.windows[0].content
tree.rootNode.children.forEach(node => node.expandNote(true))
Collapse All Notes
tree = document.windows[0].content
tree.rootNode.children.forEach(node => node.collapseNote(true))
Examples
Since the content display in the iPadOS/iOS version of OmniFocus has been redesigned using Swift UI, it now exposes the underlying “tree” structure of the content outline to automation.
For example, the following script will select all tasks in the current content view.
Note:
- The “tree” is the value of the content property of the Window class. The “tree” is made up of a hierarchical set of “tree nodes.” An instance of the TreeNode class can be a task, project, etc.
- The apply() function is used to iterate all nodes of the tree.
- The select() function of the Tree class is used to select the provided array of nodes. Another similar function is “reveal”
- The rootNode is a property of the Tree class whose value is the parent node of the entire tree.
Select All Tasks
tree = document.windows[0].content
tree.select([])
tree.rootNode.apply(node => {
if(node.object instanceof Task){tree.select([node], true)}
})
Here's a variation of the previous script concept that selects only tasks whose title begins with the string "2nd-":
Select Tasks Whose Name Begins With…
tree = document.windows[0].content
tree.select([])
tree.rootNode.apply(node => {
item = node.object
if(item instanceof Task){
if(item.name.startsWith("2nd-")){tree.select([node],true)}
}
})
Here's how to focus all projects in the content view whose title begins with "Get":
Focus Projects Whose Name Begins With…
document.windows[0].focus = []
tree = document.windows[0].content
items = []
tree.rootNode.apply(node => {
item = node.object
if(item instanceof Project){
if(item.name.startsWith("Get ")){items.push(item)}
}
})
if (items.length > 0){document.windows[0].focus = items}
How to copy the selected nodes to the clipboard:
Copy Selected Nodes to Clipboard
tree = document.windows[0].content
tree.copyNodes(tree.selectedNodes, Pasteboard.general)
Select all sidebar items. Works with all perspectives that have a corresponding sidebar:
Select All Sidebar Items
window = document.windows[0]
tree = window.sidebar
tree.select([])
tree.rootNode.apply(item => {
if(!item.isRootNode){
tree.select([item], true)
}
})
Create Markdown from Contents
This script will create markdown for the current window contents, and place the created content on the clipboard:
Markdown from Content
tree = document.windows[0].content
markdown = ""
counter = 0
tree.rootNode.apply(node => {
if(node.isRootNode === false){
item = node.object
level = node.level
name = item.name
type = item.constructor.name
if(type === "Project"){
prefix = ""
} else {
prefix = ">".repeat(level - 1)
}
entry = `${prefix}(${type}) - ${name}`
if(counter === 0){markdown += entry} else {markdown += `\n\n${entry}`}
counter = counter + 1
}
})
Pasteboard.general.string = markdown
And the script as a plug-in:
Markdown of Contents to Clipboard (@)
/*{
"type": "action",
"targets": ["omnifocus"],
"author": "Otto Automator",
"identifier": "com.omni-automation.of.markdown-of-contents-to-clipboard",
"version": "1.0",
"description": "Places markdown of the contents of the window on the clipboard.",
"label": "Markdown of Contents",
"shortLabel": "Markdown of Contents",
"image": "paragraphsign"
}*/
(() => {
const action = new PlugIn.Action(function(selection, sender){
tree = document.windows[0].content
markdown = ""
counter = 0
tree.rootNode.apply(node => {
if(node.isRootNode === false){
item = node.object
level = node.level
name = item.name
type = item.constructor.name
if(type === "Project"){
prefix = ""
} else {
prefix = ">".repeat(level - 1)
}
entry = `${prefix}(${type}) - ${name}`
if(counter === 0){markdown += entry} else {markdown += `\n\n${entry}`}
counter = counter + 1
}
})
Pasteboard.general.string = markdown
new Alert("COMPLETED", "Markdown is on clipboard.").show()
});
action.validate = function(selection, sender){
return (document.windows[0].content.rootNode.children.length > 0)
};
return action;
})();
Make the Selection Visible (iOS)
To make the selected items visible in the content view, use the scrollSelectionToVisible() function:
Make Selection Visible (iOS)
document.windows[0].content.scrollSelectionToVisible()