Sorting

Here is a first draft of a script for sorting the children of the selected row.

(As always, test carefully on dummy data before deciding that you want to use it on working material)

One could keep two copies:

  • one for AZ ascending sort,
  • one for ZA descending sort.

and these would differ only in the option line near the top, which should read either

const orderZA = false;

(for a vanilla ascending sort)

or:

const orderZA = true;

(to get a descending sort).


Expand disclosure triangle to view JS source
(() => {
    "use strict";

    // First draft of a `Sort children of selected line`
    // for Bike.

    // RobTrew @2022
    // Ver 0.02

    // --------------------- OPTION ----------------------

    // Edit the option value below to `true` if you need a
    // descending sort, or to `false` for ascending.

    const orderZA = false;

    // -------------- SORTED BIKE SIBLINGS ---------------
    // main :: IO ()
    const main = () => {
        const doc = Application("Bike").documents.at(0);

        return doc.exists() ? (() => {
            const
                rows = doc.rows,
                parentRow = rows.where({
                    _and: [
                        {selected: true},
                        {_not: [{
                            name: ""
                        }]}
                    ]
                }).at(0);

            return parentRow.exists() && (
                0 < parentRow.rows.length
            ) ? (() => {
                    const
                        children = parentRow.rows.where(
                            {_not: [{
                                name: ""
                            }]}
                        ),
                        sortOrder = orderZA ? (
                            flip(comparing(fst))
                        ) : comparing(fst);

                    return sortBy(sortOrder)(
                        zip(
                            children.name()
                        )(
                            children.id()
                        )
                    )
                    .map(
                        nameID => (
                            rows.byId(nameID[1]).move({
                                to: parentRow
                            }),
                            nameID[0]
                        )
                    )
                    .join("\n");
                })() : "No row with children selected.";
        })() : "No documents open in Bike";
    };

    // --------------------- GENERIC ---------------------

    // comparing :: Ord a => (b -> a) -> b -> b -> Ordering
    const comparing = f =>
    // The ordering of f(x) and f(y) as a value
    // drawn from {-1, 0, 1}, representing {LT, EQ, GT}.
        x => y => {
            const
                a = f(x),
                b = f(y);

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


    // flip :: (a -> b -> c) -> b -> a -> c
    const flip = op =>
    // The binary function op with
    // its arguments reversed.
        1 !== op.length ? (
            (a, b) => op(b, a)
        ) : (a => b => op(b)(a));


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


    // sortBy :: (a -> a -> Ordering) -> [a] -> [a]
    const sortBy = f =>
    // A copy of xs sorted by the comparator function f.
        xs => xs.slice()
        .sort((a, b) => f(a)(b));


    // zip :: [a] -> [b] -> [(a, b)]
    const zip = xs =>
    // The paired members of xs and ys, up to
    // the length of the shorter of the two lists.
        ys => Array.from({
            length: Math.min(xs.length, ys.length)
        }, (_, i) => [xs[i], ys[i]]);

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

To test in Script Editor, set the language selector at top left to JavaScript.

See: Using Scripts - Bike

2 Likes

Keyboard Maestro versions here:

1 Like

@complexpoint Hmm … this KM macro is not working for me. (All previous ones supplied by you work just fine.) Maybe I’m doing something wrong. When I use the key commands nothing at all seems to happen (and, yes, the macro is activated).

If you’ve got both of those KM macros installed, and both have the same key-binding (as here), then the idea is to get a choice popping up (resolved by tapping A or Z)

If you can’t see a popup waiting for your interaction, then I would just try assigning different keybindings for each of the two macros.

(and/or experiment with the Keyboard Maestro Preferences > Palettes settings for Conflict palettes)

Note, incidentally that you need to be selecting a row in Bike. The sort applies to its immediate children, which will take their descendants with them.

My bad. KM was working fine. The macros were activated but … somehow I managed to deactivate the whole Bike group and did not notice. All fixed and KM macros work perfectly. Sorry about the false alarm!

1 Like

Good to hear :slight_smile:

Thanks for letting me know.

Sorry for digging up this thread.

How to sort selected row directly, not children?

I know I can group rows up and run the script, then ungroup rows… I still want a simple way.

Sorry to be slow – could you expand a bit ?

( I’m not sure what sorting one single row would mean )

Perhaps you want to sort only the highest level rows in the selection,
leaving the children in their existing order ?

Thanks for the quick reply! Sorry, I didn’t make it clear.

If I have a list like this and selected rows b, d, a.
image

I want to sort current level rows in the selection to a, b, d, not them child rows.
image

1 Like

I don’t think that JXA will easily enable us to sort only a selected subset of the children of row e in your diagrams. ( JXA doesn’t fully implement the move destination locations that are defined for AppleScript )

That would leave us with two routes:

  1. AppleScript (workable, but not very scaleable – terribly slow if your selected lists are long)
  2. Extracting the XML for those rows, deriving and inserting a sorted version, before deleting the original copies.

Version two will take me a little more thought, so unless anyone else gets there first, I will try to sketch something this (EU) evening.

1 Like

Thanks. I would find this functionality useful as well. This is essentially the way it works in OmniOutliner.

I still think sorting–defined this way–is fundamental enough that it would be worthwhile adding as a native feature in the future.

Interim report – more success than I expected with JXA where the subset of selected lines are at levels 2 and downwards (i.e. they have a parent row), but I’m hitting a type error with sorting selected subsets of top level rows in this way.

Might try a different route over the weekend.

2 Likes
  1. No rush on my part.
  2. Thanks!
1 Like

I don’t understand JXA, So I simply thought it was just a matter of changing the operation object in the script. Sorry…

And I tried to made a Keyboard Maestro macro in a ‘dirty way’. (befor a clean script comes out…


Sort selected rows A-Z.kmmacros.zip (2.5 KB)

1 Like

:+1: Indenting, sorting, and outdenting a subset does seem like a sensible working solution for the moment.

Shall we wait and see, before reaching for XSLT or XQuery etc, whether @jessegrosjean is thinking about adding any sorting to the menu system ?

1 Like

The native way will be the best!

I’m going to try and dig into this today. Providing a native solution would be easy enough, but it seems like a problem that scripting can’t do this sort of thing. So first approach will when I need to change to allow this to be scripted.

I love the idea and hate the implementation of all automation technologies. They all try to make things easier, and end up making them harder. Grrr! No solution today.

2 Likes

Latest preview adds built in sorting:

2 Likes

Here’s a sketch of how sorting might be done in AppleScript (though using new Sorting command is better now that it exists):

tell application "Bike"
	tell front document
		set coverRows to my coverRows(selection rows)
		set firstCover to first item of coverRows
		set coverContainer to container row of firstCover
		set coverPreviousSibling to prev sibling row of firstCover
		
		repeat with each in reverse of my sortList(coverRows)
			if (coverPreviousSibling is missing value) then
				move each to before first row of coverContainer
			else
				move each to after coverPreviousSibling
			end if
		end repeat
		
	end tell
end tell

on coverRows(theList)
	using terms from application "Bike"
		set theCoverRows to {}
		set theCurrentLevel to null
		repeat with each in theList
			set theLevel to (level of each)
			if theCurrentLevel is null or (level of each) ≤ theCurrentLevel then
				set end of theCoverRows to each
				set theCurrentLevel to level of each
			end if
		end repeat
	end using terms from
	return theCoverRows
end coverRows

on sortList(theList)
	set theIndexList to {}
	set theSortedList to {}
	repeat (length of theList) times
		set theLowItem to null
		repeat with a from 1 to (length of theList)
			if a is not in theIndexList then
				set theCurrentItem to item a of theList
				if theLowItem is null then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				else if name of theCurrentItem comes before name of theLowItem then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				end if
			end if
		end repeat
		set end of theSortedList to theLowItem
		set end of theIndexList to theLowItemIndex
	end repeat
	return theSortedList
end sortList
2 Likes