Tinderbox Forum

JXA: need help with a few things

It may be that I just haven’t been a great mover of things and haven’t properly explored.

Anything that is possible in AppleScript will be possible in JS - any AppleScripters here who have experimented with note moving ?

Conceivable I think, that the move method of the osascript interface to Tbx8 is not yet fully implemented.

Putting JS aside for the moment, if I write this AppleScript for OmniOutliner, for example:

tell application "OmniOutliner"
    tell front document
        set oRow to make new row with properties {topic:"Epsilon"}
        move oRow to before first row
    end tell
end tell

The new “Epsilon” row becomes the first child of the document.

If, however, I write the same code for Tinderbox 8, the new note becomes the child, rather than preceding sibling, of the first existing note in the document.

tell application "Tinderbox 8"
    tell front document
        set oNote to make new note with properties {name:"Epsilon"}
        move oNote to before first note
    end tell
end tell

Any thoughts, @eastgate ?

1 Like

Ah, another machine seems to have logged me in, above, with the wrong credentials. Apologies, I will try to delete them.

The question would perhaps also be, what is possible with this “array-like” osascript object? What methods can be applied to it? Is this kind of array or object described anywhere? I could not find anything so far.

The basic reference is this:

https://developer.apple.com/library/archive/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/Articles/OSX10-10.html#//apple_ref/doc/uid/TP40014508-CH109-SW1

‘Element arrays’ section, in particular.

A slightly misleading name, but I think the intention was to adopt idioms that might look familiar to existing users of JS

Not too much information…

For a broader set of overall basics, with JXA and AS in parallel:

https://developer.apple.com/library/archive/documentation/LanguagesUtilities/Conceptual/MacAutomationScriptingGuide/index.html#//apple_ref/doc/uid/TP40016239-CH56-SW1

I created a little Alfred workflow, something rather basic… Perhaps this could be a starting point for further little scripting ideas?

At this time, it does the following: When you type “tb {filename}”, you can open the file, reveal the file in the finder or create an inbox item. If the inbox does not exist, it will be created as a top level note (the last one, because we did not find a way to move the note inside the top level… so this has to be done manually).

What I really like about Alfred, is, that he remembers, how often you opened a file. So if you simply hit “tb t” (as a “t” is in every ."t"bx) , you get your TBX files in the order of how often you use them, given that you opened the files with Alfred, of course. Really nice.

The creation of the text of the note does not work somehow. Somewhere I read, that properties for the scripting does not exist? Yet? Is that right, @eastgate?

So, here is my first draft of the workflow:
tinderbox8.alfredworkflow.zip (607.3 KB)

@ComplexPoint: If you like, you may rewrite the code. I am aware, that it is not elegant at all (Kamikaze noob style :joy:… but I try getting better)

Looks good :slight_smile:

FWIW, another way of checking whether an ‘Inbox’ note already exists is to use a whose/where query.

(() => {
    'use strict';

    const
        tbx = Application('Tinderbox 8'),
        doc = tbx.documents.at(0);

    // inboxFoundOrCreated :: TBX App -> TBX Doc -> TBX Note
    const inboxFoundOrCreated = app => doc => {
        const
            xs = doc.notes.where({
                name: 'Inbox'
            });
        return 0 < xs.length ? ( // Found ?
            xs.at(0)
        ) : (() => { // Or created:
            const inboxNote = new tbx.Note({
                name: 'Inbox'
            });
            return (
                doc.notes.push(inboxNote),
                inboxNote
            );
        })();
    };


    return inboxFoundOrCreated(tbx)(doc).name();
})();

The text property may not be settable in an argument to the Note() constructor, but once a note is created, you should be able to set the text attribute value by writing something like:

oNote.text = 'Some textual value';

