These forums are now read-only. Please visit our new forums to participate in discussion. A new account will be required to post in the new forums. For more info on the switch, see this post. Thank you!

I've been having an awful time trying to obtain all the projects in my omnifocus document in Applescript.

I have a script working now, which I've mentioned in another thread. Basically it iterates over all projects and folders that are selected in the sidebar, and changes their "next review date" to the "last changed date", if the project has been changed since the last "Mark Review" event occured. I run this script once at the beginning of my daily "get clean" review, and it's working great for me. As a sidebar, I've submitted an enhancement request for Omni to make this functionality available out-of-the-box.

The problem is that I have to select all the folders in my sidebar in order to get the script to act on all of my projects. Not a great hardship obviously, but I use this script every day and I'd like to make it more convenient. I want the script to iterate over every single project, without having to select everything in the sidebar. But I haven't been able to figure out how to obtain the set of all projects.

I'm an experienced programmer, but I'll confess sometimes Applescript, and the structures under the hood of OF, really mystify me. Any hints anyone has, to help me get unstuck? Any snippets that'll spell it out?

on run
tell application "OmniFocus"
tell front document
tell document window 1
set selectedTrees to selected trees of sidebar
repeat with aTree in selectedTrees
if class of value of aTree is project then
my processProject(aTree)
else if class of value of aTree is folder then
my processFolder(aTree)
end if
end repeat
end tell
end tell
end tell
end run
on processFolder(theFolderAsTree)
using terms from application "OmniFocus"
repeat with aTree in trees of theFolderAsTree
if class of value of aTree is project then
my processProject(aTree)
else if class of value of aTree is folder then
my processFolder(aTree)
end if
end repeat
end using terms from
end processFolder
on processProject(theProjectAsTree)
using terms from application "OmniFocus"
set aProject to value of theProjectAsTree
set currentDateAtMidnight to date (short date string of (current date))
set oldReviewDate to last review date of aProject
set lastChangedDate to modification date of aProject
set nextReviewDateAtMidnight to next review date of aProject
if nextReviewDateAtMidnight > currentDateAtMidnight then
if oldReviewDate is not equal to lastChangedDate then
set next review date of aProject to lastChangedDate
end if
end if
end using terms from
end processProject

There is currently no direct way to get all the projects. The only way to access all the projects is to recurse over all the sections of the front document. My Verify Next Actions Exist script does the necessary recursion. Feel free to mine it for ideas.

I really should post a script that just builds a list of all the projects. That wouldn't be too difficult. I'll try to throw something together, but no promises on the timing.

I couldn't resist the productive procrastination. Here's a script template for processing every project in OF. I hope people will find it useful as a base for building their own scripts. There are three points of customization.

A property lets you control whether projects inside dropped folders are included.

One handler lets you decide whether or not a given project should be included in the list of projects to be processed based on the project's properties and elements.

Another handler is called with a list of all the matching projects. You can customize this one to loop over the projects and update their properties.

Noticed your comment/suggestion about summing a count - this was on my maybe/someday too, i.e. to have an easy visibility of how many active projects I'm managing, per DA's guideline of 30-100. Think I'll knock this one off tonight! I think I'm going to have several uses for this template. ;-)

I completed my Reset Review Dates script tonight using Curt's template and it works well.

Interestingly enough, the script won't run if I launch it from the scripts menu icon - it only runs when launching it from a toolbar button, or from the Play button in Applescript Editor.

I also create a script that counts my active and onhold projects. Here's the code I put in the handleProjects snippet:

Code:

set activeProjectCount to 0
set onholdProjectCount to 0
repeat with theProject in theMatchedProjects
if status of theProject is active then
set activeProjectCount to activeProjectCount + 1
end if
if status of theProject is on hold then
set onholdProjectCount to onholdProjectCount + 1
end if
end repeat
display dialog ((activeProjectCount as string) & " active projects
" & (onholdProjectCount as string) & " onhold projects")

One more thing I'd like to do here - I'd also like the counts of my Stalled and Pending projects (using the terms from the sidebar filter). But of course, these are not actual statuses for the project, they are derived by OmniFocus. I'm out of time for tonight, but I think Curts's NextActions script would have the logic for the Stalled count... I'll see if I can shoehorn it in later on. (Obviously a simple look at the start date will do it for the Pending.)

Yeah, it is strange. I can't run it from the AppleScript menu or the FastScripts menu. More precisely, I suspect that it is running, but not finishing in a timely manner, so it appeared like it wasn't running it all. This morning after waking my mbp17, when I clicked on the FastScripts menu, my dialog finally showed, displaying the text from an older (i.e. mid-evening) version of the script. FastScripts also showed one sole menu item: "Terminate (or stop) running script". I think it might have been running 2 or 3 instances of the script.

Ah, yes. With a complex database (lots of folders and projects), the necessary recursion can be slow. Looping over the matched projects is also slow.

If you're willing to split your script into multiple separate ones, it would be much faster to use the shouldProjectBeIncluded method to just select a single project kind (e.g., on hold), then just display the count of theMatchedProjects in handleProjects.

With a complex database (lots of folders and projects), the necessary recursion can be slow.

You can get a flattened project list a little faster (and perhaps a bit more simply) by using where queries.

e.g.

Code:

property pfSkipHiddenFolders : false
on run
tell application id "com.omnigroup.OmniFocus"
set oDoc to front document
set lstAllProjects to my ProjectList(oDoc)
end tell
end run
on ProjectList(oParent)
using terms from application "OmniFocus"
tell oParent
set lstProject to (sections where class is project)
-- OR e.g. (sections where class is project and flagged is true)
if pfSkipHiddenFolders then
set lstFolder to (sections where class is folder and hidden is false)
else
set lstFolder to (sections where class is folder)
end if
if length of lstFolder > 0 then
return lstProject & my FolderProjects(lstFolder)
else
return lstProject
end if
end tell
end using terms from
end ProjectList
on FolderProjects(lstFolder)
set lstProject to {}
repeat with oFolder in lstFolder
set lstProject to lstProject & ProjectList(oFolder)
end repeat
return lstProject
end FolderProjects