Need help returning a value from a Javascript for Automation action

The following action was on another thread on this website where someone was asking how to find the names of the selected cells in a Numbers table. It works perfectly, but it displays the name of the cells in a pop-up window instead of returning the name of the cells to the calling action. I don't need the pop-up, or the beep, or the error strings, and I can see how to remove those things from this action. But I do need the name of the cells (eg, "B4" or "A1:B4") returned. And right now it's not returning anything, even though the final action of this macro ends with a return statement. I did read the entire thread that this came from, but couldn't figure out how to return the result I needed. Does anyone know how to return the results back to the KM action, so that I can save it into a variable or display it?

In case it isn't clear, I want an empty string returned if there is no active selection, rather than a pop-up saying there is no active selection.

Execute a JavaScript For Automation Action (v11.0.3)

Execute a JavaScript For Automation.kmactions (2.8 KB)

You have no return expression at the top level (only inside an IIFE)

Prefixing the IIFE with return should suffice to return its value to Keyboard Maestro.

return (() => {

    // etc etc
   return selRngName;
})()

(Either that, or switch off Modern syntax in the chevron drop-down menu to the left of the text field )


e.g.

Labelling of any active selection in Numbers.kmmacros (4.5 KB)

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

    // main :: IO ()
    const main = () => {
        const doc = Application("Numbers").documents.at(0);

        return either(
            alert("Numbers selection labels")
        )(
            rangeName => rangeName
        )(
            bindLR(
                doc.exists()
                    ? Right(doc.activeSheet)
                    : Left("No document open in Numbers.")
            )(
                sheet => bindLR(
                    sheet.exists()
                        ? Right(sheet.tables())
                        : Left(`No sheet active in ${doc.name()}`)
                )(
                    selectionLabelLR
                )
            )
        );
    };

    // selectionLabelLR :: [Table] -> Either String RangeName
    const selectionLabelLR = tables => {
        const
            iHasSeln = tables.findIndex(
                table => Boolean(
                    table.selectionRange()
                )
            );

        return iHasSeln === -1
            ? Left("Nothing selected in active sheet.")
            : Right(
                tables[iHasSeln].selectionRange.name()
            );
    };

    // ----------------------- 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 ---------------------

    // 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 = lr =>
    // Bind operator for the Either option type.
    // If lr has a Left value then lr unchanged,
    // otherwise the function mf applied to the
    // Right value in lr.
        mf => "Left" in lr
            ? lr
            : mf(lr.Right);


    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = fl =>
    // Application of the function fl to the
    // contents of any Left value in e, or
    // the application of fr to its Right value.
        fr => e => "Left" in e
            ? fl(e.Left)
            : fr(e.Right);

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

Your version worked. So I will mark it as solved. But there were three problems.

First, it took me a while to realize that IEFE was probably a typo. I think you meant IIFE, according to my google searches which kept insisting I was spelling it wrong. I had never heard of either term before.

Second, switching off the Modern Syntax flag didn't fix it. I still got an error saying the return was in the wrong place.

Third, I was unable to prefix the IIFE with "return" because I was unable to find the spot in the original macro corresponding to the location that you indicated ("return (() => {"). I tried several things that you might have intended by that, but they all came back with an error regarding the return statement.

I guess I don't need these problems resolved, since your version works, but I thought I would mention them in case you wanted to clarify your explanation.

Thanks – should traditionally have been invoked rather than evaluated

(though the former sounds just a little bit imperative and misleading to me, as if a small homunculus was stage-directing evaluation of the code :slight_smile: )

Assigning the value of the code evaluation to local_Return, here are two versions adapted from the late JMichaelTX's draft:

  1. Both stripped back and dropping the dialog
  2. One for modern syntax (main expression prefixed with return), and one for the old syntax, (which drew every single keyboard maestro variable name and value into the evaluation space)

(as distinguished by the options exposed by the small chevron to the left of the code text field)

Screenshot 2024-06-19 at 6.20.20 AM

twoVersions.kmmacros (6,8 Ko)

Expand disclosure triangle to view JS source
// Adaptation of original code by JMichaelTX, of blessed memory

(() => {
    "use strict";

    // --- Get Ref to Current App (needed for Dialogs, etc) ---

    const numApp = Application("Numbers");
    const oDoc = numApp.documents[0]; // active document
    const oSheet = oDoc.activeSheet(); // active sheet
    const tableList = oSheet.tables(); // all tables on sheet

    // --- Get FIRST Table That Contains the Selected Range ---
    const selTables = tableList.filter(tbl => tbl.selectionRange());

    return 0 < selTables.length
        ? selTables[0].selectionRange().name()
        : "";
})();

Or pruning back some of the redundancy, and assuming that there is indeed a document open in Numbers – this draft without the preceding return, for testing in Script Editor or VS Code etc:

(() => {
    "use strict";

    const
        selTables = Application("Numbers")
        .documents.at(0)
        .activeSheet()
        .tables()
        .filter(tbl => tbl.selectionRange());

    return 0 < selTables.length
        ? selTables[0].selectionRange().name()
        : "";
})();

For modern syntax, it would suffice to write:

const
    selTables = Application("Numbers")
    .documents.at(0)
    .activeSheet()
    .tables()
    .filter(tbl => tbl.selectionRange());

return 0 < selTables.length
    ? selTables[0].selectionRange().name()
    : "";

Pruned.kmmacros (1.8 KB)

or perhaps:

return Application("Numbers")
    .documents.at(0)
    .activeSheet()
    .tables()
    .flatMap(tbl => {
        const selnRange = tbl.selectionRange();

        return selnRange
            ? [`${tbl.name()} :: ${selnRange.name()}`]
            : [];
    })
    .join("\n");