Is there a better way to pass arguments into another script?
How would this work in this arrow function?
I tried something like ((argv) => {…, but this did not work

Tell me more ?

Could you show me the whole thing that you were experimenting with, with a quick note on what you are after ?

(I grow old, and showing works better than telling :slight_smile: )

A very basic pattern would be:

(() => {
    'use strict';

    // Function definition

    // dbl :: Int -> Int
    const dbl = x =>
        2 * x;


    // Function call

    return dbl(7);
    // -> 14
})();

but I’m not sure if that’s what you’re looking for.

Ah, perhaps you are thinking of this. See argument passed in last line:

(argv => {
    'use strict';

    // Function definition

    // dbl :: Int -> Int
    const dbl = x =>
        2 * x;


    // Function call

    return dbl(argv);
    // -> 542
    
})(271);

I pass an argument from Alfred to the script with:

function run(argv) {...,

where I pass the document name and I wanted to know, how this would work with your arrow function.

Small functions can be declared inside the big functions that use them, so for example:

function run(argv) {
    'use strict';

    // MAIN FUNCTION, CALLED AT FOOT OF RUN()
    // (after the definition of any utility functions which it needs)

    // main :: () -> IO String
    const main = () => {
        const
            tbx = Application('Tinderbox 8'),
            ds = tbx.documents,
            doc = 0 < ds.length ? (
                ds.at(0)
            ) : undefined;

        if (undefined !== doc) {
            tbx.activate();
            const inbox = inboxFoundOrCreated(tbx)(doc);

            return inbox.name();
        } else {
            return "No Tinderbox 8 docs open."
        }
    };


    // UTILITY FUNCTIONS -------------------------

    // inboxFoundOrCreated :: TBX App -> TBX Doc -> TBX Note
    const inboxFoundOrCreated = app => doc => {
        const
            xs = doc.notes.where({
                name: 'Inbox'
            });
        return 0 < xs.length ? ( // Found ?
            xs.at(0)
        ) : (() => { // Or created:
            const inboxNote = new app.Note({
                name: 'Inbox'
            });
            return (
                doc.notes.push(inboxNote),
                inboxNote
            );
        })();
    };

    return main();
}

Ok… I’ll have to digest a bit, then I’ll be back again :wink:

Another question: Is there a debugger for this kind of programming? That would make things a bit easier to comprehend. Although it’s getting clearer already…

1 Like

The Safari JS debugger is good.

https://developer.apple.com/library/archive/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/Articles/OSX10-11.html

1 Like

Another example of the basic pattern:

  1. Define main(),
  2. define any functions that main uses,
  3. call main() after these definitions.
(() => {
    'use strict';
    
    const main = () => 
        take(18, cycle([1, 2, 3]))    
        
    // ->
    // [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
        
        
    // GENERIC ----------------------------------------------------

    // cycle :: [a] -> Generator [a]
    function* cycle(xs) {
        const lng = xs.length;
        let i = 0;
        while (true) {
            yield(xs[i])
            i = (1 + i) % lng;
        }
    }
    
    
    // take :: Int -> [a] -> [a]
    // take :: Int -> String -> String
    const take = (n, xs) =>
        'GeneratorFunction' !== xs.constructor.constructor.name ? (
            xs.slice(0, n)
        ) : [].concat.apply([], Array.from({
            length: n
        }, () => {
            const x = xs.next();
            return x.done ? [] : [x.value];
        }));
        
        
    // MAIN CALL --------------------------------------------------
    return main();
    
})();

I’m a bit Alfred fan myself, although (seems like I’ve heard this before) I use it at a very elementary level.

So, given the chronic shortage of cycles, I’d be especially interested in Alfred integration.

1 Like

What about the creation of text and other attributes via osascript, @eastgate ? Is this coming anytime in the future or am I missing anything?

We can read and write the values of attributes through the attributes collection of a note object:

See the the commented lines 31 and 35 here:

(() => {
    'use strict';

    const main = () => {
        const
            appTbx = Application('Tinderbox'),
            ds = appTbx.documents;

        return either(
            // EITHER AN EXPLANATORY DIALOG WITH DETAILS,
            msg => alert('No selected note to update.')(msg),
            // OR AN UPDATED $TEXT VALUE.
            x => x,
            bindLR(
                bindLR(
                    0 < ds.length ? (
                        Right(ds.at(0))
                    ) : Left('No documents open.'),
                    frontDoc => {
                        const selns = frontDoc.selections;
                        return 0 < selns.length ? (
                            Right(selns.at(0))
                        ) : Left('Nothing selected in front document.')
                    }
                ),
                selectedNote => {
                    const textAttribute = selectedNote.attributes.byName(
                        'Text'
                    );

                    // Effect ($Text value read and updated)
                    textAttribute.value = 'EXTRA LINE ADDED AT TOP\n' +
                        textAttribute.value();

                    // Return value (new Text value)
                    return Right(textAttribute.value());
                }
            )
        );
    };

    // JXA ------------------------------------------------

    // alert :: String -> String -> IO String
    const alert = title => s => {
        const
            sa = Object.assign(Application('System Events'), {
                includeStandardAdditions: true
            });
        return (
            sa.activate(),
            sa.displayDialog(s, {
                withTitle: title,
                buttons: ['OK'],
                defaultButton: 'OK'
            }),
            s
        );
    };

    // GENERIC FUNCTIONS ----------------------------------
    // https://github.com/RobTrew/prelude-jxa

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: 'Either',
        Right: x
    });

    // bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
    const bindLR = (m, mf) =>
        undefined !== m.Left ? (
            m
        ) : mf(m.Right);

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = (fl, fr, e) =>
        'Either' === e.type ? (
            undefined !== e.Left ? (
                fl(e.Left)
            ) : fr(e.Right)
        ) : undefined;

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