chifer chifer - 26 days ago 9
Javascript Question

Mithril components - bounded controllers

I have a mithril component with an controller which is already bounded to an context, if I use m.component(), mithril disregards the bounded controller and supplies the view with an default empty controller

UserWidget = function(){
this.allUsers = User.load();
this['header'] = {
'controller' : function(users){
this.users = users;
}.bind(this, this.allUsers),
'view' : function(ctrl) {
console.log('ctrl', ctrl)
if (ctrl.users()) {
return m('.user', ctrl.users()[0].name());
}
}
}
}

//initialize
m.module(document.body, m(new UserWidget().header));


However if I pass view/controller via m.module everything works as expected

m.module(document.body, new UserWidget().header);


https://jsfiddle.net/chifer/mwddffy4/2/

Is it a caveat that component controllers should be unbounded and passed params via the m.component call? or is this a bug?

Answer

Is it a caveat that component controllers should be unbounded and passed params via the m.component call?

Yes. Controllers are invoked as constructors with the new keyword, which means this (and arguments passed in) cannot be bound to it.

Your code can be simplified by avoiding this and binding internally:

UserWidget = function(){
  var users =  User.load();

  return {
    'view' : function() {
      if (users())
        return m('.user', users()[0].name());
    }
  }
}

//initialize
m.module(document.body, m(UserWidget()));

Fiddle here.

But in practice, this code is replicating the functionality that's already built in to controllers — to quote from the Mithril API documentation for components:

The optional controller function creates an object that may be used in the following recommended ways:

[...]

  • It can store contextual data returned from model methods (i.e. a promise from a request).
  • It can hold a reference to a view model.

Basically, your original application code involves a constructor that makes a request and stores a reference to the returned promise, and this is exactly what controllers are for. So you can avoid writing any intermediary functions or constructors of your own and bake all that functionality into the component structure itself:

UserWidget = {
  'controller' : function(){
    this.users = User.load();
  },
  'view' : function(ctrl) {
    if (ctrl.users())
      return m('.user', ctrl.users()[0].name());
  }
}

//initialize
m.module(document.body, UserWidget);

Fiddle here.