Testing OmniGraffle 7.3's built-in JavaScript automation

First off, thanks for this!! Itā€™s truly awesome and has opened up a new level of usefulness for OG for me.

However, Iā€™d like to request a missing API for manipulating the user data (metadata) associated with graphic elements. According to the discussion here (https://omni-automation.com/omnigraffle/metadata-00.html), itā€™s possible to get or set specific items by key, but how do I get the list of keys defined for the given element?

What Iā€™m after is something like:

canvases[0].graphics[0].getUserDataKeys() #=> [ "key1", "key2" ]

This is pretty crucial for my use case. Also, it would be very handy (so I donā€™t have to write it in JS) to allow you to get graphics by only the user data key and not a specific value, e.g.

var g = canvas[0].allGraphicsWithUserDataKey("...");

Iā€™m not sure if thereā€™s a better place to ask this question or get specific support on the automation, but if so, please let me know.

Cheers,

ast

The userData property of a graphic (or the background) returns a vanilla JavaScript object, on which all the built-in JS Object methods can be used, so no special API is needed.

All you need is the standard JS Object.keys(objReference) which will return an array of the user data keys, over which you can map to get values.

(The discovered keys can also, of course, be used in the normal JS way to get the keyed values of the userData object).

Hi,

Thatā€™s what I was trying to do. Unfortunately, in my version, this is what happens:

g = document.windows[0].selection.graphics[0] [object Shape] userData = g.userData undefined
Thatā€™s why I was a little confused. I tried a bunch of iterations on what the property might be (figuring it was just a plain object), but I didnā€™t get the results you did.

Iā€™m on this version:

Build details:

Product: OmniGraffle-7.3.x
Tag: OmniGraffle/7.3.1/GM-v177.6
Date: 2017-05-05 06:59:45 -0700
Builder: omnibuild
Host: tb1010a.private.omnigroup.com
Revision: 286920`

I guess maybe thatā€™s a version issue.

At least I know itā€™s possible. BTW, in my version the #allGraphicsWithUserDataForKey doesnā€™t work either.

I have ā€˜check for updatesā€™ automatically, so maybe this is an early-access release youā€™re running?

Cheers!

Ah you are using 7.3, in which case a JXA approach is possible, but omniJS and the omni-automation site are not yet relevant. (My understanding is that omniJS is a 7.4 test build innovation)

Are you in fact using JavaScript for Automation, e.g. from Script Editor ?

(userData is also accessible (using a slightly different idiom) in that case, but we first need to check which JS interface to OG you are using :-)

In the JavaScript for Automation (JXA) case, graphic.userData() is a method rather than a property:

(OmniGraffle 7.3.1 (v177.6 r286920))

    (function () {
        'use strict';
    
    
        var og = Application('OmniGraffle'),
            ws = og.windows,
            w = ws.length ? ws[0] : undefined;
    
        if (w) {
            var lstSeln = w.selection(),
                g = lstSeln.length > 0 ? lstSeln[0] : undefined;
    
            if (g) {
                var userData = g.userData();
                return {
                    listOfKeys: Object.keys(userData),
                    listOfValues: Object.keys(userData)
                        .map(function (k) {
                            return userData[k]
                        })
                }
            }
        }
    })();

I just followed the original instructions for 7.3 per the title of the thread.

Iā€™m playing with my first plugin trying to basically interrogate a fairly complex diagram I have. All the important shapes have been augmented with user data so I know what they semantically represent, and then Iā€™m trying to do things based on that understanding (that data property editor in the inspector for 7.3 is buggy as hell, BTW).

I only started this today, so first I was following the examples on the omni-automation site via the console, and then I started replacing functionality in the sample skeleton plugin.

Iā€™m running this on OS X (El Capitan) if that makes a difference.

However, thatā€™s for more general use down the road. For now, Iā€™m looking for some specific properties, and thatā€™s sufficient. Iā€™ve also just iterated over the graphics objects to offset the busted canvas method to get the objects via user data values, so Iā€™m not dead in the water. Just trying to figure out what is and is not possible.

What kinds of other, more sophisticated stuff can you do like access the filesystem and send/receive web requests? For now, what Iā€™m doing is kinda a neat toy, but if I canā€™t actually integrate this script with something else, the usefulness I was hoping for wonā€™t be there.

Maybe thereā€™s a sandbox permission model or something where you could prompt the user for accessing certain modules/functions? Make the plugin registry work like authorization for OAuth style apps?

Cheers, and thanks for the speedy replies!

Iā€™m not quite sure what you mean by JXA, as Iā€™m doing it from within OG. Within the OG console, this is what I get taking the userData-as-method approach:

g [object Shape] g.userData() // // TypeError: g.userData is not a function. (In 'g.userData()', 'g.userData' is undefined) undefined:1

So no joyā€¦

JXA (JavaScript for Automation) is a marketing name for JavaScript use of the ApplesScript scripting dictionaries. (So the snippet above works only in Script Editor, with the language tab at top left set to ā€˜Javascriptā€™)

I just tried on 7.3 (thanks, I had forgotten that 7.3 included an early build of omniJS) and I was able to reproduce your result ā€“ graphic.userData is still undefined in 7.3, but works in 7.4, if you are happy to use test builds.

Have now downloaded a 7.4 build and made more progress, in addition to uncovering the docs for the URL method. Looks like I can pull things in, but I canā€™t push things out.

The URL#fetch signature only appears to support functions as arguments and nothing along the lines of working draft #fetch or proxying the properties of NSMutableURLRequest like providing #method or #body.

My objective is to trigger push notifications so that I can use OG documents as the definitive source of information vs. being a passive reflection of information. If I have a way to make POST requests, then everythingā€™s fantastic.

Iā€™ve now successfully poked OG from node via a wrapper around the ā€˜osascriptā€™ utility, so things are getting more interesting. However, Iā€™d still really, really prefer to be able to push things while the document is open rather than doing things via remote-control from another source.

Any way this can be included in 7.4? Iā€™m now using this build:

Product: OmniGraffle-7.4.x Tag: Date: 2017-06-29 13:27:13 -0700 Builder: omnibuild Host: tb1010h.private.omnigroup.com Revision: 291208

Also, Iā€™m still seeing #allGraphicsWithUserDataForKey return zero-length arrays for values I know are in the canvas. Should this work? It it a known issue?

Cheers,

ast

Another thing I was wondering about the architecture of the implementation was why something like CommonJS wasnā€™t used for defining modules since itā€™d make things easier to re-use vs. the approach taken?

It is what it is, I guess, but was curious why OmniJS went its own way on this.

Iā€™m just really glad itā€™s there!

Cheers,

ast

Seeing the same here ā€“ If I have various graphics with an ā€˜animalā€™ key, a couple of which have the data ā€˜kuduā€™ for that key, then

allGraphicsWithUserDataForKey

Returns an empty array, where a more direct:

// concatMap :: (a -> [b]) -> [a] -> [b]
concatMap = (f, xs) => [].concat.apply([], xs.map(f));

ks = concatMap(
    g => g.userData['animal'] === 'kudu' ? [g] : [],
    cnv.graphics
);

or just the less nestable:

cnv.graphics.filter(g => g.userData['animal'] === 'kudu')

Returns an array of two graphics.

Worth flagging through OmniGraffle Menu > Help > Contact Omni ā€“ I donā€™t think itā€™s always possible for them to get around to reading these threads, which are mainly positioned as user to user.

(The ideal thing may be to post here, and then forward a link to omni support through that app menu item)

Yeah, thatā€™s what I did as a workaround as well.

I saw the @SupportHumans reference above, so Iā€™ll try that first, and then see what happens. It isnā€™t critical since thereā€™s a workaround, but Iā€™d like to see some of the other things addressed that I mentioned a couple of posts ago.

I also noticed that this build is ignoring the toolbarLabel strings key and just using the menu label in the toolbar text. Thatā€™s kinda annoying since itā€™s generally too long for a toolbar and the docs say it works. Must work on iOS since I guess there you donā€™t have to manually add the icons.

Thanks for the feedback and confirmation of the issue.

OT: are you from/in ZA, or just like African animals? Kudu is an odd choice of animal otherwise ;)

1 Like

From but not in - nostalgia, perhaps :-)

toolbarLabel strings key

In the context of Plugin actions ?

Lived there for 6 years now.

Re: toolbaLabel, yes. Thatā€™s correct.

1 Like

Another one for the @SupportHumans that Iā€™m not sure qualifies for a Contact Omniā€¦ yet:

Any idea how to dynamically change the labels, tooltips and icons so as to implement a ā€œtoggleā€ action?

What I want is to be able to alter graphics in the canvas, save their original state and restore them outside of the context of the undo/redo mechanism. This is perfectly valid for my use case, even though it might not seem like it.

I see we have access to the this.plugIn reference, but I donā€™t see any properties, and nothing semi-obvious seemed to give me any values, e.g. label, menuLabel, ā€¦

Any and all ideas are appreciated.

I havenā€™t really experimented there yet, but in one context I have thought of simply cycling the referenced icon from the JXA interface, which speaks to the file system.