These are chat archives for canjs/canjs

21st
Mar 2017
Guido Smeets
@gsmeets
Mar 21 2017 08:21

Does anyone know what alternative I have to the change event in canjs 3?

Say I have a list of foo's on my viewModel, and I want to listen to a change on any of the list "value" properties. I.e. how do I write this in canjs 3:

"{viewModel.foos} change" : function ( foos, event, prop ) {
    if ( prop.indexOf( "value" !== -1 )) { ... }
}
Gira Minus
@gKreator
Mar 21 2017 08:25
Yes
Jeroen Cornelissen
@jeroencornelissen
Mar 21 2017 08:25
@gsmeets try “{viewModel} foos” : function(…. or "{viewModel.foos} property” : function(...
Guido Smeets
@gsmeets
Mar 21 2017 08:25
does that work? because it doesn't in 2.X
(Can't try it atm, still working in 2.X, just trying to write everything 3.X compatible, so that migration in a few months will be easier)
Jeroen Cornelissen
@jeroencornelissen
Mar 21 2017 08:28
It works in 3.X
Szabolcs Schmidt
@sszabolcs
Mar 21 2017 13:40

Hello!

I've further investigated the memory problem. It seems to me that can-util/dom/data/data causes the leak.
In data.js there is a variable 'data' that stores objects by id.
As I could understand the problem lies in not cleaning the 'data' variable.

To put everything in context: we have a quite large app (19 000 LOC) that is built with CanJS 2.x.
We are happy with it and started to migrate to 3.x.
We are at the final steps of migration and now we realized that with CanJS 3 we have a memory leaking problem.
I've created a small sample app that compares CanJS 2 and 3 regarding memory usage:
gist link

  1. Open index_canjs2.html and with dev tools Timeline record memory usage (start recording with a GC).
  2. Click on Insert/Remove for a few times.
  3. Before stopping Timeline recording call GC, then stop recording.
    For example if I click on Insert/Remove for three times I can see on Timeline that there are exactly the same number of listeners (3) and nodes (70) in the begining as in the end.

Repeating these steps with index_canjs3.html show that the number of listeners grow gradually (with 3 Insert/Remove cycle the number of listeners grows from 4->10)!
If you put a breakpoint at the 753. row of can.all.js and analyze the content of variable 'data' (in Closure) after you click on Insert, you can see, that after a few "click on Insert/click on Remove" cycle 'data' keeps growing.
Initially - at the first click on Insert - there are 3 items in it.
Clicking Insert for the second time the 'data' variable contains 8 item.
When I click on Insert for the sixth time 'data' contains 28 objects!
No object gets removed from it.

This causes a memory leak: there are detached HTMLAnchorElements which cannot be GC-d because 'data' keeps reference for objects (canAttributesObserver, MutationObserver) which reference these HTML objects.
Shouldn't 'data' contain only objects that are somehow related to objects live in the DOM?
Is there something I'm doing wrong regarding the component insertion and removal?
Thank you in advance!

Kevin Phillips
@phillipskevin
Mar 21 2017 13:42
@sszabolcs can you open an issue in can-util? we will investigate

also, does this memory leak happen if you instead of

var frag = template(new TestVM());
document.body.appendChild(frag);

you do

var myCompFrag = myCompRenderer();
document.body.appendChild(myCompFrag);
?
Szabolcs Schmidt
@sszabolcs
Mar 21 2017 13:46
@phillipskevin of course, I open an issue for this
Kevin Phillips
@phillipskevin
Mar 21 2017 13:46
creating document fragments inside of a DefineMap like that is not the conventional way of doing it
I don’t know that there’s any issue doing it that way, just curious
Szabolcs Schmidt
@sszabolcs
Mar 21 2017 13:47
Ok, I'll test it soon! Thx!
Szabolcs Schmidt
@sszabolcs
Mar 21 2017 14:37
@phillipskevin if I use the code you suggested instead the one that renders "content-using-my-component" then I won't have Insert and Remove buttons, the "control" which drives the component insertion and removal. This way only the component is appended to the body.
Kevin Phillips
@phillipskevin
Mar 21 2017 15:16
yeah, that makes sense, that was just the best suggestion I could come up with quickly
would you be able to put the 3.0 code in a JSBin?
I’m curious if the same issue happens if the insert/remove are handled outside of a DefineMap
with it in the DefineMap I’m concerned that myCompFrag is being observed
Szabolcs Schmidt
@sszabolcs
Mar 21 2017 15:36
here is the bin: JSBin link
Nico R.
@nriesco
Mar 21 2017 18:13
is parentId a special keywork when setting a binding in stache?
Kevin Phillips
@phillipskevin
Mar 21 2017 18:19
nope
Nico R.
@nriesco
Mar 21 2017 18:21
@phillipskevin thanks, I know what is going on...
I need to use {(parent-id)}
I always have trouble with that
Nico R.
@nriesco
Mar 21 2017 18:33

@phillipskevin I’m confused, please help me with this:

export const ViewModel = Map.extend({
  define: {
    cardsPromise: {
      get: function() {
        return Card.getList({ storyId: this.attr('parentId') });
      }
    }
  },
  showShow: function(element) {
    var self = element;
    var data = self.attr('text');
    return '<span style="color:white;background:red;">' + data + '</span>';
  }
});

and in the stache file:
{{{showShow}}}

this works, but I don’t understand why showShow is receiving the correct object (element)

Kevin Phillips
@phillipskevin
Mar 21 2017 18:34
it’s receiving the current scope
for wherever it is in the template
if you changed the scope somehow, then it wouldn’t work

like if you did

{{#each myCoolList}}
  {{{ showShow }}}
{{/each}}

then it would get each item from myCoolList instead of the entire viewModel

it’s better to not rely on this and to pass the scope explicitly
like {{{ showShow(this) }}}
Nico R.
@nriesco
Mar 21 2017 18:36
cool, that makes things easier then.. what about if I use a parameter? then the scope would be the second parameter and so on?
Kevin Phillips
@phillipskevin
Mar 21 2017 18:36
if you pass a parameter like this, then it would just pass that parameter
Nico R.
@nriesco
Mar 21 2017 18:37
ok
thanks!
Kevin Phillips
@phillipskevin
Mar 21 2017 18:38
no problem
Nico R.
@nriesco
Mar 21 2017 18:54
I tried {{{showShow}}}, {{{showShow(this)}}} and {{{showShow this}}} and they all seem to work. I’m using can 2.x, which syntax is the recommended one?
Gira Minus
@gKreator
Mar 21 2017 19:01
Is this in the docs?
Kevin Phillips
@phillipskevin
Mar 21 2017 19:07
{{{showShow(this)}}}
it’s definitely in the 3.0 docs
Kevin Phillips
@phillipskevin
Mar 21 2017 19:12
It’s in the 2.3 docs also:
{{{showShow(this)}}} : Call Expression - https://v2.canjs.com/docs/can.stache.expressions.html#section_Callexpression
{{{showShow this}}}: Helper Expression -https://v2.canjs.com/docs/can.stache.expressions.html#section_Helperexpression
Nico R.
@nriesco
Mar 21 2017 19:55
@phillipskevin so if both syntax work, why use two?
Gira Minus
@gKreator
Mar 21 2017 20:26
Two different models technically
Kevin Phillips
@phillipskevin
Mar 21 2017 22:19
@nriesco helper expressions have been around for a while and have some downsides
That's why call expressions were added
Gira Minus
@gKreator
Mar 21 2017 23:27
What are the pro's to helper functions then?