These are chat archives for canjs/canjs

19th
Oct 2017
Gregg Roemhildt
@roemhildtg
Oct 19 2017 17:08
Hi guys. I'm looking into finding resources about making other "observables" work with canjs. I've looked at can-stream-kefir for guidance, are there any other resources demonstrating what I need to do? I'm looking at using can-symbol and adding the properties. Would I need to implement all of the can-symbols in order to use another observable in can-stache?
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 17:12
At very least you should implement @@can.onKeyValue and @@can.offKeyValue. As I've been working on can-observe, that's been mostly what's required for making observable proxies.
If you are making a List-like, though, currently you will still need to have an addEventListener and removeEventListener to work with can-view-live.list. That won't be a requirement in can 4.x
Gregg Roemhildt
@roemhildtg
Oct 19 2017 17:14
Okay, I'll start there. Will this prevent canjs from automatically trying to convert the data structure to a DefineMap?
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 17:14
Yes. It should not automatically convert anything that returns true when passed to can-reflect.isObservableLike()
Gregg Roemhildt
@roemhildtg
Oct 19 2017 17:15
Okay! Cool!
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:13
So I'm not sure if I'm doing this right. I have implemented those two methods for an object structure, but the methods for [canSymbol.for('can.onKeyValue')] are not getting called, even though the values are being rendered in a stache template.
And just to test, I have made sure that canReflect.isObservableLike returns true on the objects
So in effect, when the values change, they aren't updated in the template.
/**
 * Decorate esri's observable type with canjs methods
 * @param {esri/core/Accessor} obj 
 */
