Using Text Objects

Although the process of working with text objects is more intricate than working with a simple text string, the type of actions you can perform with text objects are varied and exciting to create and use.

The example featured on this page demonstrates how to create actions that mark and un-mark rows as being in or out of DRAFT mode, by inserting or removing the text “DRAFT: ” styled in bold red, placed at the start of row.

In addition, this example demonstrates how to assign macOS keyboard shortcuts to the individual actions.

Prepending a Styled Text Marker to Rows

Here is the basic script for inserting a styled text marker at the beginning of each selected row in an OmniOutliner document. This script is easily adaptable to use as an Omni-Automation action. A line-by-line description of the script is below the script code:

// style name and insert string styleTitle = 'DRAFT INDICATOR' insertString = 'DRAFT: ' // get the titles of the existing named styles nStyles = document.outline.namedStyles.all titles = nStyles.map(function(nStyle){return nStyle.name}) // create a named style if it doesn't exist if (titles.indexOf(styleTitle) == -1){ nStyle = document.outline.namedStyles.add(styleTitle) nStyle.set(Style.Attribute.FontFamily, 'Helvetica') nStyle.set(Style.Attribute.FontName, 'Helvetica Bold') nStyle.set(Style.Attribute.FontWeight, 9) nStyle.set(Style.Attribute.FontFillColor, Color.red) nStyle.set(Style.Attribute.FontStrokeColor, Color.red) } // get a reference to the target style targetStyle = document.outline.namedStyles.byName(styleTitle) // create a new text object using the insert string textInsert = new Text(insertString, document.outline.baseStyle); // apply the special style to the new text object textInsert.style.addNamedStyle(targetStyle); // get the selected rows selectedItems = document.editors[0].selectedNodes.map(function(node){ return node.object }) // insert the new text object into the start of the target text object selectedItems.forEach(function(item){ targetTextObj = item.valueForColumn(document.outline.outlineColumn) textObjChars = targetTextObj.ranges(TextComponent.Characters) textStr = targetTextObj.string if (textStr != '' && textStr.startsWith(insertString) === false){ targetTextObj.insert(textObjChars[0].start, textInsert) } })
/*{ "type": "action", "targets": ["omnioutliner"], "author": "Your Name or Company", "description": "This action will append “DRAFT: ” in red to the start of each of the selected rows.", "label": "DRAFT - Mark Selected Rows", "shortLabel": "Mark as Draft" }*/ var _ = function(){ var action = new PlugIn.Action(function(selection, sender) { // selection options: columns, document, editor, items, nodes, styles // get the selected rows var selectedItems = selection.items // style name and insert string var styleTitle = 'DRAFT INDICATOR' var insertString = 'DRAFT: ' // get the titles of the existing named styles nStyles = document.outline.namedStyles.all titles = nStyles.map(function(nStyle){return nStyle.name}) // create a named style if it doesn't exist if (titles.indexOf(styleTitle) == -1){ nStyle = document.outline.namedStyles.add(styleTitle) nStyle.set(Style.Attribute.FontFamily, 'Helvetica') nStyle.set(Style.Attribute.FontName, 'Helvetica Bold') nStyle.set(Style.Attribute.FontWeight, 9) nStyle.set(Style.Attribute.FontFillColor, Color.red) nStyle.set(Style.Attribute.FontStrokeColor, Color.red) } // get a reference to the target style targetStyle = document.outline.namedStyles.byName(styleTitle) // create a new text object using the insert string textInsert = new Text(insertString, document.outline.baseStyle); // apply the special style to the new text object textInsert.style.addNamedStyle(targetStyle); // insert the new text object into the start of the target text object selectedItems.forEach(function(item){ targetTextObj = item.valueForColumn(document.outline.outlineColumn) textObjChars = targetTextObj.ranges(TextComponent.Characters) textStr = targetTextObj.string if (textStr != '' && textStr.startsWith(insertString) === false){ targetTextObj.insert(textObjChars[0].start, textInsert) } }) }); action.validate = function(selection, sender) { // selection options: columns, document, editor, items, nodes, styles if(selection.nodes.length > 0){return true} else {return false} }; return action; }(); _;
omnioutliner://localhost/omnijs-run?script=%2F%2F%20style%20name%20and%20insert%20string%0AstyleTitle%20%3D%20%27DRAFT%20INDICATOR%27%0AinsertString%20%3D%20%27DRAFT%3A%20%27%0A%0A%2F%2F%20get%20the%20titles%20of%20the%20existing%20named%20styles%0AnStyles%20%3D%20document%2Eoutline%2EnamedStyles%2Eall%0Atitles%20%3D%20nStyles%2Emap%28function%28nStyle%29%7Breturn%20nStyle%2Ename%7D%29%0A%2F%2F%20create%20a%20named%20style%20if%20it%20doesn%27t%20exist%0Aif%20%28titles%2EindexOf%28styleTitle%29%20%3D%3D%20-1%29%7B%0A%09nStyle%20%3D%20document%2Eoutline%2EnamedStyles%2Eadd%28styleTitle%29%0A%09nStyle%2Eset%28Style%2EAttribute%2EFontFamily%2C%20%27Helvetica%27%29%0A%09nStyle%2Eset%28Style%2EAttribute%2EFontName%2C%20%27Helvetica%20Bold%27%29%0A%09nStyle%2Eset%28Style%2EAttribute%2EFontWeight%2C%209%29%0A%09nStyle%2Eset%28Style%2EAttribute%2EFontFillColor%2C%20Color%2Ered%29%0A%09nStyle%2Eset%28Style%2EAttribute%2EFontStrokeColor%2C%20Color%2Ered%29%0A%7D%0A%0A%2F%2F%20get%20a%20reference%20to%20the%20target%20style%0AtargetStyle%20%3D%20document%2Eoutline%2EnamedStyles%2EbyName%28styleTitle%29%0A%2F%2F%20create%20a%20new%20text%20object%20using%20the%20insert%20string%0AtextInsert%20%3D%20new%20Text%28insertString%2C%20document%2Eoutline%2EbaseStyle%29%3B%0A%2F%2F%20apply%20the%20special%20style%20to%20the%20new%20text%20object%0AtextInsert%2Estyle%2EaddNamedStyle%28targetStyle%29%3B%0A%0A%2F%2F%20get%20the%20selected%20rows%0AselectedItems%20%3D%20document%2Eeditors%5B0%5D%2EselectedNodes%2Emap%28function%28node%29%7B%0A%09return%20node%2Eobject%0A%7D%29%0A%2F%2F%20insert%20the%20new%20text%20object%20into%20the%20start%20of%20the%20target%20text%20object%0AselectedItems%2EforEach%28function%28item%29%7B%0A%09targetTextObj%20%3D%20item%2EvalueForColumn%28document%2Eoutline%2EoutlineColumn%29%0A%09textObjChars%20%3D%20targetTextObj%2Eranges%28TextComponent%2ECharacters%29%0A%09textStr%20%3D%20targetTextObj%2Estring%0A%09if%20%28textStr%20%21%3D%20%27%27%20%26%26%20textStr%2EstartsWith%28insertString%29%20%3D%3D%3D%20false%29%7B%0A%09%09targetTextObj%2Einsert%28textObjChars%5B0%5D%2Estart%2C%20textInsert%29%0A%09%7D%0A%7D%29

 2  The title to use for the style created for the text insert.

 3  The text to use for the styled text insert, in this case the word: “DRAFT: ”

 5-16  Create a new named style for the text insert if the style does not already exist.

 18-19  Generate a reference to the named style for the insert.

 20-21  Create a new text object from the indicated string. Use the document’s baseStyle for generating the text object.

 22-23  Apply the named style to the created text object.

 25-28  Get references to the selected items.

 30-37  Iterate the selected items, inserting the style text object at the start of each row.

 31  Create a text object containing the contents of the topic column of the iterated row.

 32  Get an array of the character objects of the row’s text object.

 33  Generate a text string of the contents of the text object. This will be used to determine if the text insert already begins the row.

 34-36  If the row is not empty and its text does not begin with the insert string, then insert the styled text object at the beginning of the row’s text object.

The result of the script is a styled text indicator at the beginning of a selected row:

text-prepend-styled

Removing the Text Marker

Of course, once “draft rows” have been edited, you will want to remove the text marker from the edited rows. To accomplish this task, the following script function uses the apply() method of the TreeNode class to iterate and process every row in the outline.

function removeLeadingStringFromEditorRows(stringToRemove){ searchParams = [Text.FindOption.Anchored,Text.FindOption.CaseInsensitive] document.editors[0].rootNode.apply(function(node){ if (node.isRootNode === false){ textObj = node.valueForColumn(document.outline.outlineColumn) if(textObj != null){ matchingRange = textObj.find(stringToRemove,searchParams) if (matchingRange != null){textObj.remove(matchingRange)} } } }) } removeLeadingStringFromEditorRows('DRAFT: ')
omnioutliner://localhost/omnijs-run?script=function%20removeLeadingStringFromEditorRows%28stringToRemove%29%7B%0A%09searchParams%20%3D%20%5BText%2EFindOption%2EAnchored%2CText%2EFindOption%2ECaseInsensitive%5D%0A%09document%2Eeditors%5B0%5D%2ErootNode%2Eapply%28function%28node%29%7B%0A%09%09if%20%28node%2EisRootNode%20%3D%3D%3D%20false%29%7B%0A%09%09%09textObj%20%3D%20node%2EvalueForColumn%28document%2Eoutline%2EoutlineColumn%29%0A%09%09%09matchingRange%20%3D%20textObj%2Efind%28stringToRemove%2CsearchParams%29%0A%09%09%09if%20%28matchingRange%20%21%3D%20null%29%7BtextObj%2Eremove%28matchingRange%29%7D%0A%09%09%7D%0A%09%7D%29%0A%7D%0AremoveLeadingStringFromEditorRows%28%27DRAFT%3A%20%27%29
/*{ "type": "action", "targets": ["omnioutliner"], "author": "Your Name or Company", "description": "This action will remove “DRAFT: ” from the start of all rows.", "label": "DRAFT - Remove from All Rows", "shortLabel": "Un-mark All as Draft" }*/ var _ = function(){ var action = new PlugIn.Action(function(selection, sender) { // selection options: columns, document, editor, items, nodes, styles stringToRemove = 'DRAFT: ' searchParams = [Text.FindOption.Anchored,Text.FindOption.CaseInsensitive] document.editors[0].rootNode.apply(function(node){ if (node.isRootNode === false){ textObj = node.valueForColumn(document.outline.outlineColumn) if(textObj != null){ matchingRange = textObj.find(stringToRemove,searchParams) if (matchingRange != null){textObj.remove(matchingRange)} } } }) }); action.validate = function(selection, sender) { // selection options: columns, document, editor, items, nodes, styles return true }; return action; }(); _;

 2  The text search parameters to be used with the find() method of the TextObject class. The search is set to begin from the leftmost character and ignore the text case of the searched text.

 3  The apply() method of the TreeNode class is used to iterate every row of the rootNode of the outline.

 4  A comparison to make sure the processed node (row) is not the rootNode.

 5  Generate a text object containing the topic text of the row.

 6  The find() method of the TextObject class is used to determine if a row begins with the text marker. The result is a text range object for the matching text.

 7  If a text range is returned from the search, remove the characters in the matched range.

 6  Call the script function, passing in the text of the text marker: “DRAFT: ”

To adapt the previous function to examine only the selected rows, add a condition using the isSelected property of the TreeNode class in the comparison (line 4):

function removeLeadingStringFromSelectedEditorRows(stringToRemove){ searchParams = [Text.FindOption.Anchored,Text.FindOption.CaseInsensitive] document.editors[0].rootNode.apply(function(node){ if (node.isRootNode === false && node.isSelected){ textObj = node.valueForColumn(document.outline.outlineColumn) if(textObj != null){ matchingRange = textObj.find(stringToRemove,searchParams) if (matchingRange != null){textObj.remove(matchingRange)} } } }) } removeLeadingStringFromSelectedEditorRows('DRAFT: ')
/*{ "type": "action", "targets": ["omnioutliner"], "author": "Your Name or Company", "description": "This action will remove “DRAFT: ” from the start of selected rows.", "label": "DRAFT - Remove from Selected Rows", "shortLabel": "Un-mark Selected as Draft" }*/ var _ = function(){ var action = new PlugIn.Action(function(selection, sender) { // selection options: columns, document, editor, items, nodes, styles stringToRemove = 'DRAFT: ' searchParams = [Text.FindOption.Anchored,Text.FindOption.CaseInsensitive] document.editors[0].rootNode.apply(function(node){ if (node.isRootNode === false && node.isSelected){ textObj = node.valueForColumn(document.outline.outlineColumn) if(textObj != null){ matchingRange = textObj.find(stringToRemove,searchParams) if (matchingRange != null){textObj.remove(matchingRange)} } } }) }); action.validate = function(selection, sender) { // selection options: columns, document, editor, items, nodes, styles if(selection.nodes.length > 0){return true} else {return false} }; return action; }(); _;
omnioutliner://localhost/omnijs-run?script=function%20removeLeadingStringFromSelectedEditorRows%28stringToRemove%29%7B%0A%09searchParams%20%3D%20%5BText%2EFindOption%2EAnchored%2CText%2EFindOption%2ECaseInsensitive%5D%0A%09document%2Eeditors%5B0%5D%2ErootNode%2Eapply%28function%28node%29%7B%0A%09%09if%20%28node%2EisRootNode%20%3D%3D%3D%20false%20%26%26%20node%2EisSelected%29%7B%0A%09%09%09textObj%20%3D%20node%2EvalueForColumn%28document%2Eoutline%2EoutlineColumn%29%0A%09%09%09if%28textObj%20%21%3D%20null%29%7B%0A%09%09%09%09matchingRange%20%3D%20textObj%2Efind%28stringToRemove%2CsearchParams%29%0A%09%09%09%09if%20%28matchingRange%20%21%3D%20null%29%7BtextObj%2Eremove%28matchingRange%29%7D%0A%09%09%09%7D%0A%09%09%7D%0A%09%7D%29%0A%7D%0AremoveLeadingStringFromSelectedEditorRows%28%27DRAFT%3A%20%27%29

And here is a variation of the previous function that uses the rootItem of the Item class (line 3) rather than the rootNode of the Editor class for the iteration process:

function removeLeadingStringFromAllRows(stringToRemove){ searchParams = [Text.FindOption.Anchored,Text.FindOption.CaseInsensitive] rootItem.apply(function(item){ if (item != rootItem){ node = document.editors[0].nodeForItem(item) textObj = node.valueForColumn(document.outline.outlineColumn) if (textObj != null){ matchingRange = textObj.find(stringToRemove,searchParams) if (matchingRange != null){textObj.remove(matchingRange)} } } }) }

Example Actions

Here are links to the set example action files for marking and un-marking rows in an outline.

macOS_deviceTo install on macOS, download and unpack the ZIP archive files from the links above. Choose “Plug-Ins…” from the OmniOutliner automation menu, and place the files in the PlugIns folder now opened on the desktop. The actions will now be available from the OmniOutliner automation menu.

macOS_deviceTo install on iOS, tap each link, choosing the “Open” option in forthcoming dialog. Then tap “More…” and choose the “Copy to OmniOutliner” option. The installed action will appear in the Plug-Ins view on your device, and will be available from the OmniOutliner automation menu:

draft-plugins-installed-ios

Keyboard Shortcuts

One the great productivity features of macOS is the ability to assign keyboard shortcuts to any application menu item, including actions appearing the OmniOutliner automation menu.

draft-marker-actions

Show the Shortcuts tab in the Keyboard system preference pane and select “App Shortcuts” in the list on the left of the window.

keyboard-shortcuts-preference-pane

Utility Functions

Here is a script function for removing spaces from the beginning of a row. It uses the remove() method of the TextObject class.

function trimLeadingSpaces(textObj){ while (true){ charStr = textObj.characters[0].string if (charStr != ' '){ break } textObj.remove(textObj.characters[0].range) } }

The following scripts call the trimLeadingSpaces() function to remove leading spaces from every row in the document:

function trimLeadingSpaces(textObj){ while (true){ charStr = textObj.characters[0].string if (charStr != ' '){ break } textObj.remove(textObj.characters[0].range) } } document.editors[0].rootNode.apply(function(node){ if (node.isRootNode === false){ textObj = node.valueForColumn(document.outline.outlineColumn) trimLeadingSpaces(textObj) } })
omnioutliner:///omnijs-run?script=function%20trimLeadingSpaces%28textObj%29%7B%0A%09while%20%28true%29%7B%0A%09%09charStr%20%3D%20textObj%2Echaracters%5B0%5D%2Estring%0A%09%09if%20%28charStr%20%21%3D%20%27%20%27%29%7B%20break%20%7D%0A%09%09textObj%2Eremove%28textObj%2Echaracters%5B0%5D%2Erange%29%0A%09%7D%0A%7D%0A%0Adocument%2Eeditors%5B0%5D%2ErootNode%2Eapply%28function%28node%29%7B%0A%09if%20%28node%2EisRootNode%20%3D%3D%3D%20false%29%7B%0A%09%09textObj%20%3D%20node%2EvalueForColumn%28document%2Eoutline%2EoutlineColumn%29%0A%09%09trimLeadingSpaces%28textObj%29%0A%09%7D%0A%7D%29
function trimLeadingSpaces(textObj){ while (true){ charStr = textObj.characters[0].string if (charStr != ' '){ break } textObj.remove(textObj.characters[0].range) } } rootItem.apply(function(item){ if (item != rootItem){ node = document.editors[0].nodeForItem(item) textObj = node.valueForColumn(document.outline.outlineColumn) trimLeadingSpaces(textObj) } })
omnioutliner:///omnijs-run?script=function%20trimLeadingSpaces%28textObj%29%7B%0A%09while%20%28true%29%7B%0A%09%09charStr%20%3D%20textObj%2Echaracters%5B0%5D%2Estring%0A%09%09if%20%28charStr%20%21%3D%20%27%20%27%29%7B%20break%20%7D%0A%09%09textObj%2Eremove%28textObj%2Echaracters%5B0%5D%2Erange%29%0A%09%7D%0A%7D%0A%0ArootItem%2Eapply%28function%28item%29%7B%0A%09if%20%28item%20%21%3D%20rootItem%29%7B%0A%09%09node%20%3D%20document%2Eeditors%5B0%5D%2EnodeForItem%28item%29%0A%09%09textObj%20%3D%20node%2EvalueForColumn%28document%2Eoutline%2EoutlineColumn%29%0A%09%09trimLeadingSpaces%28textObj%29%0A%09%7D%0A%7D%29
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