MaxobjListener not instantiating properly when onload

McNamara's icon

I'm using a MaxobjListener in the JS object to watch for changes in a dict object.

I followed the example from the documentation:
https://docs.cycling74.com/max8/vignettes/jsmaxobjlistener

It works fine if I have the patch running and then create the JS object afterwards with my code. But when I close the patch and re-open, the JS script works except for the MaxobjListener - it doesn't fire it's callback on changes. If I delete the object, then undo (re-instantiate it) it is able to watch the dict correctly.

Has anyone had a similar issue? Is this a bug or am I missing something? I am using a dict that is embedding it's data too, if that makes a difference.

tyler mazaika's icon

No experience with MaxobjListener on [dict] itself. But... console errors offer any clues? Are you instantiating your listener inside loadbang() {} ? When I work with JS and dicts I often find I need to send deferred bangs after load* to initialize and get the expected data read properly... like checking for [dict]s with certain names -- that I know should exist -- aren't available at the moment a patch is loaded.

* This may be M4L Parameter Mode related though, rather than just 'embed'.

McNamara's icon

The best clue of the problem I'm getting so far is I set up a bang() function to report the name of the object the object listener is attached to; when I open the patch and hit bang, it gives me a different name than when I subsequently delete and re-instantiate the js object (ie onloadbang: print: jsobject -1266632666049520, after delete/undo: print: jsobject -1266632666049832).

So the listener is listening to something else onload vs when I start it after the patch is loaded. I thought since the [dict] is set to @embed 1 it might persist onopen/close.

So in terms of deferred bangs, does that mean you add a delay to a loadbang?

tyler mazaika's icon

Small test here. Basically comparing setting this up in/after loadbang() vs. trying to set the same things up prior to loadbang(). No callback fired if configured prior to loadbang.

var vname = "MyTest" // [dict] object varname
var listener = null
var preloadbanglistener = null


function dictExists() {
    var dict = new Dict(jsarguments[1])
    post("dictExists", jsarguments[2], dict.getkeys(), "\n")
    preloadbanglistener = new MaxobjListener( this.patcher.getnamed(vname), null, function(data) {
        post("dictExists-callback", jsarguments[2], data.value, "\n")
    } ) 
}


function loadbang() {
    var dict = new Dict(jsarguments[1])
    post("loadbang", jsarguments[2], dict.getkeys(), "\n")
    listener = new MaxobjListener( this.patcher.getnamed(vname), null, function(data) {
        post("loadbang-callback", jsarguments[2], data.value, "\n")
    } ) 
}



// Tries initializing listener prior to loadbang()
dictExists()
McNamara's icon

Thanks Tyler - when I run this test on my machine: dictExists fires if I instantiate the js object after loading the patch, if I close an re-open the patch both the loadbang and dictExist listener fire on changes. Is that what you expected the patch to do?

tyler mazaika's icon

You're saying you see "dictExists-callback" printed? I definitely do not see that. It is never printed on my machine.

The "dictExists" print is just printing about whether a JS Dict() object with the dictionary name can be accessed and print its keys. When you open the patch after Max has been quit and relaunched, it prints no valid key names, whereas the one in loadbang() prints out the keys correctly (one two three). Then when I edit the dictionary the "loadbang-callback" line is printed, but the "dictExits-callback" is not.

You still haven't shown how you initialize your MaxobjListener or answered whether you're using loadbang().

tyler mazaika's icon

Sorry for including the plain JS Dict() / "dictExists" stuff which may have confused things for you. I was just trying to demonstrate accessing embedded [dict] values before/after loadbang(). With that issue, things can appear to work when just closing and re-opening the patch because the dictionary name and data is persisted while Max is running. But if you quit and relaunch Max then the initial "dictExists" doesn't have the right key names printed by getkeys() (as in my screenshot) prior to the loadbang.

McNamara's icon

Took me a bit to realize the intention of the test I can see now that the [dict] has a different key when calling dictExist() vs the loadbang() function.

In my original JS I do not create a MaxobjListener in loadbang(). I create it at the end of my JS similar to the example from the Max doc :

inlets = 2;
outlets = 3;


var eventNum = jsarguments[2];


function valuechanged(data) {
    var ob = new Dict(jsarguments[1]);
      post(ob.name + ": value changed", '\n')
    outlet(0, bang);
    post(eventNum);
    outlet(1, check(eventNum, "::position"));
    outlet(2, check(eventNum, "::window"));
}

function check(eventNum, item) {
    if(!ob.get(eventNum)) return;
    
    var num = eventNum.toString();
    //post("position: " + ob.get(num+"::position"));
    if(ob.get(num+"::window") !== ob.get(num+"::position")) return ob.get(num + item);
    //return ob.get(num+"::window");
}

function setEvent(x) {
    eventNum = x;
    post("event changed to: " + eventNum +  "\n");
    outlet(1, check(eventNum, "::position"));
    outlet(2, check(eventNum, "::window"));
}

//creating listener here:
l = new MaxobjListener(this.patcher.getnamed(jsarguments[1]), valuechanged);
McNamara's icon

Is the solution then to have two different listeners created like this:

function dictExists() {
    post("dictExists", jsarguments[2], ob.getkeys(), "\n") 
    
    preloadbanglistener = new MaxobjListener( this.patcher.getnamed(jsarguments[1]), valuechanged);
    
}

function loadbang() { 
    post("loadbang", jsarguments[2], ob.getkeys(), "\n")
    
    listener = new MaxobjListener( this.patcher.getnamed(jsarguments[1]), valuechanged);
}

That seems to work, but haven't I created two listeners, with one doing the job correctly and the other broken depending on when the script is run?

tyler mazaika's icon

I guess I'm surprised the callback works at all since the 2nd argument should be the attribute to listen to rather than the callback function.

Anyway, just one listener that is instantiated inside loadbang() should be all you need. Very small change to your original code I think.

// Declare a global variable for easy access in the future
var listener = null

// Instantiate/assign listener inside loadbang instead of in the global code
function loadbang() {
listener = new MaxobjListener( this.patcher.getnamed(jsarguments[1]), null, valuechanged )
}


McNamara's icon

Ok I'll try that out. Will that permit new JS objects to be created while the patch is up an running and still be able to reference the correct [dict]?

In terms of not have an attribute specified, I think it works that if you only provide two arguments to MaxobjListener it listens/runs the callback for any/all attributes that change. From the documentation:

If the attribute_name argument is specified, the MaxobjListener object will observe that named attribute.

I took the "If" to imply it was an optional argument.

tyler mazaika's icon

“new JS objects” — this is dangerously vague… not your fault just confusing to discuss.

like new [js code.js] objects in Max code? Or in the MaxobjListener is an object in JS sense?

in any event if you really mean “will the loadbang in JS trigger when creating a new JS object box in Max after the patch was loaded”? I think so. loadbang is really just about ensuring that the required automatic patch loading stuff is completed before your other code starts interacting with it. You can always just send the loadbang message to your JS to call it like a normal JS function too.

or If it makes more sense to you make a initListener() function that initializes the listener, and call initListener() inside loadbang() to get it to work on load. Then if for some reason the loadbang thing didn’t work, you can pass a message name (initListener) that is more descriptive to your purpose.

McNamara's icon

I agree - the terminology is messy: object in max - vs object in JS.

I meant the first description, a new [js code.js] object in the Max window.

Thanks for your help Tyler, it's working now correctly when I open the patch, or quit Max and start it up again, or if I instantiate a new [js code.js] object in the same window as the [dict].

For future readers: Create the listener in the loadbang function