function decorate (obj) {

    // make sure object exists and isn't already decorated through circular references
    if (!obj || !obj.__accessor__ || obj[canSymbol.for('can.onKeyValue')]) {
        return obj;
    }

    const handlers = {
    };
    obj[canSymbol.for('can.offKeyValue')] = function (key, handler) {
        if (!handlers[key]) {
            handlers[key] = [];
        }

        const filtered = handlers[key].filter((handle) => {
            return handle.handle === handler;
        })[0];
        if (filtered) {
            filtered.watch.remove();
            handlers[key].splice(obj.handlers[key].indexOf(handler), 1);
        }
    };
    obj[canSymbol.for('can.onKeyValue')] = function (key, handler) {
        console.log(key, handler);

        const watch = obj.watch(key, (newValue, oldValue, propertyName, target) => {
            console.log(newValue, key);
            handlers[key].forEach((handle) => {
                handle.handle.call(obj, newValue);
            });
        });

        if (!handlers[key]) {
            handlers[key] = [];
        }
        handlers[key].push({handle: handler, watch: watch});
    };

    // decorate child keys
    obj.keys().forEach((key) => {
        console.log(key);
        decorate(obj[key]);
    }); 

    return obj;
}
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:21
ok. That seems mostly fine assuming that there's only one copy of this object (looks like handlers is a reference to an outside scope)
oh you know what, there's probably one more thing you need to do.
Import can-observation and call Observation.add(this, key) whenever a key is read.
That's how you notify other computes that they depend on your value and need to call @@can.onKeyValue
(incidentally for Can 4.0 we'll be splitting this functionality into its own package, can-observation-recorder, as Observation is a thing)
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:24
Hmm, well that might be tricky, I don't have access to the internal api of these particular objects, like their getters
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:25
Are the set of keys known?
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:25
I can find the keys, obj.keys() gets the array
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:26
The easy way in that case is to Object.create a prototypal sub-object of the original and make a get/set pair for each key that calls Observation.add on the get and returns the value from this.__proto__[key]
(In retrospect, it sounds complicated, but it's not really). Let me see if I can draw up some example code.
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:28
one sec, I think that might break something, because the obj is already using accessor properties
like getters and setters
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:30
function decorate(obj) {
  var ret = Object.create(obj);
  Object.keys(ret).forEach(function(key) {
    Object.defineProperty(ret, key, {
      configurable: false,
      enumerable: true,
      get() {
        Observation.add(this, key);
        return this.__proto__[key];
      },
      set(val) {
        this.__proto__[key] = val;
      }
    });
  });
  return ret;
}
so even if the original object uses accessors, this will still call the accessors above.
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:31
So its basically a shell, that returns the original objects properties
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:32
Yes. You would also still do the symbol functions like you had above, but I didn't include them for brevity/clarity
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:32
right, that makes sense
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:32
You could also do a Proxy like I'm currently working on for can-observe
Proxies just have one interceptor for each operation, so every property get goes through .get()
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:39
Hmm, so I added the code you just posted above, and the stache template is still rendering, but my onKeyValue function isn't getting called, and the stache template isn't updating yet.
I still feel like I'm missing something obvious
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:43
Can you just check those getters are called?
If you dip into Observation.add() as well, you should see something on Observation.observationStack when those things are added
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:45
Yup, those getters are called correctly
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:45
:fry:
You know what, I'm being a bit overengineered with that decorator above.
You can instead implement @@can.getKeyValue to call Observation.add() (and return the property value on the object), and the relevant places will call it as needed
But that's obviously not the problem.
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:48
So the property I'm testing it with is view.center.longitude The getter is logging the key value, and center gets logged, but never longitude
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:48
wait, are you also decorating view.center?
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:48
Yeah, I'm recursively decorating the object
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:48
hmm
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:48
at the end of the function above
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:49
ok, and I guess you've already checked that center is (a) defined as enumerable, and (b) defined at the time you decorate?
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:49
So, view.center works but view.center.longitude doesn't because its no longer accessing our custom object
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:49
right.
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:50
So can.getKeyValue should fix it maybe
I'll try that
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:50
Yeah, forget the snippet above. just add @@can.getKeyValue
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:54
Okay! progress! The view.center handler is getting called now. I'm guessing I need to implement another symbol method to get the child keys registered with can
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:55
Are you expecting "view.center" to fire when "view.center.longitude" changes?
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:57

I take that back, both center and longitude are getting registered.

Not necessarily, but it does. The center watch handler fires when its latitude or longitude change

Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:57
ah, I see. That's a function of the watch engine. We did this with "change" handlers in can-map, but it's not performant
Which is why in DefineMap if you want to listen on any and all property changes you have to manually make a bound serializer.
Gregg Roemhildt
@roemhildtg
Oct 19 2017 19:58
That makes sense.
I wouldn't really expect center to fire changes when its child prop changes
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 19:59
So is the stache updating correctly now or is there still something missing?
Gregg Roemhildt
@roemhildtg
Oct 19 2017 20:00
I'm still missing something, but I think its on their end. I think longitude is actually a virtual property that isn't firing a change
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 20:01
ahh. perhaps it's computed from something else?
Gregg Roemhildt
@roemhildtg
Oct 19 2017 20:06
Yup, confirmed, in the metadatas, longitude depends on x. But using the main property center.x isn't working quite right either. Its actually returning the same as longitude, even though in the browser console I have a global set to view that returns a different coordinate system value
I'll keep fiddling with it, you've gotten me to a good point where I think its close. Thanks much for the help!
:100:
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 20:08
OK. One thing you might be able to do in the stache is {{#center.x}}{{center.longitude}}{{/center.x}}
I did this trick to force redrawing of charts in a pre-can-reflect project.
(of course, that only works if center.x is never zero)
Gregg Roemhildt
@roemhildtg
Oct 19 2017 20:22
So one other question, right now I'm doing this decorating to the instances, should I be instead decorating the prototype? That way I wouldn't have to decorate every object, right?
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 20:23
Right. The only difference with the prototype is that you need some way of managing handlers for each individual object using the same functions.
Dropping another symbol on the object as a handler store should suffice. So:
Gregg Roemhildt
@roemhildtg
Oct 19 2017 20:24
Yup, thats pretty straightforword. These Accessor objects already have unique ids
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 20:25
OK, yeah, that's fine as well
Gregg Roemhildt
@roemhildtg
Oct 19 2017 20:25
All the objects are essentially Dojo library subclasses of this Accessor base class
Not sure if you're familiar with dojo, but can I just decorate the Accessor class perhaps?
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 20:27
That should work fine.
Gregg Roemhildt
@roemhildtg
Oct 19 2017 20:28
I'll give it a shot. Thanks for all your help. If this works out, I'll put it on my profile, just in case any of you guys ever work with the arcgis js api :wink: (word of advice...don't!)
(jk)
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 20:29
Heh, thanks! You might consider releasing it as a general library for Dojo accessors though!
Gregg Roemhildt
@roemhildtg
Oct 19 2017 20:30
I don't think dojo itself uses this, tbh I'm not sure of the Accessor's origins. I think it came from esri and the esri api (which was built on dojo)
Brad Momberger
@bmomberger-bitovi
Oct 19 2017 20:30
ah, well, I can't claim knowledge of the ecosystem
Morgan Heimbeck
@Xitstrategies
Oct 19 2017 21:01
Anyone else having issues with can-zone eating console errors/warnings that are important to finding issues in there code?