Tags

In OmniFocus, a tag represents an association that a task has to the world around it. A tag could represent a person, place, or thing most relevant to completion of a project, or it could represent a mindset most applicable to execution of an action item.

An item can have as many tags as you find useful, and there is no specific purpose assigned to them; use tags to assign priority, group items by energy level or required equipment, or don’t use them at all.

The Tags perspective displays a list of your tags in the sidebar, and a list of all your actions grouped by the tags they belong to in the outline. Here is a script for setting the value of the perspective property of the Window class so that the Tags perspective is displayed:

document.windows[0].perspective = Perspective.BuiltIn.Tags
omnifocus://localhost/omnijs-run?script=try%7Bdocument%2Ewindows%5B0%5D%2Eperspective%20%3D%20Perspective%2EBuiltIn%2ETags%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

Class Properties of the Tag Class

Here are the properties of the Tag class:

In the View Options panel for the Forecast perspective, you can assign an existing tag to used as the “forecast tag.” Once this tag is assigned to an item, the tagged item will appear in the Forecast view. Using Omni Automation, you can add the forecast tag to a task or project.

var task = inbox.byName("Car Repair") if(task && Tag.forecastTag){task.addTag(Tag.forecastTag)}

If no tag has been assigned to be the “forecast tag” the value of this property will be returned as: null. Also note that the forecastTag property is read-only and its value cannot be set via a script.

Instance Properties of the Tag Class

Here are the properties of an instance of the Tag class:

Properties of the Tag.Status class:

var tgs = document.windows[0].selection.tags tsks = (tgs.length === 1) ? tgs[0].availableTasks : null
var tgs = document.windows[0].selection.tags projects = (tgs.length === 1) ? tgs[0].projects : null

Iterating Tags

References to top-level tags are retrieved through the use of the tags property of the Database class, as in this example for processing the top-level tags. Since the OmniFocus is the implied topmost scripting element, the values of one of its property can be accessed by simply entering the property name (such as tags) in the console:

A script example using the JavaScript forEach() function to iterate the array of top-level tags:

