Deprecated: Return type of I::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 62

Deprecated: Return type of I::next() should either be compatible with Iterator::next(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 91

Deprecated: Return type of I::key() should either be compatible with Iterator::key(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 71

Deprecated: Return type of I::valid() should either be compatible with Iterator::valid(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 101

Deprecated: Return type of I::rewind() should either be compatible with Iterator::rewind(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 53

Deprecated: Return type of Collection::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/collection.php on line 80

Deprecated: parse_str(): Passing null to parameter #1 ($string) of type string is deprecated in /home/public/kirby/toolkit/lib/url.php on line 135
One Tap Less | Searching OmniFocus with Launchbar — Part 1

Searching OmniFocus with Launchbar — Part 1


Deprecated: trim(): Passing null to parameter #1 ($string) of type string is deprecated in /home/public/kirby/toolkit/lib/str.php on line 506

Deprecated: trim(): Passing null to parameter #1 ($string) of type string is deprecated in /home/public/kirby/toolkit/lib/str.php on line 506

Deprecated: trim(): Passing null to parameter #1 ($string) of type string is deprecated in /home/public/kirby/toolkit/lib/str.php on line 506

I was reluctant and lingered until I embraced OmniFocus as my panacea for task management; I was afraid it would overwhelm me with features and stifle my productivity with hoaxes, but that hasn't been the case. Launchbar has been my loyal companion since I began fiddling with automation on my Mac. We'll mingle both in this article: we'll search through all our tasks from Launchbar and learn some Javascript for Automation along the way.

Getting Started

Let's get something out of the way: this may be a hefty investment as you need the Pro version of OmniFocus to run JXA and that inflates the price tag for the Mac app to $80, then you probably want the whole suite and that will be more $40 for the standard iOS version or $60 for the Pro. Launchbar is almost an impulse buy in comparison at measly €24 (around $26). If you're still reading, I assume you're as enthusiastic for OmniFocus as myself, so avanti.

If you didn't read Alex Guyot's introductory piece on MacStories, you should. I also suggest you watch the WWDC Session Video—Look for the Javascript for Automation title.

After mastering the subject, open Script Editor and let's begin right after you stop cringing. We'll start with a very simple script to show all our available tasks1. First we must get OmniFocus with the Application global property and store it in a variable for further use:

var of = Application('OmniFocus');

Since I don't intent to dictate a script to you, let's get versed in the dictionaries, not the spelling kind, and much more verbose. Hit ⇧⌘O and select OmniFocus on the dialog and look for the Application object from the OmniFocus Suite.

OmniFocus dictionary for Javascript for Automation.
It's like Stack Overflow: you'll spend more time here than actually coding.

Now you have access to the properties contained by the object, we're looking for the defaultDocument property, so let's update our script and assign another variable to stash this value:

var of = Application('OmniFocus');
var doc = of.defaultDocument;

The new variable, doc, is a Document object that contains different properties than the Application object. You can inspect the properties it holds by looking for it in the dictionary, it should be below the description you're currently in, otherwise look for the Document object under the OmniFocus Suite.

There's a lot of cool stuff here that we'll explore later, but I want you to check the flattenedTasks object: it returns all your projects and tasks regardless of the hierarchy, this is yet not what we want since it also returns completed tasks. Also, let's clarify this: the object returns an array of Task objects and, as the documentation describes, these might represent the root of a project, an action within a project or other action or an inbox item, therefore, that's why you also get projects.

We'll filter this array using the special whose method. Don't try looking for it in a JavaScript documentation but let's say it behaves like the filter method, but instead of a function you'll pass another object. The JXA Release Notes have great input on the whose method.

The Task object has a property called completed, it returns a boolean: true if the task is completed and false otherwise. Our script looks like this now:

var of = Application('OmniFocus');
var doc = of.defaultDocument;

function searchTasks() {
    return doc.flattenedTasks.whose({completed: false})();
}

I assigned our action to a function for multiple use, now you can just write searchTasks() at the bottom of the script and hit run.

Verbose results from first function display all tasks in an unfamiliar format.
Full results containing all active tasks. Looks intimidating.

That prolix array contains all your available tasks, but what if we extend our filter to also select only tasks that contain a certain string in the title? As we get into that, let's start building our Launchbar action. Wake up Launchbar with your beloved shortcut and press ⎇⌘E to launch the Action Editor. Press the plus icon in the bottom-left corner to create a new action. Pick a name, let's go with Search Omnifocus for now. For the icon, try com.omnigroup.OmniFocus2, which is the bundle ID for OmniFocus. Fill the remaining requirements and then press the Scripts tab. Select AppleScript as your language of choice and toggle a couple of items on: Requires argument, Accept string argument and Returns result. There's a dropdown for the latter, there pick Item. Click the Edit button to launch Script Editor with a default script.

Launchbar action's configuration in the Action Editor after toggling the indicated checkboxes.
Launchbar Action Editor as supposed to look like after you toggle the right checkboxes.

You'll find out that Launchbar is messing with you: the script is written in AppleScript, not JXA, so we must replace it with the following code:

// LaunchBar Action Script

function handle_string(_string) {
    return [{title: '1 string passed'}, {title: _string}]
}

Don't expect fancy results, this is just a scaffolding so we get on the same page, but you can already save and test this script. Our script will actually look like this:

// LaunchBar Action Script

var of = Application('OmniFocus');
var doc = of.defaultDocument;

function searchTasks(_query) {
    return doc.flattenedTasks.whose({_and: [
        { name: { _contains: _query } },
        { completed: false }
    ]})();
};

function handle_string(_string) {
    return searchTasks(_string).map( function(el) { return { title: el.name() } })
} 

If you try this script now it will return the title of every task that contains your query, which isn't often helpful. What if the script opened the selected task? Every Omnifocus' task has a link; to test yourself, right-click on any task and select Copy as Link, the link in your clipboard will match omnifocus:///task/id. Launchbar can attach URLs to items and open them on selection. So we'll just change our handle_string function to this:

function handle_string(_string) {
    return searchTasks(_string).map( function(el) { 
        return { 
            title : el.name(),
            url : "omnifocus:///task/" + el.id()
        } })
}

You can download the raw action here and you're welcome to experiment. We're going to tweak and improve this action in the upcoming weeks.

What's Next

In the next article about controlling OmniFocus from Launchbar, we're going to add icons to our tasks according to its category and start building an action to navigate through your projects so you won't even need to open OmniFocus to complete tasks. I hope to see you then.


  1. Fair warning: a project also counts as a task since you can mark it as completed.