intercellular / cell

A self-driving web app framework
https://www.celljs.org
MIT License
1.51k stars 94 forks source link

$update not fired when components have a $init defined #155

Closed rchevillot-pippajean closed 7 years ago

rchevillot-pippajean commented 7 years ago

Hi guys, I've faced the following problem, the function $update is not fired in all subcomponents when they have a $init function defined, maybe i did something wrong: First case (that works):

var test = {
    _date: null,

    $cell:true,

    $init: function() {
        this._d1 = this.querySelector('div');
        this._d2 = this.querySelector('div:last-child');

        this._date = new Date(Date.now());
        this._date.setHours(0);
        this._date.setMinutes(0);
        this._date.setSeconds(0);
        this._date.setMilliseconds(0);
    },

    $update: function() {
        this._d1._date = new Date('2017-11-08');
        this._d2._date = new Date('2017-11-09');
    },

    $components: [
        {
            _date: null,

            $update: function() {
                console.log('from d1', this._date);
            }
        },
        {
            _date: null,

            $update: function() {
                console.log('from d2', this._date);
            }
        }
    ]
};

Here everything is fine it logs 'from d1' and 'and from d2'

but with this one things don't work the same way:

var test = {
    _date: null,

    $cell:true,

    $init: function() {
        this._d1 = this.querySelector('div');
        this._d2 = this.querySelector('div:last-child');

        this._date = new Date(Date.now());
        this._date.setHours(0);
        this._date.setMinutes(0);
        this._date.setSeconds(0);
        this._date.setMilliseconds(0);
    },

    $update: function() {
        this._d1._date = new Date('2017-11-08');
        this._d2._date = new Date('2017-11-09');
    },

    $components: [
        {
            _date: null,

            $init:function() {},

            $update: function() {
                console.log('from d1', this._date);
            }
        },
        {
            _date: null,

            $init:function() {},

            $update: function() {
                console.log('from d2', this._date);
            }
        }
    ]
};

In this example it only pass in the 'from d2' What am i getting wrong?

i include celljs from this link: https://www.celljs.org/cell.js

Regards

gliechtenstein commented 7 years ago

Thanks for reporting, this was happening because of the order in which the nodes were getting initialized via $init. I fixed it so that the nodes get initialized in the order you would normally expect: https://github.com/intercellular/cell/pull/156

I also merged it to develop so please try using the latest develop branch version. (You can try this too: https://rawgit.com/intercellular/cell/develop/cell.js)

That said, I want to point out that you should avoid all these $inits and $updates as much as possible. The whole point of Cell is that it gets rid of all the boilerplate code that you're forced to write in other "reactive" frameworks. I say this because I see a lot of confused people trying to use Cell like they use other frameworks.

Here's what this means in practice:

if you want to build a node with a text "Hello world",

instead of:

var n = {
  $cell: true,
  $init: function() {
    this.$text = "Hello World"
  }
}

you should just do

var n = {
  $cell: true,
  $text: "Hello World"
}

This approach is native to how the DOM actually works. You're basically writing a blueprint JSON for your DOM tree, and this blueprint should avoid all these complex $init() callbacks and $update() callbacks as much as possible.

So when do you use them?

In case of $init, you use it if you want to apply some logic AFTER the node is drawn to the screen. For example you may want to apply a jQuery plugin or some javascript library to your node. Here's an example: https://play.celljs.org/items/yqefWy/edit

In case of $update, you use it ONLY when you want to actually "update". I think this is much more intuitive and native way of building web apps, you don't need to write all the boilerplate code to make your app "reactive". With Cell, when it says "$update" it literally means update. Here's an example:

var caption = "Hello World";
var n = {
  $cell: true,
  $type: "a",
  _counter: 0,
  $update: function() {
    this.$text = "Clicked " + this._counter + " times";
  },
  $text: "Click me",
  onclick: function() {
    this._counter++;
  }
}

Notice how we're directly setting the $text attribute, no $inits necessary, and you only use $update to respond to actual updates of _variables.

In fact you don't even really need to use the $update callback, you can directly modify the node from any function if you want, like this (demo here https://jsfiddle.net/bnLw8htg/3/):

var caption = "Hello World";
var n = {
  $cell: true,
  $type: "a",
  _counter: 0,
  $text: "Click me",
  onclick: function() {
    this._counter++;
    this.$text = "Clicked " + this._counter + " times";
  }
}

Notice how there's no $init nor $update. This is a perfectly fine usage of Cell.

The reason we have $update() callback is because above approach doesn't scale for more complex apps, because then we would be making view updates everywhere and would be hard to keep track. The $update callback provides a nice interface where you can aggregate all view update related methods and update all at once in a single tick.

Hope this makes sense.

gliechtenstein commented 7 years ago

Anyway, closing this, since I think it's fixed. Please reopen if not. Thanks!