0

I have a long list of items that I want to show in a <ul>. I want to add a "filter" input, so the user can narrow down the list of items to those matching the filter.

My controller contains a filter prop and a list array:

function Ctrl() {
    this.filter = m.prop('');
    this.list = [];
}

I've added an update method to the controller, which looks at the filter prop and updates the contents of the list array:

Ctrl.prototype.update = function (value) {
    var _this = this;
    if (this.filter()) {
        searchItems(this.filter(), function (items) {
          _this.list = items;
        });
    } else {
        this.list = [];
    }
};

Finally, my view iterates over the list array and renders the items. Additionally, it displays an input on top, bound to the filter prop:

var view = function (ctrl) {
    return m('#content', [
        m('input', { 
             oninput: m.withAttr("value", ctrl.filter), 
             value: ctrl.filter() 
        }),
        m('ul', [
            ctrl.list.map(function (item, idx) {
                return m('li', m('span', item.getName()));
            })
        ])
    ]);
};

My question is, how to make the update function fire when the value of filter changes, so that I get the updated list of items?

Do I need to position two oninput events? One to update filter and one to fire update?

Should I use a single oninput event and update the filter property within he update function?

Anything else?

miniml
  • 1,489
  • 2
  • 17
  • 27
  • 1
    You should take a look on the example given in the doc: http://lhorie.github.io/mithril-blog/organizing-components.html It explains exactly what you are doing: filtering a list. – fluminis Dec 08 '15 at 14:54

1 Answers1

0

When you use m.withAttr, what you're saying is that when the event handler fires (oninput), you will take some attribute of the element (value) and pass it into your second argument, which is a function (ctrl.filter). Your current sequence of events:

  1. filter property gets updated
  2. mithril redraws

What you want to do, is call the update function (instead of the getter/setter ctrl.filter function), and bind it so you can retain the proper context in the function:

m('input', {
  oninput: m.withAttr("value", ctrl.update.bind(ctrl)), 
  value: ctrl.filter() 
}),

Then, in your update function the value will be passed to the function and you can set it there.

Ctrl.prototype.update = function (value) {
  this.filter(value);
  ...

Now what'll happen:

  1. ctrl.filter property gets updated
  2. ctrl.list gets filtered based on ctrl.filter
  3. mithril redraws

Another way to handle this is to not have any "list" property in your controller / model, but to let the view grab a filtered list instead. There's only one thing really changing, after all, and that's the "filter" property. The filtered list is derived from that, so by creating another property on the controller, you're effectively duplicating the same state.

Additionally, you could keep m.withAttr('value', ctrl.filter) and benefit from that simplicity.

Something like:

var filteredItems = ctrl.getFilteredItems();
var view = function (ctrl) {
    return m('#content', [
        m('input', { 
             oninput: m.withAttr("value", ctrl.filter), 
             value: ctrl.filter() 
        }),
        m('ul', [
            filteredItems.map(function (item, idx) {
                return m('li', m('span', item.getName()));
            })
        ])
    ]);
};
dcochran
  • 1,055
  • 2
  • 9
  • 15
  • Thank you for your answer. However, your first example makes `filter` dependent on `update`, instead of the other way around. So, if I change the value of `filter` somewhere further down in the code, the values of `list` will not update Your second example is a bit better in my case, but it doesn't allow me to construct a list of items that contain metadata. It binds me to using the data as provided by the source (ajax get or whatever). – miniml Dec 08 '15 at 21:26
  • Hmm, I would recommend you read up on the docs a bit more, specifically the link fluminus provided: http://lhorie.github.io/mithril-blog/organizing-components.html – dcochran Dec 08 '15 at 21:52
  • You're right that list would not update if you only updated filter, you'd have to call the update function. That's why I recommended not duplicating state and just using the filter to filter the list within the view. That's what the mithril example in that link does. And you can still "construct a list of items that contain metadata" -- you would just write that functionality in "getFilteredItems". – dcochran Dec 08 '15 at 21:59