The Omni Group
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!

Go Back   The Omni Group Forums > OmniFocus > OmniFocus Extras
FAQ Members List Calendar Search Today's Posts Mark Forums Read

 
Acting on all projects using Applescript Thread Tools Search this Thread Display Modes
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?

I had hope that some of the tips and hints in this thread were going to help - didn't get me there though:
http://forums.omnigroup.com/showthre...ight=flattened

Thanks!

Here's my script, which works now, abridged:

Code:
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.
__________________
Cheers,

Curt
 
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.

Code:
(*
	This script is a template for use in scripts that need to process every project in an OmniFocus database.  The template provides stubs for two handlers that script developers can complete to create custom solutions.  Script developers, see the handlers shouldProjectBeIncluded and handleProjects.
	
	At this writing (Feb 10, 2010) there is no command in OmniFocus's dictionary to get all the projects, so this template uses a brute force recursion over the Library tree.
	
	version 0.1, by Curt Clifton
	
	Copyright  2010, Curtis Clifton
	
	Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
	
		 Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
		
		 Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
		
	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
	
	version 0.1: Original release
*)

(* TODO: Change this property to control whether the script looks at projects inside folders that are hidden. The hidden property of a folder seems to correspond to the Dropped setting for a folder in the OmniFocus user interface. *)
property shouldLookInHiddenFolders : false

(*
	Tests whether the given OmniFocus project should be included in the list of projects to be processed.
	
	Developers should customize this handler if they wish to filter out some projects.
*)
on shouldProjectBeIncluded(theProject)
	using terms from application "OmniFocus"
		-- TODO: test theProject and return true to include it or false to exclude it
		-- For example: return theProject is flagged
		return true
		-- -----------------------------------------------------------------------------
	end using terms from
end shouldProjectBeIncluded

(*
	Processes the list of all matched projects.
	
	Developers should customize this handler to process the projects.
*)
on handleProjects(theMatchedProjects)
	using terms from application "OmniFocus"
		-- TODO: replace the following with code to process the projects (perhaps looping over them to process them individually)
		return count of theMatchedProjects
		-- -----------------------------------------------------------------------------
	end using terms from
end handleProjects

(*
	Main entry point.
*)
tell application "OmniFocus"
	tell front document
		set theSections to every section
		set matchedProjects to my accumulateProjects(theSections, {})
		my handleProjects(matchedProjects)
	end tell
end tell

(* 
	Recurses over the given sections of the Library. Accumulates a list of projects that satisfy the 'shouldProjectBeIncluded' handler.

	theSections: a list of folders, projects, and tasks
	accum: the matching projects that have been found so far 
*)
on accumulateProjects(theSections, accum)
	if (theSections is {}) then return accum
	return accumulateProjects(rest of theSections, accumulateProjectsHelper(item 1 of theSections, accum))
end accumulateProjects

(* 
	Recurses over the tree rooted at the given item. Accumulates a list of projects that satisfy the 'shouldProjectBeIncluded' handler.

	theItem: a folder, project, or task
	accum: the matching projects that have been found so far 
*)
on accumulateProjectsHelper(theItem, accum)
	using terms from application "OmniFocus"
		if (class of theItem is project) then
			-- screens the item before accumulating it
			if my shouldProjectBeIncluded(theItem) then
				set end of accum to theItem
			end if
			return accum
		else if (class of theItem is folder) then
			return my accumulateProjectsInFolder(theItem, accum)
		else
			(* The item must be a task. Shouldn't really reach here since we don't recurse into projects or SALs. *)
			error "Script attempted to process inside a project.  This script is supposedly designed to just process projects.  So, it looks like there's a bug.  Please contact the developer.  We apologize for the inconvenience."
		end if
	end using terms from
end accumulateProjectsHelper

(* 
	Recurses over the tree rooted at the given folder. Accumulates a list of projects that satisfy the 'shouldProjectBeIncluded' handler.

	theFolder: a folder
	accum: the items lacking next actions that have been found so far 
*)
on accumulateProjectsInFolder(theFolder, accum)
	using terms from application "OmniFocus"
		if ((not shouldLookInHiddenFolders) and hidden of theFolder) then return accum
		set theChildren to every section of theFolder
		return my accumulateProjects(theChildren, accum)
	end using terms from
end accumulateProjectsInFolder
All the customization points are near the top of the script. Search for "TODO". Feel free to use as you see fit, but please respect the attribution requirement in the license agreement.

Share and enjoy.
__________________
Cheers,

Curt
 
Sweet. Absolutely freakin' sweet.

Can't thank you enough Curt. This is exactly what I needed.

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. ;-)

Thanks again man, cheers.
 
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.)
 
Strange about the script menu. It works from the FastScripts menu for me. I don't have the regular AppleScript menu.
__________________
Cheers,

Curt
 
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.
__________________
Cheers,

Curt
 
Ah, got it - makes sense. Sure, I don't mind splitting it. Thanks again Curt, cheers.
 
Quote:
Originally Posted by curt.clifton View Post
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
--

Last edited by RobTrew; 2010-02-12 at 05:06 AM..
 
 


Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes


Similar Threads
Thread Thread Starter Forum Replies Last Post
Applescript for dependent projects fudster OmniFocus Extras 10 2011-03-17 03:00 PM
Can't Get Through Initial 1st Sync & Now OF is Acting Odd. HappyDude Other WebDAV 4 2009-09-06 03:06 PM
.Mac and file saves acting weird since Leopard... dude OmniOutliner 3 for Mac 0 2008-03-05 09:30 AM
Can Omniplan do this? 10,000 projects / AppleScript isv OmniPlan General 3 2007-07-04 03:00 AM
AppleScript Gurus: Projects -> Notes BwanaZulia OmniFocus 1 for Mac 6 2007-06-21 11:02 AM


All times are GMT -8. The time now is 08:49 AM.


Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2019, vBulletin Solutions, Inc.