How to sort display order of tasks using javascript

I have a tag - “Today”. That tag, say has 20 tasks. When that tag is open in a window, I can manually sort the order of tasks, and when I switch to say another tag and come back to “Today” tag, the manual resort that I did, is retained. So Omnifocus has a way to know the order to display the tasks in.

What I would like to do is to use javascript to change the order of the tasks.

This is what I have so far. And this code does not work.

(() => {
    const omniFocus = Application("OmniFocus");
    omniFocus.includeStandardAdditions = true;


    const windows = omniFocus.windows();
    const firstWindow = windows[0];
    const content = firstWindow.content;
    const trees = content.trees;
    const leaves = trees[0].leaves;

    // at this point, I need to
    // change order of tasks based on estimated minutes of the task
    // so the lowest estimated minutes should show up as the first task

    // and this approach below does not work to change the order
    l0 = leaves[0];
    l1 = leaves[1];

    leaves[0] = l1;
    leaves[1] = l0;

})();

Question: What attribute of task determines the display order in the window showing the tasks in the tag “Today”? I would like to change that attribute.

And as I ask this question, I figure that the attribute that I need to change is to change the order of the leaves. Not sure how I can do it in JXA.

Can somebody help? And thank you! :)

What you are trying to do isn’t currently possible with JXA (nor OmniJS), I think. I’ll look into AppleScript.

@unlocked2412 thank you for the reply. Been researching and was hoping that somebody would reply.

So just to confirm, you are saying that there is no way to reorder tasks in a project or tag, unless one does a manual drag and drop.

It is ok, I will work around what I have right now - which is a Today perspective that picks up tasks with “today” tag and in that perspective tasks are sorted by estimateMinutes.

The problem is that I cannot freely add to the Today perspective. I have to add the task in QuickEntry or somewhere else, and add the Today tag which task is then picked up by the Today perspective.

You can certainly reorder the tasks in a project. If you want, for example, to sort top-level selected tasks in a certain project by name…

OmniJS source:

(() => {
    'use strict';

    // OMNI JS CODE ---------------------------------------
    const omniJSContext = () => {
        // main :: IO ()
        const main = () => {
            const
                win = document.windows[0],
                ts = win.selection.tasks,
                parent = ts[0].parent

            return moveTasks(
                sortOn(x => x.name)(ts),
                parent.beginning
            )
        };
        
        // GENERICS ----------------------------------------------------------------
        // https://github.com/RobTrew/prelude-jxa
        // JS Prelude --------------------------------------------------
        // Tuple (,) :: a -> b -> (a, b)
        const Tuple = a =>
            b => ({
                type: "Tuple",
                "0": a,
                "1": b,
                length: 2
            });

        // comparing :: (a -> b) -> (a -> a -> Ordering)
        const comparing = f =>
            x => y => {
                const
                    a = f(x),
                    b = f(y);

                return a < b ? -1 : (a > b ? 1 : 0);
            };

        // cycle :: [a] -> Generator [a]
        const cycle = function* (xs) {
            // An infinite repetition of xs,
            // from which an arbitrary prefix
            // may be taken.
            const lng = xs.length;
            let i = 0;

            while (true) {
                yield xs[i];
                i = (1 + i) % lng;
            }
        };

        // fst :: (a, b) -> a
        const fst = tpl =>
            // First member of a pair.
            tpl[0];

        // length :: [a] -> Int
        const length = xs =>
            // Returns Infinity over objects without finite
            // length. This enables zip and zipWith to choose
            // the shorter argument when one is non-finite,
            // like cycle, repeat etc
            "GeneratorFunction" !== xs.constructor
            .constructor.name ? (
                xs.length
            ) : Infinity;

        // list :: StringOrArrayLike b => b -> [a]
        const list = xs =>
            // xs itself, if it is an Array,
            // or an Array derived from xs.
            Array.isArray(xs) ? (
                xs
            ) : Array.from(xs || []);

        // member :: Key -> Dict -> Bool
        const member = k =>
            // True if dict contains the key k.
            dict => k in dict;

        // on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
        const on = f =>
            // e.g. groupBy(on(eq)(length))
            g => a => b => f(g(a))(g(b));

        // or :: [Bool] -> Bool
        const or = xs =>
            xs.some(Boolean);

        // repeat :: a -> Generator [a]
        const repeat = function* (x) {
            while (true) {
                yield x;
            }
        };

        // snd :: (a, b) -> b
        const snd = tpl =>
            // Second member of a pair.
            tpl[1];

        // sort :: Ord a => [a] -> [a]
        const sort = xs =>
            // An A-Z sorted copy of xs.
            list(xs).slice()
            .sort((a, b) => a < b ? -1 : (a > b ? 1 : 0));

        // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
        const sortBy = f =>
            xs => list(xs).slice()
            .sort((a, b) => f(a)(b));

        // sortOn :: Ord b => (a -> b) -> [a] -> [a]
        const sortOn = f =>
            // Equivalent to sortBy(comparing(f)), but with f(x)
            // evaluated only once for each x in xs.
            // ('Schwartzian' decorate-sort-undecorate).
            xs => xs.map(
                x => Tuple(f(x))(x)
            )
            .sort(uncurry(comparing(fst)))
            .map(snd);

        // uncurry :: (a -> b -> c) -> ((a, b) -> c)
        const uncurry = f =>
            // A function over a pair, derived
            // from a curried function.
            (...args) => {
                const
                    xy = Boolean(args.length % 2) ? (
                        args[0]
                    ) : args;

                return f(xy[0])(xy[1]);
            };

        return main();
    };


    // OmniJS Context Evaluation ------------------------------------------------
    return 0 < Application('OmniFocus').documents.length ? (
        Application('OmniFocus').evaluateJavascript(
            `(${omniJSContext})()`
        )
    ) : 'No documents open in OmniFocus.'
})();

