This blog article is for advanced users of Emacs org-mode and people who are curious how non-trivial todo management can be accomplished using those tools.
Defining dependencies between todo tasks is done via the contribution package
org-depend.el. It offers a nice set of features. However, I do only use a sub-set for my workflows. Therefore, this article is not an exhaustive description of
org-depend.el but it gives ideas on how to use and improve it.
For a one-time workflow, I create the tasks and define dependencies manually. However, when I have to do things multiple times, I tend to use a snippet software which inserts the headings and tasks on a keyboard shortcut. I wrote a German blog article on text snippet tools.
A simple static snippet is for example
TAB which inserts my credit card number. A simple dynamic snippet is
TAB which inserts the current day in ISO format such as
2016-12-17. For a complex workflow as described further down, you'll need a snippet tool which delivers also complex, interactive snippets.
For using simple to very advanced Emacs snippets there is yasnippet. With yasnippet, you are managing your snippets in separate files in directories according to your major modes such as org-mode. Invoking a snippet is done by typing the defined short abbreviation of the snippet (such as
dd) followed by typically the
I was using yasnippet for many years when I found yankpad which is a wrapper for yasnippet. In contrast to yasnippet, yankpad manages snippets in an Org-mode file as separate headings (in contrast to files in directories). Since most of my snippets are for Org-mode, I happily embraced yankpad and switched all of my Org-mode snippets to it. This makes the maintenance more easy because I have the full feature set of org-mode while writing snippets.
With such a capable snippet system, I define complex workflows only once without having to cope with dependence definitions when I create an instance of this workflow. Therefore, for workflows I am using regularly, complexity is put into the snippet template which I am able to benefit instantly.
I love going to cabaret events with my friends. To minimize the organization effort without forgetting something important, I maintain a simple workflow:
- Basic information on the event: artist, date, short description, ...
- Make reservations for the tickets
- Send out an email to my friends asking who is joining
- Write down everyone who is joining us
- Send out a reminder email shortly before the event so no one forgets
- Create an org-mode event for the event evening itself
Let's assume, I stumbled over an news paper article of one of my favorite artists coming to town.
I switch to my org-mode file which contains such events. I invoke the keyboard sequence to insert a yankpad snippet and then choose the snippet for cabaret. Emacs asks me the day of the event (using a date picker), the name of the artist, the name of the cabaret program, the location (choosing from a pre-defined list of venues), how many seats I am going to reserve, the date of the first and second email.
My yankpad snippet looks like below. You also need my simple wrapper functions for asking and inserting stuff:
my-capture-insert which can be found in my Emacs setup.
\** Cabaret `(my-capture-prompt "date of event" 'my-event-date)`: `(my-capture-prompt "artist" 'my-artist)` :PROPERTIES: :ID: `(my-capture-insert 'my-event-date)`-cabaret :END: - Title: `(my-capture-prompt "title" 'my-title)` - `(my-capture-prompt "Num of seats" 'my-num-seats)` seats reserved: - 2: My girlfriend and I - 2: - 2: - 2: \*** WAITING Make reservation for `(my-capture-insert 'my-num-seats)` seats :PROPERTIES: :ID: `(my-capture-insert 'my-event-date)`-reservation :TRIGGER: `(my-capture-insert 'my-event-date)`-offer-seats(TODO) `(my-capture-insert 'my-event-date)`-reminder-email(TODO) :END: \*** Email: offer `(my-capture-insert 'my-num-seats)`-2 seats SCHEDULED: <`(my-capture-prompt "date 1st email" 'my-email-date)`> :PROPERTIES: :ID: `(my-capture-insert 'my-event-date)`-offer-seats :END: Email template: #+BEGIN_QUOTE Cabaret: `(my-capture-insert 'my-artist)` on `(my-capture-insert 'my-event-date)` Hi friends! Who: `(my-capture-insert 'my-artist)` What: "`(my-capture-insert 'my-title)`" When: `(my-capture-insert 'my-event-date)` 19:15 Where: `(my-capture-selection '("Theatercafé" "Orpheum") 'my-location)` First come, first served. We've got `(my-capture-insert 'my-num-seats)` seats. Karl #+END_QUOTE \*** Send reminder email SCHEDULED: <`(my-capture-prompt "date reminder" 'my-reminder-date)`> :PROPERTIES: :BLOCKER: `(my-capture-insert 'my-event-date)`-offer-seats :ID: `(my-capture-insert 'my-event-date)`-reminder-email :END: \*** `(my-capture-insert 'my-artist)`: "`(my-capture-insert 'my-title)`" (`(my-capture-insert 'my-location)`) :PROPERTIES: :ID: `(my-capture-insert 'my-event-date)`-cabaret-event :END: <`(my-capture-insert 'my-event-date)` 20:00-23:30>
As you can see, with the
my-capture-promt and the
my-capture-insert functions, it is very easy to re-use for example the date of the event multiple times.
Applying the snippet and creating an instance will result in something like this:
** Cabaret 2017-01-24: Thomas Maurer :PROPERTIES: :ID: 2017-01-24-cabaret :END: - Title: Der Tolerator - 8 seats reserved: - 2: My girlfriend and I - 2: - 2: - 2: *** WAITING Make reservation for 8 seats :PROPERTIES: :ID: 2017-01-24-reservation :TRIGGER: 2017-01-24-offer-seats(TODO) 2017-01-24-reminder-email(TODO) :END: *** Email: offer 8-2 seats SCHEDULED: <2017-01-05> :PROPERTIES: :ID: 2017-01-24-offer-seats :END: Email template: #+BEGIN_QUOTE Cabaret: Thomas Maurer on 2017-01-24 Hi friends! Who: Thomas Maurer What: "Der Tolerator" When: 2017-01-24 19:15 Where: Theatercafé First come, first served. We've got 8 seats. Karl #+END_QUOTE *** Send reminder email SCHEDULED: <2017-01-21> :PROPERTIES: :BLOCKER: 2017-01-24-offer-seats :ID: 2017-01-24-reminder-email :END: *** Thomas Maurer: "Der Tolerator" (Theatercafé) :PROPERTIES: :ID: 2017-01-24-cabaret-event :END: <2017-01-24 20:00-23:30>
Notice that with multiple cabaret events on different dates, the IDs are still unique due to the event date being part of it and all dependencies are pre-defined accordingly.
Once the reservation is acknowledged and its task is marked as done, the two tasks for sending out the emails get their "TODO" status via
To demonstrate a blocking precondition, I added a
:BLOCKER: dependency for the reminder email task which is a bit redundant in this particular example. There is a subtle additional difference with the
:BLOCKER: heading as well: as long as the blocking ID is not marked as done (or canceled), the
:BLOCKER: task does not get on my agenda. This is awesome because I don't see already defined and scheduled tasks as long as the pre-condition is not met. Therefore, I always define
:BLOCKER: dependencies in my workflows in order to keep my agenda not messed up with todos I am not able to do now.
Defining a complex snippet takes time and effort. Although once you have defined a complex snippet for a workflow, the beauty is that a workflow instance can be easily set-up for many times.
The cabaret example is a rather simple one just to demonstrate the basic idea. Much more complex workflows I use are project templates, eBay-purchase workflow, Scrum stories management, and even whole lecture management for an entire term including exam preparation and student grading that consists of dozens of headings.
Additional to the
:BLOCKER: dependencies I was using in my snippet,
org-depend.el offers other features as well. With
chain-siblings(KEYWORD) the next heading gets the status
KEYWORD when the current heading gets marked as done. Then there is
chain-siblings-scheduled which moves on the SCHEDULED date to the next heading as well.
chain-find-next(KEYWORD[,OPTIONS]) helps you finding the "next" heading.
Although those are nifty features, I don't use them because I would need even more elaborate features which I discuss in the following sections.
Since I am a power-user of
org-depend.el and Carsten asked for ideas on improving
org-depend.el I wrote down some possible improvements that would ease my personal digital life.
Some of them are probably solved with a few lines of Elisp code. Unfortunately, I am very bad at coding Elisp myself and thus can't extend Emacs the way I would love to.
First of all, I'd like to see some kind of ID picker when defining
This should work like this: after setting up the task in headings and giving them IDs, I'd like to invoke a "I want to define a dependency"-command. It first asks me what property I want to set:
Then I get asked to select any ID which could be found within the same sub-hierarchy (or even in all files?).
After being asked for the KEYWORD to be set for
:TRIGGER: dependencies (if applicable), the property is added to the current heading accordingly.
This would drastically improve creating dependency definitions and prevent typing errors in the first place.
So far, I define
:ID: properties manually. There are settings that result in random IDs set for any new heading. I don't like random ID numbers because I would like to get a hint what heading this might be when I see it.
Usually, my IDs start with the current ISO day to enforce uniqueness and look like this:
|Schedule a meeting with Bob||2016-12-18-schedule-meeting-bob|
|Add additional URLs to lecture notes||2016-12-18-add-URLs-to-lecture|
Wouldn't it be nice when there is a command which takes the current heading title and auto-generates the ID property accordingly? I guess this is not that hard to do:
|Schedule a meeting with Bob||2016-12-18-Schedule-a-meeting-with-Bob|
|Add additional URLs to lecture notes||2016-12-18-Add-additional-URLs-to-lecture-notes|
This is an idea that Christophe Schockaert wrote on the mailinglist: Why not having an assistant which does multiple things at once?
Besides that, I wonder if/how we could automate the following course of actions:
- let have point on an entry
- create a new "TODO-like" entry as a link to that entry
- assign an ID to both entries: lets say "ID-original" and "ID-duplicate"
- in the new entry: define a BLOCKER property set on "ID-original"
- in the original entry: define a TRIGGER property set as ID-duplicate(DONE)
At first sight:
- the new entry could be created besides the original or in a file where it is ready to refile
- the TODO state in the new entry could be set with a default, I think it is so easy to switch afterwards with Org keystrokes
- the triggered state might better be a parameter (possibly a customized default as "TODO"): otherwise, it would be necessary to go inside the drawer to change it
Currently, I am doing all this manually, quite often. [...]
I can copy that: this is a very common set of operations which are done together. However, I personally would like to have the previously mentioned functions above in addition to this assistant.
I love the
:TRIGGER: property because I can mark headings as open tasks only if they can be done now. Only headings which are ready to be looked at do have the
One limitation of
org-depend.el is that I am only to move forward scheduled dates to siblings and I am not able to define a different scheduled date.
Assume following syntax:
** TODO Asking the client about the project :PROPERTIES: :TRIGGER: 2016-12-18-send-offer(TODO,2016-12-23) :END: ** Send offer to client :PROPERTIES: :ID: 2016-12-18-send-offer :END:
I extended the option of the trigger property so that I added an ISO date to the keyword parameter.
What I'd expect is that on finishing the first task, the heading with the ID
2016-12-18-send-offer not only gets the keyword
TODO but also is scheduled for 2016-12-23 as well.
Notice that the send-offer heading is not necessarily located in the same sub-hierarchy as the ask-client heading. Therefore, sibling-operations are not the whole answer here.
Additional to this, I'd like to have the possibility to define relative schedule dates as stated in manual for the date prompt:
||the day when marking the asking-task as done|
||3 days after the scheduled date of the asking-task|
||3 days from the day when marking the asking-task as done|
||nearest Monday from the day when marking the asking-task as done|
||second Tuesday from the day when marking the asking-task as done|
Wouldn't it be nice to have a general setting (or a property?) whether or not I want to handle canceled tasks differently as tasks marked as done?
Imagine the example from above. Does it really make sense to send an offer when I canceled the ask-client task? Many people probably would love to cancel all follow-up workflow tasks as well.
While most people do not need advanced workflow management, such as dependencies between tasks, I do love this Org-mode feature. It was the reason I started with Org-mode in the first place. I love that my agenda only shows tasks which can be done now and whose dependencies are already met.
So even when you did not feel the urge to define your workflows with a snippet/template system you might enjoyed this article. Maybe you are going to start defining simple workflows as well.
I'd love to read your comments on snippets, workflows, dependencies and such: write me an email or commend via Disqus (see below).