These are chat archives for canjs/canjs

10th
Apr 2018
Justin Meyer
@justinbmeyer
Apr 10 2018 16:19
@dbleier this is likely a source of confusion we've been seeing over and over
listenTo only fires when an event is fired
it isn't called with the initial value of units
well, judging by how you called resolve() initially, you might be aware of this
basically value() will be called either:
  • when someone reads an unbound users property, OR
  • the first time someone binds to users
We've been thinking of adding a onValue to complement listenTo
which does the same thing, but is called with the immediate value

I'm not totally sure you are miss-understanding how it's supposed to work.

so each time units is updated the users.value method is called, but the prop.listenTo('units', ...) is not

That makes me think something is wrong, that it's not a missunderstanding

is it possible that users isn't being bound between changes in units?
Justin Meyer
@justinbmeyer
Apr 10 2018 16:24
if you do something like:
connectedCallback(){
  this.listenTo("users", function(){})
}
that should make sure users stays bound for the lifetime of your component
Dovid Bleier
@dbleier
Apr 10 2018 18:41
@justinbmeyer yeah, it almost seems to me that the listenTo() is not finding the units property
where would I put and call the connectedCallback() you wrote?
Dovid Bleier
@dbleier
Apr 10 2018 18:48
one more question, upon reflection
since user.value() gets called every time units changes, that means it's adding another listener on units each time, which if listenTo() would fire, would cause it to fire multiple times and perhaps lead to some sort of leak and performance degradation?
Justin Meyer
@justinbmeyer
Apr 10 2018 21:26
@dbleier you there?
Dovid Bleier
@dbleier
Apr 10 2018 21:26
yes
Justin Meyer
@justinbmeyer
Apr 10 2018 21:26
ok, so lets see if we can get to the bottom of this ... :-)
Dovid Bleier
@dbleier
Apr 10 2018 21:26
great thanks
Justin Meyer
@justinbmeyer
Apr 10 2018 21:27
ok, so first put that connectedCallback in your ViewModel
Dovid Bleier
@dbleier
Apr 10 2018 21:27
done
Justin Meyer
@justinbmeyer
Apr 10 2018 21:28
export const ViewModel = DefineMap.extend({
  connectedCallback(){
    this.listenTo("users", function(){})
  },
  units: {...},
  ...
})
Dovid Bleier
@dbleier
Apr 10 2018 21:28
did it
Justin Meyer
@justinbmeyer
Apr 10 2018 21:28
can you put a console.log("connectedCallback") called in there too
Dovid Bleier
@dbleier
Apr 10 2018 21:28
ok
Justin Meyer
@justinbmeyer
Apr 10 2018 21:28
put one in users's value too ... console.log("user.value")
and one in:
prop.listenTo('units', (target, newval, oldval) => {
  console.log("user.value's units")
then do whatever you do to change units and paste the logs here
Dovid Bleier
@dbleier
Apr 10 2018 21:29
ok
Justin Meyer
@justinbmeyer
Apr 10 2018 21:30
btw, this might also be a good place to use can.queues.logStack()
but we can get to that in a min
Dovid Bleier
@dbleier
Apr 10 2018 21:31
user.value
00:30:48.575 multiple-units.js:8 connectedCallback
Justin Meyer
@justinbmeyer
Apr 10 2018 21:32
do you see any of these logs before the change?
Dovid Bleier
@dbleier
Apr 10 2018 21:32
only after
Justin Meyer
@justinbmeyer
Apr 10 2018 21:32
oh, so the component is being inserted only after the change
Dovid Bleier
@dbleier
Apr 10 2018 21:32
and user.value always precedes connectedCallback
yes, I guess so
Justin Meyer
@justinbmeyer
Apr 10 2018 21:33
ok
so there is no bug, just inadequately explained behavior of value()
which i was trying to explain up above
Dovid Bleier
@dbleier
Apr 10 2018 21:34
ok, so I am still unclear
Justin Meyer
@justinbmeyer
Apr 10 2018 21:35
will explain again in one min ... toddler problems to deal with one sec ...
Dovid Bleier
@dbleier
Apr 10 2018 21:35
np, been there many times myself :)
Justin Meyer
@justinbmeyer
Apr 10 2018 21:35
ok, so listenTo only listens to CHANGES ... that happen after value() is called
that's the important thing to understand
Dovid Bleier
@dbleier
Apr 10 2018 21:36
right, but changes are happening after value is called
the component that controls the list of units is already there
Justin Meyer
@justinbmeyer
Apr 10 2018 21:37
that's not what your logs seem to indicate
Dovid Bleier
@dbleier
Apr 10 2018 21:37
units are controlled by a different component
Kevin Phillips
@phillipskevin
Apr 10 2018 21:37
you're trying to listen to items being added/removed from units?
Dovid Bleier
@dbleier
Apr 10 2018 21:37
and fed into this component via stache bindings
Kevin Phillips
@phillipskevin
Apr 10 2018 21:37
or units being changed to point to something else?
Dovid Bleier
@dbleier
Apr 10 2018 21:37
items added/removed
Justin Meyer
@justinbmeyer
Apr 10 2018 21:38
ah, good call @phillipskevin
then you need to listenTo(this.units,"length", fn)
Dovid Bleier
@dbleier
Apr 10 2018 21:38
aha
Justin Meyer
@justinbmeyer
Apr 10 2018 21:41
so if you wanted to handle if units changed or values changed within units (and didn't care too much about performance), you could do:
get unitsClone(){
  return this.units.get()
},


// then

    prop.listenTo("unitsClone", ...)
Dovid Bleier
@dbleier
Apr 10 2018 21:43
so I switched to listenTo(this.units,"length", fn)
and now it fires
except 2 issues
  1. the initial set, as you mentioned - how to get around that?
  1. does not seem to be resolving when calling prop.resolve(users)
I will say that while I was waiting for a response, I tried just moving everything from listenTo, right into value and that worked
what is the benefit of using the listenTo in this case?
Kevin Phillips
@phillipskevin
Apr 10 2018 21:46
can you post what your code is now?
Dovid Bleier
@dbleier
Apr 10 2018 21:47
    connectedCallback() {
        console.log("connectedCallback");
        this.listenTo("users", function() {})
    },
    units: {
        default: () => { return []; },
    },
    users: {
        value: function(prop) {
            console.log("user.value");
            let self = this;
            prop.resolve({});
            prop.listenTo(prop.lastSet, prop.resolve);
            prop.listenTo(this.units, 'length', (target, newval, oldval) => {
                console.log("user.value's units");
                let organizeUnits = (users, user, sign, unit) => {
                        users[user] = users[user] || {};
                        users[user][sign] = users[user][sign] || [];
                        users[user][sign].push(unit);
                        return users;
                    },
                    users = self.units.reduce((users, unit) => {
                        return organizeUnits(users, unit.user || 'unassigned',
                            unit.currentSign ? unit.currentSign.path : 'unassigned', unit.serialize());
                    }, {});
                prop.resolve(users);
            });
        }
    },
Kevin Phillips
@phillipskevin
Apr 10 2018 21:49
you're saying that if you remove the prop.listenTo(this.units, 'length' it still changes when items are added/removed from units ?
Dovid Bleier
@dbleier
Apr 10 2018 21:50
yes, seems that users.value() is called every time units change
Kevin Phillips
@phillipskevin
Apr 10 2018 21:51
like Justin mentioned, it shouldn't do that if users is bound
if it does, it's a bug
since users will have one change to {} and then a second change to the correct value each time
you'll have an extra event that you shouldn't have
Dovid Bleier
@dbleier
Apr 10 2018 21:52
no, I removed the prop.resolve({}) in that case
    users: {
        value: function(prop) {
            prop.listenTo(prop.lastSet, prop.resolve);
            let organizeUnits = (users, user, sign, unit) => {
                    users[user] = users[user] || {};
                    users[user][sign] = users[user][sign] || [];
                    users[user][sign].push(unit);
                    return users;
                },
                users = this.units.reduce((users, unit) => {
                    return organizeUnits(users, unit.user || 'unassigned',
                        unit.currentSign ? unit.currentSign.path : 'unassigned', unit.serialize());
                }, {});
            prop.resolve(users);
        }
    },
Kevin Phillips
@phillipskevin
Apr 10 2018 21:54
that's just a getter then
Dovid Bleier
@dbleier
Apr 10 2018 21:54
basically
Kevin Phillips
@phillipskevin
Apr 10 2018 21:54
value shouldn't behave like that
can you try reproducing in a jsbin?
Dovid Bleier
@dbleier
Apr 10 2018 21:55
I for sure don't have time today, it's pushing 1am here - and seems like it will be complicated to repro in jsbin
but I could just change value to get
Kevin Phillips
@phillipskevin
Apr 10 2018 21:56
get: function(lastSet) {
Justin Meyer
@justinbmeyer
Apr 10 2018 21:56

@phillipskevin :

since users will have one change to {} and then a second change to the correct value each time

That's not correct. value only emits once per "batch"

Kevin Phillips
@phillipskevin
Apr 10 2018 21:57
oh, right
thanks
Justin Meyer
@justinbmeyer
Apr 10 2018 21:57
I REALLY think we need a onValue
Dovid Bleier
@dbleier
Apr 10 2018 21:58
and maybe a training session on proper use of value and listenTo in can4
;)
but let me ask you guys while I have you on the line, I have another component inside this one, which has a widget that injects itself into a div in the template
problem is that the code to inject was firing before the div was rendered
in can3 I would do it in the inserted event, but I think that was deprecated or removed in can4
so how would I delay creation of the widget until the component is inserted into the dom?
oh, and this is using a .component
Kevin Phillips
@phillipskevin
Apr 10 2018 22:04
the connectedCallback should be able to replace most of what you were doing with the inserted event
Dovid Bleier
@dbleier
Apr 10 2018 22:07
hey, that worked! super cool! thanks! And I just changed user.value to user.get old-style and it works too
Justin Meyer
@justinbmeyer
Apr 10 2018 22:09
yeah, if you can derive the value from other values "instantaneously" you should use get ... You use value for "time" based changes
get is like algebra
value is like calculus
get is like DERIVED = FN(sourceValue)
but value you might want to do something as sourceValue changes ... sorta like a derivative in calculus
Dovid Bleier
@dbleier
Apr 10 2018 22:12
what like change in dist based on change in acceleration?
still not so clear on the use case, definitely think a deep dive would be helpful
anyway it's crazy late here now, gotta head to bed, thanks for your help and happy to be using canjs
Justin Meyer
@justinbmeyer
Apr 10 2018 23:26
@dbleier yeah, pretty much that
so value() (and streams in general) could create a value that was how much another value last changed by
number: "number",
lastChange: {
  value({resolve, listenTo}){
    resolve(0)
    listenTo("number", function(ev, newVal, oldVal){
      resolve( Math.abs(newVal - oldVal) )
    })
  }
}