Are you trying to directly add a task to the today perspective via Quick Entry ? Why can’t you simply select the tag in the Quick Entry panel ? Perhaps, I am missing your point.

Thank you. That is useful to know. I wonder if similar technique can also be applied to tasks in a tag?

Am unfamiliar with Omni Automation - sounds like what you are using is not JXA but Omni Automation. And sounds like moveTasks will only move relative to a project?


I am talking about writing tasks when in a project view or tags view - one can write a task, and hit enter, and write another task.
In Quick entry, have to tag every task with Today for it to show up in Today perspective, and every task needs to have the right estimatedMinutes for it to fit right in the order in my Today perspective where tasks are ordered by estimatedMinutes.
In Tags or Project view, I could select the third task, hit enter and start typing what would now be a fourth and then a fifth task in that Project or Tag.


And finally, your coding style - so cool. Have you by chance created a YouTube video explaining some of your videos or written up some post somewhere sharing how you structure your code in your mind? The coding style appears that you are able to spin up scripts in a hurry and significantly reuse your code. Fascinating.

I do know that this is functional programming, and you had shared some links last time to study but I could not quite complete the study. I know some basics about currying etc now but code is still hard to follow and harder to conceive of if I were to even follow.

I think that is currently planned (OmniAutomation interface).

I am calling OmniAutomation code from JXA.

Ok, I see what you are trying to do. Could you test this script ? As a proof of concept, does it solve this problem ?

(() => {
    'use strict';

    // jxaContext :: IO ()
    const jxaContext = () => {
        // main :: IO ()
        const main = () => {
            const
                se = Application('System Events'),
                of = Application('OmniFocus'),
                doc = of .defaultDocument(),
                oWin = doc.documentWindows[0],
                qe = of .quickEntry,
                oTask = of .InboxTask({
                    primaryTag: doc.flattenedTags.byName("today")
                })

            qe.open()
            qe.inboxTasks.push(oTask)
            treeWithId(qe, oTask.id()).selected = true
            se.keystroke("e")
            return oTask
        };

        // FUNCTIONS ---
        // treeWithId :: Qe -> Strid -> OF Tree
        const treeWithId = (qe, strID) =>
            qe.trees.whose({
                id: strID
            })()[0];


        // MAIN --
        return main();
    };

    return jxaContext();
})();

Launching Quick Entry with “today” tag applied.

1 Like

Perhaps I can do a little book to explain in the future ? I am not sure.

I appreciate your kind words. I use the library created by the amazing Robin Trew. You can find it here:

Good for you. That’s certainly an important concept.

Thank you @unlocked2412. I will work on this. Right thing for me to do is to also understand how the OmniAutomation part works, and of course to understand your script structure so that if I want to make minor changes, I can.

My vote for the book. Or a short post here or on a blog. I think this style of programming is so little used, but appears so powerful.

My latest script is JXA (not OmniAutomation). Did that proved useful to solve your problem, or were you referring to something else ?

I will consider it. In the meantime, just ask here if you find something unfamiliar.

@unlocked2412 thank you for helping and for your time. I have not been able to get to this as got tied up with some work. Will get to it.

The inbox script will definitely be useful in launching an inbox that has Today already set as tag.

OmniFocus is so powerful. But even with all its power, there are some simple things that become difficult.

For example, for Today tasks, have created a perspective that shows tasks with “Today” tag, and sorted by estimated time to mimic priority.
However, adding inline tasks on that perspective page is obviously not possible. Understandable because perspective view is synthetically created.
Need to use Quick Entry window to add tasks with Today tag and the right order estimated time to add the task to the perspective. Can be done.

In the original post on this thread, I was trying to see if the tasks in “Today” tag view can mirror the order in the “Today” perspective - with the perspective tasks always ordered by estimated minutes. So, a script to reorder tasks in “Today” view to mirror the order in “Today” Perspective view, and another script to change estimated minutes to reflect the manually changed order in “Today” tag view so that “Today” perspective can reflect what the manually changed “Today” tag view has.

Hope this made sense.

Are you using the estimated time field like “manual sorting” ? Why don’t you simply sort by Tag and arrange the tasks in the order you want ?

I am still not sure that I understand your workflow.

OmniFocus content: So I have a number of mostly parallel projects and some lists and tasks within those projects that I have in my OmniFocus system.

Objective: I would like to have a list of tasks that I want to work on today. In a priority order preferably.

Current solution: The way I am achieving that (sort of) is by way of a “Do Today” perspective that picks up tasks that are tagged with “Today” tag, or flagged, or tasks that have a due date.

Perspective conditions

To have tasks show up in “Do Today” perspective, I

  • add “Today” tag to tasks and “estimated time” to determine its display order in the “Do Today” perspective
  • And if I want to work on a task after 5 days, then I add the Today tag to that task and add a defer date

The original question at the top of this thread: The question at the top of the thread was meant to ask if I could reorder tasks in “Today” tag view to reflect the order in the “Do Today” perspective. I do currently have a script that increases the estimated time (lowers the priority of the task) with a keyboard shortcut (using Keyboard Maestro) and JXA script. And another to increase the priority. The scripts are cobbled together based on what I found here and help here on the forum.


Any comments on the above process are welcome.

The sorting by tag may not work as well as the perspective because the perspective picks up the Due tasks as well, although may be I should consider a script that regularly adds “Today” tag to the Due tasks. But even then, I would like a keyboard shortcut to move tasks up one level and down one level and that would require a scriptable way to change order of tasks in a “Tag” view.

I hope the above makes more sense.

Thank you for reading.