Search code examples
macosapplescriptosascriptjavascript-automation

How to get multiple properties from objects in JXA?


Is there a way in JXA to get multiple properties from multiple objects with a single call?

For example, I want to get name and enabled property from menu items which can be done for each individual property as follows:

Application("System Events").processes.byName('Finder').menuBars[0].menuBarItems.name()
Application("System Events").processes.byName('Finder').menuBars[0].menuBarItems.enabled()

but is it possible to get them with a single function call? Something like:

Application("System Events").processes.byName('Finder').menuBars[0].menuBarItems.select('name', 'enabled')

I know, that I can iterate through the menuBarItems and collect properties from .properties() method, but this approach is too slow, that's why I'm looking for other options.

UPDATE

I'm looking for better performance, not for nicer syntax, i.e. I want properties to be retrieved in a single call to System Events.


Solution

  • I'd probably do it like this:

    sys = Application('com.apple.systemevents');
    FinderProc = sys.processes['Finder'];
    FinderMenuBarItems = FinderProc.menuBars[0].menuBarItems();
    
    
    Array.from(FinderMenuBarItems,x=>[x.name(),x.enabled()]);
    

    By first converting the object to an array, this allows one to map each element and retrieve the desired properties for all in one go. The code is split over several lines for ease of reading.

    EDIT: added on 2019-07-27

    Following on from your comment regarding Objective-C implementation, I had a bit of time today to write a JSObjc script. It does the same thing as the vanilla JXA version above, and, yes, it clearly makes multiple function calls, which is necessary. But it's performing these functions at a lower level than System Events (which isn't involved at all here), so hopefully you'll find it more performant.

    ObjC.import('ApplicationServices');
    ObjC.import('CoreFoundation');
    ObjC.import('Foundation');
    ObjC.import('AppKit');
    
    var err = {
        '-25211':'APIDisabled',
        '-25206':'ActionUnsupported',
        '-25205':'AttributeUnsupported',
        '-25204':'CannotComplete',
        '-25200':'Failure',
        '-25201':'IllegalArgument',
        '-25202':'InvalidUIElement',
        '-25203':'InvalidUIElementObserver',
        '-25212':'NoValue',
        '-25214':'NotEnoughPrecision',
        '-25208':'NotImplemented',
        '-25209':'NotificationAlreadyRegistered',
        '-25210':'NotificationNotRegistered',
        '-25207':'NotificationUnsupported',
        '-25213':'ParameterizedAttributeUnsupported',
             '0':'Success' 
    };
    
    var unwrap = ObjC.deepUnwrap.bind(ObjC);
    var bind = ObjC.bindFunction.bind(ObjC);
    
    bind('CFMakeCollectable', [ 'id', [ 'void *' ] ]);
    Ref.prototype.nsObject = function() {
        return unwrap($.CFMakeCollectable(this[0]));
    }
    
    function getAttrValue(AXUIElement, AXAttrName) {
        var e;
        var _AXAttrValue = Ref();
    
        e = $.AXUIElementCopyAttributeValue(AXUIElement,
                                            AXAttrName,
                                            _AXAttrValue);
        if (err[e]!='Success') return err[e];
    
        return _AXAttrValue.nsObject();
    }
    
    function getAttrValues(AXUIElement, AXAttrNames){
        var e;
        var _AXAttrValues = Ref();
    
        e = $.AXUIElementCopyMultipleAttributeValues(AXUIElement,
                                                     AXAttrNames,
                                                     0,
                                                     _AXAttrValues);
        if (err[e]!='Success') return err[e];
    
        return _AXAttrValues.nsObject();
    }
    
    function getAttrNames(AXUIElement) {
        var e;
        var _AXAttrNames = Ref();
    
        e = $.AXUIElementCopyAttributeNames(AXUIElement, _AXAttrNames);
        if (err[e]!='Success') return err[e];
    
        return _AXAttrNames.nsObject();
    }
    
    
    (() => {
        const pid_1        = $.NSWorkspace.sharedWorkspace
                                          .frontmostApplication
                                          .processIdentifier;   
        const appElement   = $.AXUIElementCreateApplication(pid_1);
        const menuBar      = getAttrValue(appElement,"AXMenuBar");
        const menuBarItems = getAttrValue(menuBar, "AXChildren");
    
        return menuBarItems.map(x => {
            return getAttrValues(x, ["AXTitle", "AXEnabled"]);
        });
    })();