tags.forEach((tag)=>{ // processing statements go here })

A script example using the JavaScript map() function to retrieve the names of the top-level tags:

tagNames = tags.map((tag)=>{return tag.name})
omnifocus://localhost/omnijs-run?script=try%7BtagNames%20%3D%20tags%2Emap%28%28tag%29%3D%3E%7Breturn%20tag%2Ename%7D%29%3Bconsole%2Elog%28tagNames%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

Since tags may be contained within other tags (Tag Groups), a recursive routine is necessary to iterate the entire tree of database tags, like this script for retrieving the name of every tag, which uses the apply(…) function whose use is detailed in the topic section Finding Items.

tagNames = new Array() tags.apply(tag => tagNames.push(tag.name)) console.log(tagNames)
omnifocus://localhost/omnijs-run?script=try%7BtagNames%20%3D%20new%20Array%28%29%0Atags%2Eapply%28tag%20%3D%3E%20tagNames%2Epush%28tag%2Ename%29%29%0Aconsole%2Elog%28tagNames%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

And here’s a verson of the previous script that uses creates an instance of the Set class to remove all duplicates from the tag name list:

tagNames = new Array() tags.apply(tag => tagNames.push(tag.name)) tagNames = Array.from(new Set(tagNames)) console.log(tagNames)
omnifocus://localhost/omnijs-run?script=try%7BtagNames%20%3D%20new%20Array%28%29%0Atags%2Eapply%28tag%20%3D%3E%20tagNames%2Epush%28tag%2Ename%29%29%0AtagNames%20%20%3D%20Array%2Efrom%28new%20Set%28tagNames%29%29%20%0Aconsole%2Elog%28tagNames%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

Reference Tag by Name

To provide the ability to reference a specific tag by name, the tagNamed(…) function is included with both the OmniFocus Database class and the Tag class:

// Top-Level Not Existing tagNamed("Zebra") //--> null // Top-Level Exists tagNamed("Locrian") //--> [object Tag: Locrian] // Tags within Tag Group tagNamed("Locrian").children //--> [[object Tag: Minor],[object Tag: Major]] // Tag within Tag Group tagNamed("Locrian").tagNamed("Major") //--> [object Tag: Major]

Here’s a script example that uses the apply(…) function to iterate the entire tag heirarchy to locate the first tag whose name matches the specified name. If no tag is found, one with the specified name is created.

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

Here is an alternate method using the JavaScript filter(…) function for deriving a tag object with a specified name. Using the flattenedTags property of the Database class, the script checks every tag in the database to see if it is named the provided name. If no matching tag object exists, a new tag is created.

var nameToMatch = "Shrub" var result = flattenedTags.filter((tag) => tag.name === nameToMatch)[0] var tagObj = ((result) ? result : new Tag(nameToMatch))

Create New Tag

Instances of the Tag class are created using the standard JavaScript new item constructor.

tag = new Tag("LAST BY DEFAULT")
omnifocus://localhost/omnijs-run?script=try%7Btag%20%3D%20new%20Tag%28%22LAST%20BY%20DEFAULT%22%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D
tag = new Tag("FIRST TAG", tags.beginning)
omnifocus://localhost/omnijs-run?script=try%7Btag%20%3D%20new%20Tag%28%22FIRST%20TAG%22%2C%20tags%2Ebeginning%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

Using a JavaScript logical operator (||) to reference a top-level tag by name, or creating it if it does not exist. As explanation, A || B returns the value A if A can be coerced into true; otherwise, it returns B. Therefore, in the example below, if the tag “ZEBRA” exists, a reference to it will be returned, otherwise a new tag named “ZEBRA” will be created and a reference to the created tag will be the result that is stored in the variable.

tag = tagNamed("ZEBRA") || new Tag("ZEBRA")
omnifocus://localhost/omnijs-run?script=try%7Btag%20%3D%20tagNamed%28%22ZEBRA%22%29%20%7C%7C%20new%20Tag%28%22ZEBRA%22%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

Using the previous technique to add tags to either an existing named tag or a newly created tag with the target name:

tagGroup = tagNamed("Fall Colors") || new Tag("Fall Colors") colors = ["Pale Blue","Harvest Gold","Autumn Gray"] colors.forEach((color)=>{ tagGroup.tagNamed(color) || new Tag(color, tagGroup.beginning) })
omnifocus://localhost/omnijs-run?script=try%7BtagGroup%20%3D%20tagNamed%28%22Fall%20Colors%22%29%20%7C%7C%20new%20Tag%28%22Fall%20Colors%22%29%0Acolors%20%3D%20%5B%22Pale%20Blue%22%2C%22Harvest%20Gold%22%2C%22Autumn%20Gray%22%5D%0Acolors%2EforEach%28%28color%29%3D%3E%7B%0A%09tagGroup%2EtagNamed%28color%29%20%7C%7C%20new%20Tag%28color%2C%20tagGroup%2Ebeginning%29%0A%7D%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

 01  A Javascript logical operator is used to reference a tag which will contain the tags of the tag group.

 02  An array (list) of names for the new tags.

 03-05  Using the JavaScript forEach() function to iterate the array of tag names, passing into its function each of the tag names in sequence.

 04  Using a Javascript logical operator to ensure that a new tag is created if only one with the passed tag name does not exist. Note that the positional property beginning is used to indicate where in the tag group the new tag should be placed.

Here’s how to generate an array of tag references to tags whose names are in a list of tag names. If an existing tag is not matched to a name in the list, a new tag is created using the unmatched name.

tagNames = ["Bow","Stern","Port","Starboard"] var tagRefs = new Array() tagNames.forEach(tagName => { var targetTag = null tags.apply(function(tag){ if(tag.name === tagName){ targetTag = tag return ApplyResult.Stop } }) tagRef = targetTag || new Tag(tagName) tagRefs.push(tagRef) }) console.log(tagRefs)

Here’s a variation of the previous script written as a function that returns object references to tags whose names are included in the input array of tag names. If a tag name is not matched, a new tag with the unmatched name is created.

function getTagsWithNames(tagNames){ var tagRefs = new Array() tagNames.forEach(tagName => { var targetTag = null tags.apply(function(tag){ if(tag.name === tagName){ targetTag = tag return ApplyResult.Stop } }) tagRef = targetTag || new Tag(tagName) tagRefs.push(tagRef) }) return tagRefs }

Here is an alternate version of the previous function that uses the flattenedTags property of the Database class with the JavaScript filter(…) function to return an array of tag objects matching the provided list of tag names:

function getTagsWithNames(tagNames){ var tagRefs = new Array() tagNames.forEach(tagName => { var result = flattenedTags.filter((tag) => tag.name === tagName)[0] var tagObj = ((result) ? result : new Tag(tagName)) tagRefs.push(tagObj) }) return tagRefs }

Creating Tag Groups

A Tag Group is a tag that contains other tags. Omni Automation is useful for quickly creating one or more tag groups. In the example below, tag groups are created for regions of the United States, and the appropriate states are added to their respective region tag group.

var regions = ["Northeast", "Southeast", "Midwest", "Southwest", "West"] var stateGroups = [["Maine", "Massachusetts", "Rhode Island", "Connecticut", "New Hampshire", "Vermont", "New York", "Pennsylvania", "New Jersey", "Delaware", "Maryland"],["West Virginia", "Virginia", "Kentucky", "Tennessee", "North Carolina", "South Carolina", "Georgia", "Alabama", "Mississippi", "Arkansas", "Louisiana", "Florida"],["Ohio", "Indiana", "Michigan", "Illinois", "Missouri", "Wisconsin", "Minnesota", "Iowa", "Kansas", "Nebraska", "South Dakota", "North Dakota"],["Texas", "Oklahoma", "New Mexico", "Arizona"],["Colorado", "Wyoming", "Montana", "Idaho", "Washington", "Oregon", "Utah", "Nevada", "California", "Alaska", "Hawaii"]] regions.forEach((region, index)=>{ tagGroup = tagNamed(region) || new Tag(region) states = stateGroups[index] states.forEach((state)=>{ tagGroup.tagNamed(state) || new Tag(state, tagGroup.ending) }) })
omnifocus://localhost/omnijs-run?script=try%7Bvar%20regions%20%3D%20%5B%22Northeast%22%2C%20%22Southeast%22%2C%20%22Midwest%22%2C%20%22Southwest%22%2C%20%22West%22%5D%0A%0Avar%20stateGroups%20%3D%20%5B%5B%22Maine%22%2C%20%22Massachusetts%22%2C%20%22Rhode%20Island%22%2C%20%22Connecticut%22%2C%20%22New%20Hampshire%22%2C%20%22Vermont%22%2C%20%22New%20York%22%2C%20%22Pennsylvania%22%2C%20%22New%20Jersey%22%2C%20%22Delaware%22%2C%20%22Maryland%22%5D%2C%5B%22West%20Virginia%22%2C%20%22Virginia%22%2C%20%22Kentucky%22%2C%20%22Tennessee%22%2C%20%22North%20Carolina%22%2C%20%22South%20Carolina%22%2C%20%22Georgia%22%2C%20%22Alabama%22%2C%20%22Mississippi%22%2C%20%22Arkansas%22%2C%20%22Louisiana%22%2C%20%22Florida%22%5D%2C%5B%22Ohio%22%2C%20%22Indiana%22%2C%20%22Michigan%22%2C%20%22Illinois%22%2C%20%22Missouri%22%2C%20%22Wisconsin%22%2C%20%22Minnesota%22%2C%20%22Iowa%22%2C%20%22Kansas%22%2C%20%22Nebraska%22%2C%20%22South%20Dakota%22%2C%20%22North%20Dakota%22%5D%2C%5B%22Texas%22%2C%20%22Oklahoma%22%2C%20%22New%20Mexico%22%2C%20%22Arizona%22%5D%2C%5B%22Colorado%22%2C%20%22Wyoming%22%2C%20%22Montana%22%2C%20%22Idaho%22%2C%20%22Washington%22%2C%20%22Oregon%22%2C%20%22Utah%22%2C%20%22Nevada%22%2C%20%22California%22%2C%20%22Alaska%22%2C%20%22Hawaii%22%5D%5D%0A%0Aregions%2EforEach%28%28region%2C%20index%29%3D%3E%7B%0A%09tagGroup%20%3D%20tagNamed%28region%29%20%7C%7C%20new%20Tag%28region%29%0A%09states%20%3D%20stateGroups%5Bindex%5D%0A%09states%2EforEach%28%28state%29%3D%3E%7B%0A%09%09tagGroup%2EtagNamed%28state%29%20%7C%7C%20new%20Tag%28state%2C%20tagGroup%2Eending%29%0A%09%7D%29%0A%7D%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

 01  An array of names the for tag groups.

 02  An array of arrays containing the content for the tag groups. Note the order of the content array items matches the order of the tag group names.

 05-11  Using a JavaScript forEach() function to iterate the array of the names of the tag groups. Note the passed-in parameters for the function include a group name and the its position (index) in the array of tag group names.

 06  Using a JavaScript logical operator to reference a tag group matching the passed-in name.

 07  Get the corresponding array of tag names to be used for the group items.

 08-10  Iterate the array of tag names and adding a newtag to the group if it doesn’t already exist.

This action plug-in will “adopt” the tags contained in tag groups that are assigned to the selected task or project:

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.adopt-child-tags", "version": "1.2", "description": "This action will assign the child tags of the tag groups of the selected task|project, to the selected task|project.", "label": "Adopt Tags from Tag Groups", "shortLabel": "Adopt Child Tags" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code // selection options: tasks, projects, folders, tags if(selection.tasks[0]){ var task = selection.tasks[0] } else { var task = selection.projects[0].task } task.tags.forEach((tag)=>{ task.addTags(tag.children) }) }); action.validate = function(selection, sender){ // validation code // selection options: tasks, projects, folders, tags return (selection.tasks.length === 1 || selection.projects.length === 1) }; return action; })();

The following action plug-in will select all tags whose name matches the title entered in the form. If the tag is not found, an option to create and reveal it will be offered:

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.tag-check", "description": "This action will select all of the tags sharing the provided title.", "version": "1.4", "label": "Tag Check", "shortLabel": "Tag Check" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // CONSTRUCT THE FORM var inputForm = new Form() // CREATE FORM ELEMENTS: TEXT INPUT, DATE INPUT, AND CHECKBOX textField = new Form.Field.String("tagName", null, null) // ADD THE ELEMENTS TO THE FORM inputForm.addField(textField) // DIALOG PROMPT AND OK BUTTON TITLE let formPrompt = "Enter the title of the tag to check:" let buttonTitle = "Continue" // DISPLAY THE FORM DIALOG formPromise = inputForm.show(formPrompt, buttonTitle) // VALIDATE FORM CONTENT inputForm.validate = function(formObject){ // EXTRACT VALUES FROM THE FORM’S VALUES OBJECT textValue = formObject.values['tagName'] // RETURN VALIDATION BOOLEAN return (!textValue) ? false:true } // PERFORM PROCESSES USING FORM DATA formPromise.then(function(formObject){ try { var targetTagName = formObject.values['tagName'] var targetTags = new Array() tags.apply(function(tag){ if(tag.name === targetTagName){ targetTags.push(tag) } }) if(targetTags.length > 0){ Timer.once(1,function(timer){ // wait for sheet/dialog document.windows[0].perspective = Perspective.BuiltIn.Tags Timer.once(1,function(timer){ // wait for perspective document.windows[0].selectObjects(targetTags) }) }) } else { var alert = new Alert("Tag Not Found", "The tag “" + targetTagName + "” was not found.\n\nDo you want to create it?") alert.addOption("Stop") alert.addOption("Create") alert.show(function(result){ if (result == 0){ console.log("Done") } else { Timer.once(1,function(timer){ // wait for sheet/dialog var tag = new Tag(targetTagName) document.windows[0].perspective = Perspective.BuiltIn.Tags Timer.once(1,function(timer){ // wait for perspective document.windows[0].selectObjects([tag]) }) }) } }) } } catch(err){ console.error(err) } }) // PROCESS FORM CANCELLATION formPromise.catch(function(err){ console.log("form cancelled", err.message) }) }); return action; })();

Deleting Tags

To delete one or more tags from the database, the deleteObject() function of the Database class is used, as in this script that deletes all tags from the database:

tags.forEach((tag) => {deleteObject(tag)})
omnifocus://localhost/omnijs-run?script=try%7Btags%2EforEach%28%28tag%29%20%3D%3E%20%7BdeleteObject%28tag%29%7D%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

And here’s a script that removes duplicate tags from the entire tag heirarchy:

var tagNames = new Array() tags.apply(function(tag){ if (tagNames.includes(tag.name)){ deleteObject(tag) } else { tagNames.push(tag.name) } })
omnifocus://localhost/omnijs-run?script=try%7Bvar%20tagNames%20%3D%20new%20Array%28%29%0Atags%2Eapply%28function%28tag%29%7B%0A%09if%20%28tagNames%2Eincludes%28tag%2Ename%29%29%7B%0A%09%09deleteObject%28tag%29%20%0A%09%7D%20else%20%7B%0A%09%09tagNames%2Epush%28tag%2Ename%29%0A%09%7D%0A%7D%29%7Dcatch%28err%29%7Bconsole%2Elog%28err%29%7D

NOTE: The previous script isn’t designed to examine any task relationships of the tags, it simply removes duplicate tags based upon the order they are encountered.

The following plug-in will delete all tags that have not been associated with items. Parent tags of tags who have been assigned will be retained.

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of-remove-unused-tags", "version": "1.5", "description": "This action will delete all tags that have not been associated with items.", "label": "Delete Unused Tags", "shortLabel": "Delete Tags" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code // selection options: tasks, projects, folders, tags, allObjects var deletionTotal = 0 do { var deletionCount = 0 tags.apply(tag => { // DO NOT REMOVE TAG GROUPS if (tag.children.length === 0){ console.log(tag.name) if (tag.tasks.length === 0){ console.log("Deleting tag: ", tag.name) deleteObject(tag) deletionCount = deletionCount + 1 } } }) deletionTotal = deletionTotal + deletionCount } while (deletionCount != 0); var msg = String(deletionTotal) + " tags were deleted." new Alert("Completed", msg).show() }); action.validate = function(selection, sender){ // validation code // selection options: tasks, projects, folders, tags, allObjects return (tags.length > 0) }; return action; })();

Clearing Tags from Tasks

To remove an assigned tag from its host task, use the removeTag(…) or removeTags(…) functions of the Task class.

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.clear-task-tags", "version": "1.1", "description": "This action will clear all tags assigned to the selected tasks.", "label": "Clear Tags from Tasks", "shortLabel": "Clear Task Tags" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // selection options: tasks, projects, folders, tags selection.tasks.forEach(function(task){ task.removeTags(task.tags) }) }); action.validate = function(selection, sender){ // selection options: tasks, projects, folders, tags return (selection.tasks.length > 0) }; return action; }(); _;
/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.clear-project-tags", "version": "1.1", "description": "This action will clear all tags assigned to the selected projects.", "label": "Clear Tags from Projects", "paletteLabel": "Clear Project Tags" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // selection options: tasks, projects, folders, tags selection.projects.forEach(function(project){ project.task.removeTags(project.task.tags) }) }); action.validate = function(selection, sender){ // selection options: tasks, projects, folders, tags return (selection.projects.length > 0) }; return action; })();

Importing Tags from Text File

An important aspect of the Omni Automation support in OmniFocus is in its ability to import data from sources outside of the application. In the following example action plug-in, the user is prompted to choose a text file whose paragraphs comprise a list of tags to be added to the database. The chosen file is parsed, and each paragraph of the imported text is used to create a new top-level tag if an existing matching one does not exist.

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.import-text-to-tags", "version": "1.2", "description": "This action will convert the paragraphs of the chosen text file into OmniFocus tags.", "label": "Import Text to Tags", "paletteLabel": "Import Tags" }*/ var _ = function(){ var action = new PlugIn.Action(function(selection, sender){ // get the names of existing tags var tagTitles = new Array() tags.apply(tag => tagTitles.push(tag.name)) // show the file picker var picker = new FilePicker() picker.folders = false picker.multiple = false picker.types = [FileType.plainText] pickerPromise = picker.show() // process the results of the file picker pickerPromise.then(function(urlsArray){ aURL = urlsArray[0] aURL.fetch(function(data){ textArray = data.toString().split('\n') var newTagObjs = new Array() textArray.forEach((item)=>{ if(item != "" && !tagTitles.includes(item)){ var tag = new Tag(item) tagTitles.push(item) newTagObjs.push(tag) } }) document.windows[0].perspective = Perspective.BuiltIn.Tags Timer.once(1,function(timer){ document.windows[0].selectObjects(newTagObjs) }) }) }) // if user cancels pickerPromise.catch(function(err){ console.log("picker cancelled", err.message) }) }); action.validate = function(selection, sender){ return true }; return action; }(); _;

 01-10  The plug-in metadata keys and corresponding values.

 11-49  The main function that contains the statements for creating and validating the action object.

 12-42  The action function instantiates a new action object and is passed the current selection object as input. In this action, the selection is not used and instead the user is prompted to choose the file containing the data to import.

 15-16  Use the apply(…) method to generate a list of all tag titles, which be used to ensure that imported tags are not duplicate of the existing tags.

 19-22  Create a new instance of the FilePicker class and set its properties to only allow the user-selection of a single text file.

 23  Use the show() method to display the file picker and return a JavaScript promise to handle the picker results.

 26-37  Process the result of the picker dialog using the then() method whose callback function is passed an array of file URLs representing the files chosen by the user in the file picker dialog.

 27  The passed parameter is an array of file URLs representing the chosen items. In this action, an array containing a single URL is returned.

 28-36  Call the fetch() method of the URL class to extract the data from the chosen file.

 29  Convert the file data to text using the toString() method and call the split(…) function on the resulting string to split the paragraphs into an array of strings.

 30-36  Iterate the arrays of strings to create new tags.

 31-34  Check to see if there is an existing tag matching the string. If not, then create a new tag and add the tag title to teh list of tag titles.

Copy Tags Between Selected Tasks

The following Omni Automaton plug-in uses the addTags(…) function of the Task class to copy the tags of a chosen task to the other tasks selected in the OmniFocus window.

To use, select both the tasks to receive the tags, and the task containing the set of tags to be copied, and run the action.

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.copy-tags-between-selected-tasks", "version": "1.6", "description": "Append the tags of the chosen task to the other selected tasks.", "label": "Copy Tags between Selected Tasks", "shortLabel": "Copy Tags" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // selection options: tasks, projects, folders, tags var selectedTasks = selection.tasks var taskNames = selectedTasks.map(function(task){ return task.name }) var inputForm = new Form() taskNames.forEach(function(taskName,index){ checkboxSwitch = new Form.Field.Checkbox( String(index), taskName, false ) inputForm.addField(checkboxSwitch) }) formPrompt = "Select the single task to copy tags from:" formPromise = inputForm.show(formPrompt,"Continue") inputForm.validate = function(formObject){ resultArray = Object.values(formObject.values) var count = 0; for (var i = 0; i < resultArray.length; i++) { if (resultArray[i] == true){count = count + 1} } return ((count == 1) ? true : false) } formPromise.then(function(formObject){ keys = Object.keys(formObject.values) for (var i = 0; i < keys.length; i++) { k = keys[i] if (formObject.values[k] == true){ var chosenTask = selectedTasks[Number(k)] var chosenTaskName = taskNames[Number(k)] } } str1 = "Copy the tags from “" + chosenTaskName + "” to the other selected tasks?" str2 = "Tags will be added to those tags already applied to the other selected tasks." alertMsg = str1.concat("\n\n",str2) var alert = new Alert("CONFIRMATION:", alertMsg) alert.addOption("Continue") alert.addOption("Cancel") var alertPromise = alert.show() alertPromise.then(function(result){ if (result == 0){ sourceTags = chosenTask.tags selectedTasks.forEach(function(task){ task.addTags(sourceTags) }) } }) }) }); action.validate = function(selection, sender){ // selection options: tasks, projects, folders, tags return (selection.tasks.length > 1) }; return action; })();

Assigning Tags to Selected Tasks

This action will display a list of all tags (sorted), any or all of which may selected to be assigned to the selected tasks. The plug-in is designed to work only on iPadOS and iOS as their form interfaces scroll and allow for the display of dozens of tags.

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.assign-tags-ipados-ios", "version": "1.5", "description": "(iPadOS/iOS) This action will display a list of all tags (sorted), any or all of which may selected to be assigned to the selected tasks.", "label": "Assign Tags", "shortLabel": "Assign Tags" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code // selection options: tasks, projects, folders, tags, allObjects var allTags = flattenedTags if (allTags.length > 1){ allTags.sort((a, b) => { var x = a.name.toLowerCase(); var y = b.name.toLowerCase(); if (x < y) {return -1;} if (x > y) {return 1;} return 0; }) } var menuItems = allTags.map(tag => {return tag.name}) var menuIndexes = new Array() menuItems.forEach((item,index) => {menuIndexes.push(index)}) var menuLabel = "Assign Tags" var multiOptionMenu = new Form.Field.MultipleOptions( "menuKey", menuLabel, menuIndexes, menuItems, [] ) var inputForm = new Form() inputForm.addField(multiOptionMenu) var formPrompt = "Select one or more tags to apply to selected tasks:" var buttonTitle = "Continue" var formPromise = inputForm.show(formPrompt,buttonTitle) inputForm.validate = function(formObject){ var indexes = formObject.values["menuKey"] return (indexes.length > 0)?true:false } formPromise.then(function(formObject){ var indexes = formObject.values["menuKey"] var tags = new Array() indexes.forEach(index => { tags.push(allTags[index]) }) selection.tasks.forEach(function(task){ task.addTags(tags) }) }) 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 ( Device.current.iOS && selection.tasks.length > 0 && tags.length > 0 ) }; return action; })();

Toggle Status of Selected Tag

This plug-in will toggle the status of the selected tag between active and on hold. If the selected tag is a tag group, the status of all of the group tags will changed accordingly.

NOTE: this plug-in uses the sender parameter of the action handlers to toggle the text of the plug-in menu item.

/*{ "type": "action", "targets": ["omnifocus"], "author": "Otto Automator", "identifier": "com.omni-automation.of.toggle-tag-status", "version": "1.0", "description": "This plug-in will toggle the status of the selected tag between active and on hold. If the selected tag is a tag group, the status of all of the group tags will changed accordingly.", "label": "Toggle Tag Status", "shortLabel": "Toggle Tag" }*/ (() => { var action = new PlugIn.Action(function(selection, sender){ // action code // selection options: tasks, projects, folders, tags, allObjects var tag = selection.tags[0] if(tag.status === Tag.Status.Active){ tag.status = Tag.Status.OnHold tag.flattenedTags.forEach(tg => { tg.status = Tag.Status.OnHold }) } else { tag.status = Tag.Status.Active tag.flattenedTags.forEach(tg => { tg.status = Tag.Status.Active }) } }); action.validate = function(selection, sender){ // validation code // selection options: tasks, projects, folders, tags, allObjects // sender can be: MenuItem, ToolbarItem, or undefined (remote script) if (selection.tags.length === 1){ if (sender instanceof MenuItem){ var menuText = (selection.tags[0].status === Tag.Status.Active)?'Set Status to On Hold':'Set Status to Active' sender.label = menuText } return true } if (sender instanceof MenuItem){sender.label = 'Toggle Tag Status'} return false }; return action; })();

DISCLAIMER