In hindsight, I saw exactly how it happened. I had accidentally checked off the reminder to pay next month’s rent, thinking I just hadn’t checked off paying this month’s rent. I had, but the repeating task was just an indistinguishable “Pay rent”. I needed a better way to track the task.
OmniFocus allows you to create repeating tasks and projects, but sometimes you need a little more flexibility:
- Every recurrence looks exactly the same. You can lose track of which occurrence is which. And for projects that repeat frequently, it can start to feel monotonous, like you aren’t making any progress.
- Repeating projects add clutter to OmniFocus. Even if they have a start date, they still show up, and you end up training yourself to ignore them, or even resent them.
- The structure of every recurrence is exactly the same. You have to remember anything you need to add to (or remove from) the project, increasing the chance you’ll overlook something.
- Projects can only repeat at fixed intervals. This makes them difficult to use for projects that are started in response to an external event, like the paperwork when a member of the congregation has a baby.
- Only one instance can be active at a time. If you need to have copies that overlap, you need another way to create them.
You could forgo repeating projects altogether and just enter them by hand, but that will take a lot more work in the long-term. Instead, let’s create an AppleScript to create the project for us.
Before we get started, you should check out Chris Sauve’s Templates.scpt and see if it will fit your needs. It doesn’t require any scripting on your part, but it doesn’t solve all the problems listed above. Mainly, I didn’t like the idea of having the templates hanging around in OmniFocus, so I spent an evening figuring out how to do this through pure AppleScript.
Paying Rent
Let’s start with a simple project for paying rent. The project has two tasks:
- Write out rent check
- Drop off rent check
First, I want the project to be clear on which month’s rent, so let’s put the name of the month in the project.
set theMonth to (do shell script "date -v+1m +\"%B\"")
set projectName to "Pay " & theMonth & " Rent"
I don’t want to write out the check too early, so I’m going to start the project when there’s a week left in the month. This gives me a couple days to write out the check and drop it off before OmniFocus starts warning me about the due project. I’m going to make the project due at close of business on the last day of the month.
set weekBefore to date (do shell script "date -v+1m -v1d -v-1w +\"%B %d, %Y\"")
set weekBefore's hours to 6
set dayBefore to date (do shell script "date -v+1m -v1d -v-1d +\"%B %d, %Y\"")
set dayBefore's hours to 17
Finally, tell OmniFocus to create the project. Tasks are created within a project by telling the project to make a task instead of the document.
tell front document of application "OmniFocus"
set homeContext to first flattened context whose name is "Home"
set theProject to make new project with properties {name:projectName, start date:weekBefore, due date:dayBefore, context:homeContext, sequential:true}
tell theProject
make new task with properties {name:"Write out check"}
make new task with properties {name:"Drop off rent check"}
end tell
end tell
Here’s the full script so you can see the pieces in context:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(* | |
Creates a “Pay Rent” project in OmniFocus. | |
*) | |
set theMonth to (do shell script "date -v+1m +\"%B\"") | |
set projectName to "Pay " & theMonth & " Rent" | |
set weekBefore to date (do shell script "date -v+1m -v1d -v-1w +\"%B %d, %Y\"") | |
set weekBefore's hours to 6 | |
set dayBefore to date (do shell script "date -v+1m -v1d -v-1d +\"%B %d, %Y\"") | |
set dayBefore's hours to 17 | |
tell front document of application "OmniFocus" | |
set homeContext to first flattened context whose name is "Home" | |
set theProject to make new project with properties {name:projectName, start date:weekBefore, due date:dayBefore, context:homeContext, sequential:true} | |
tell theProject | |
make new task with properties {name:"Write out check"} | |
make new task with properties {name:"Drop off rent check"} | |
end tell | |
end tell |
Copy and paste that into a new script in AppleScript Editor (or download a copy here) and try it out. (If you don’t have a “Home” context, be sure to update the script accordingly.) For bonus points, create a LaunchAgent to automatically run the script on the 20th. Towards the end of the month, you’ll have a new, specific project appear to pay next month’s rent.
Sending Greeting Cards
Let’s take a look at a longer script, the script I use to send greeting cards throughout the year. This is the resulting project:
I’m just going to highlight a few pieces of the script, but you can view the full script to see the pieces in context. If you’re eager to try it out, you’ll need to customize a few things first:
- In
createOmniFocusTasksForEventInProject
, change"Family"
,"Home"
, and"Errands"
to a project and two contexts that exist in your OmniFocus document. - If you’re not on the west coast of North America, you should also change
America/Los_Angeles
to your local timezone.
I usually invoke the script via LaunchBar. After placing the script where LaunchBar can find it (I added a search rule for ~/Dropbox/Library/Scripts
), invoke the script via ⌘-space
, cbir
, space
, and type in the details (“Jim’s Birthday on 3/22”).
-- Entry point when run
on run
display dialog "Enter the event for the card:" default answer "Jim’s Birthday on March 22"
createTasksForBirthday(the result's text returned)
end run
-- Entry point when launched with an argument by LaunchBar
on handle_string(inputStr)
my createTasksForBirthday(inputStr)
end handle_string
-- Funnel point
on createTasksForBirthday(inputStr)
set parsedInfo to my parseString(inputStr)
set eventName to my extractEventNameFromUserInput(parsedInfo)
set dueDate to my extractDateFromUserInput(parsedInfo, eventName)
my createTasksForEvent(eventName, dueDate)
end createTasksForBirthday
The on run
handler will be called when the script is run, either by invoking it through LaunchBar without an argument or by running it from Script Editor. The on handle_string
handler will be called when you use LaunchBar to pass an argument, and it’s passed the string you typed in.
I didn’t want these to be actual projects that show up in the sidebar. Instead, they’re complex tasks inside of an ongoing project called “Role—Family”, where I put family-related tasks. This is done by getting a reference to the project and telling it to create tasks.
set proj to first project whose name is "Role — Family"
tell proj
-- Create the parent task and the subtasks inside it
set parentTask to make new task with properties {name:eventName, sequential:true, due date:eventDate}
tell parentTask
make new task with properties {name:"Buy a card for " & eventName, context:errandsContext, due date:eventDate - (21 * days)}
make new task with properties {name:"Sign the card for " & eventName, context:homeContext, due date:eventDate - (11 * days)}
make new task with properties {name:"Prepare " & eventName & " card for mailing", context:homeContext, due date:eventDate - (11 * days)}
make new task with properties {name:"Mail " & eventName & " card", context:errandsContext, due date:eventDate - (10 * days)}
end tell
For most tasks, I prefer to use start dates, but these involve hard deadlines, so I set the due date instead.
The rest of the script is just to parse “Jim’s Birthday on 3/22” into “Jim’s Birthday” and “3/22”. The only interesting bit is converting “3/22” into an AppleScript date:
on parseDate(inputStr)
set cmd to makeDateCommand(inputStr)
set theDate to (do shell script cmd)
return date theDate
end parseDate
on makeDateCommand(inputStr)
set timeZoneName to "America/Los_Angeles" -- See http://www.php.net/manual/en/timezones.php
set timeZoneFix to "date_default_timezone_set('" & timeZoneName & "')"
set quotedInput to quoted form of inputStr
set dateReformat to "echo date('m/d/Y',strtotime(" & quotedInput & "));"
set cmd to "php -r \"" & timeZoneFix & ";" & dateReformat & "\""
return cmd
end makeDateCommand
Writing Your Own Scripts
While this isn’t an introduction to writing AppleScripts, you should be able to take the examples above and start cobbling together your own project template scripts. A few final tips:
- Start with the project you want. Create a sample project in OmniFocus. It’s much easier to tune it and tweak it there. Then start writing your script.
- Replicate your sample project verbatim. Write a script that will recreate the sample project. Then expand it to handle user input and date calculations.
- Let the script work for you. The final script should take as little user input as possible. The Pay Rent script, for example, can be run automatically because it figures out the start date, due date, and name of the month on its own.
- Be creative. You’re working in AppleScript. That puts lots of information at your disposal. Need to create a note in Evernote? Send an email? Extract information from the current page in Safari? You can. Have your entire Mac work together to save you time down the road.
Be sure to also check out the OmniFocus scripting dictionary in AppleScript Editor. It will have more details on how to set every little property on a task.
Just to be clear: You’re free (as in an eagle soaring majestically over the Rocky Mountains of Colorado) to learn from the scripts shared in this post, find ways to improve them, and use them to create your own scripts. If you create something useful, please share it with others.
Question: What projects would you find most useful if you could script them? Share your thoughts in the comments, on Twitter, LinkedIn, or Facebook.