Execute JavaScript For Automation - Mixed Code

Based on what I understand from this post.

I am guessing that the only way to be able to get this to work is to reference a file to the following JavaScript code since it is not pure JXA

JavaScript for OmniFocus Using Keyboard Maestro
(() => {
    'use strict';

    // Get the KM variable in the JXA context BEFORE passing to OmniJS
    const kmApp = Application("Keyboard Maestro Engine");
    const daysOffset = parseInt(kmApp.getvariable("DaysOffset")) || 1;

    // OMNI JS CODE ---------------------------------------
    const omniJSContext = (daysOffsetValue) => {
        const main = () => {
            // Get selected tasks first
            const seln = document.windows[0].selection.tasks;
            if (seln.length === 0) {
                return "No tasks selected in OmniFocus";
            }

            // Use the passed in offset value directly
            const daysOffset = daysOffsetValue;

            // Calculate target date with offset
            const targetDate = new Date();
            targetDate.setDate(targetDate.getDate() + daysOffset);
            const formattedDate = targetDate.getFullYear() + '-' + 
                String(targetDate.getMonth() + 1).padStart(2, '0') + '-' + 
                String(targetDate.getDate()).padStart(2, '0');

            // Find target project with the date
            let targetProject = flattenedProjects.find(p => p.name.includes(formattedDate));
            if (!targetProject) {
                return `No Project Found With the Date of ${formattedDate}`;
            }

            // Process each selected task
            for (const task of seln) {
                try {
                    let parentGroup = targetProject.tasks.find(t => t.name === task.parent.parent.name);
                    if (!parentGroup) {
                        parentGroup = new Task(task.parent.parent.name, targetProject);
                    }

                    let subGroup = parentGroup.children.find(t => t.name === task.parent.name);
                    if (!subGroup) {
                        subGroup = new Task(task.parent.name, parentGroup);
                    }

                    if (typeof moveTasks === 'function') {
                        moveTasks([task], subGroup);
                    } else {
                        task.move(subGroup);
                    }
                } catch (error) {
                    // Silent error handling
                }
            }

            return `Successfully Moved to the Project Named ${targetProject.name}`;
        };

        return main();
    };

    // OmniJS Context Evaluation ------------------------------------------------
    return 0 < Application('OmniFocus').documents.length ? (
        Application('OmniFocus').evaluateJavascript(
            `(${omniJSContext})(${daysOffset})`
        )
    ) : 'No documents open in OmniFocus';
})();
Execute JavaScript For Automation - Pasted Code Doesn't Work

The above OmniFocus script is using TWO different JavaScript contexts:

  1. The outer JXA context (which can talk to Keyboard Maestro)
  2. The inner OmniJS context (which runs inside OmniFocus)

When I save the script as a file and reference it, the entire script runs properly through both contexts. But when you I paste it directly into "Execute JavaScript For Automation", it doesn't properly handle the nested OmniJS context evaluation.

So do I need to break up the code or is there some other Keyboard Maestro action that will allow me to keep this all in Keyboard Maestro and not have to reference another file which is my preference.

Possibly just that you need to make a couple of adjustments for the Modern Syntax option which is now the default in the menu behind the small chevron to the left of the Execute JavaScript for Automation text field:

  1. You need to prefix your IIFE with return
  2. You don't need the .getvariable method.

Assuming that in the Chevron menu you have not only selected "Modern Syntax" but have also checked the name of the KM Variable value that you want to use (DaysOffset here)

You can write:

return (() => {
    'use strict';

    const daysOffset = parseInt(kmvar.DaysOffset) || 1;

    // OMNI JS CODE ---------------------------------------

   etc. etc.

In full:

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

    const daysOffset = parseInt(kmvar.DaysOffset) || 1;

    // OMNI JS CODE ---------------------------------------
    const omniJSContext = (daysOffsetValue) => {
        const main = () => {
            // Get selected tasks first
            const seln = document.windows[0].selection.tasks;
            if (seln.length === 0) {
                return "No tasks selected in OmniFocus";
            }

            // Use the passed in offset value directly
            const daysOffset = daysOffsetValue;

            // Calculate target date with offset
            const targetDate = new Date();
            targetDate.setDate(targetDate.getDate() + daysOffset);
            const formattedDate = targetDate.getFullYear() + '-' + 
                String(targetDate.getMonth() + 1).padStart(2, '0') + '-' + 
                String(targetDate.getDate()).padStart(2, '0');

            // Find target project with the date
            let targetProject = flattenedProjects.find(p => p.name.includes(formattedDate));
            if (!targetProject) {
                return `No Project Found With the Date of ${formattedDate}`;
            }

            // Process each selected task
            for (const task of seln) {
                try {
                    let parentGroup = targetProject.tasks.find(t => t.name === task.parent.parent.name);
                    if (!parentGroup) {
                        parentGroup = new Task(task.parent.parent.name, targetProject);
                    }

                    let subGroup = parentGroup.children.find(t => t.name === task.parent.name);
                    if (!subGroup) {
                        subGroup = new Task(task.parent.name, parentGroup);
                    }

                    if (typeof moveTasks === 'function') {
                        moveTasks([task], subGroup);
                    } else {
                        task.move(subGroup);
                    }
                } catch (error) {
                    // Silent error handling
                }
            }

            return `Successfully Moved to the Project Named ${targetProject.name}`;
        };

        return main();
    };

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

And this seems to work here:

OmniJS test.kmmacros (4.6 KB)

2 Likes

There's a bit more detail on Script Editor vs KM JXA Action here:

https://forum.keyboardmaestro.com/t/using-script-editor-to-test-scripts-for-execute-jxa-actions

1 Like

That works great here as well. Thank you for the information and making that work. It is interesting that Keyboard Maestro doesn't the code look all nice like it does with AppleScript and it is more difficult to get the error messages and "Save Results to a Clipboard" then choosing System Clipboard.

Save Results to System Clipboard

This is awesome, thank you. Funny you just posted this info too.

You get code formatting if you switch off Modern Syntax.

The difficulty with achieving that in the latter may, I think, be a price paid for wrapping the code in an IIFE and preparing a kmvar object (informed with the selected KM Variable key:value pairs).

1 Like

Okay, yay that is the best of both worlds and I can get better readable results using Script Editor and I can see the formatting and can just copy and paste the code into "Execute JavaScript For Automation" and disable Modern Syntax.

Thank you that was quick and easy and I will not have to modify the dozens of Script files I have made (though the changes were relatively simple) this is much easier to read.

Keeping legacy scripts with the old syntax seems a sensible economy of effort.

For new scripts, it does mean losing the simpler kmvar.varName syntax, and the ability to limit which variables are exported.


Perhaps the place where you need script visibility is really in the coding editor ?

(best not to edit code in the KM action, it's really just a place to paste into once the thing is cooked and prepared)

(Visual Studio code gives good syntax highlighting for JavaScript, and a lot of different themes to choose from)