2

Trying to get the hang of Mithril, can't really understand one thing. Can I render components on events?

Let's assume I have one parent component:

var MyApp = {
    view: function() {
        return m("div", [
            m.component(MyApp.header, {}),
            m("div", {id: "menu-container"})
        ])
    }

};

m.mount(document.body, megogo.main);

It renders the header component (and a placeholder for the menu (do I even need it?)):

MyApp.header = {
    view: function() {
        return m("div", {
            id: 'app-header'
        }, [
            m('a', {
                href: '#',
                id: 'menu-button',
                onclick: function(){
                    // this part is just for reference
                    m.component(MyApp.menu, {})
                }
            }, 'Menu')
        ])
    }
}

When the user clicks on the menu link I want to load the menu items from my API and only then render the menu.

MyApp.menu = {
    controller: function() {
        var categories = m.request({method: "GET", url: "https://api.site.com/?params"});
        return {categories: categories};
    },
    view: function(ctrl) {
        return m("div", ctrl.categories().data.items.map(function(item) {
            return m("a", {
                href: "#",
                class: 'link-button',
                onkeydown: MyApp.menu.keydown
            }, item.title)
        }));
    },
    keydown: function(e){
        e.preventDefault();
        var code = e.keyCode || e.which;
        switch(code){
            // ...
        }
    }
};

This part will obviously not work

onclick: function(){
    // this part is just for reference
    m.component(MyApp.menu, {})
}

So, the question is what is the correct way render components on event?

3 Answers3

2

First of all, you'll want to use the return value of m.component, either by returning it from view, or (more likely what you want) put it as a child of another node; use a prop to track whether it's currently open, and set the prop when you wish to open it.

To answer the actual question: by default Mithril will trigger a redraw itself when events like onclick and onkeydown occur, but to trigger a redraw on your own, you'll want to use either m.redraw or m.startComputation / m.endComputation.

The difference between them is that m.redraw will trigger a redraw as soon as it's called, while m.startComputation and m.endComputation will only trigger a redraw once m.endComputation is called the same amount of times that m.startComputation has been called, so that the view isn't redrawn more than once if multiple functions need to trigger a redraw once they've finished.

Raymond H.
  • 21
  • 2
2

Try This: http://jsbin.com/nilesi/3/edit?js,output

You can even toggle the menu.

And remember that you get a promise wrapped in an m.prop from the call to m.request. You'll need to check that it has returned before the menu button can be clicked.

// I'd stick this in a view-model
var showMenu = m.prop(false)

var MyApp = {
    view: function(ctrl) {
        return m("div", [
            m.component(MyApp.header, {}),
            showMenu() ? m.component(MyApp.menu) : ''
        ])
    }

};

MyApp.header = {
    view: function() {
        return m("div", {
            id: 'app-header'
        }, [
            m('a', {
                href: '#',
                id: 'menu-button',
                onclick: function(){
                  showMenu(!showMenu())
                }
            }, 'Menu')
        ])
    }
}

MyApp.menu = {
    controller: function() {
        //var categories = m.request({method: "GET", url: "https://api.site.com/?params"});
      var categories = m.prop([{title: 'good'}, {title: 'bad'}, {title: 'ugly'}])
        return {categories: categories};
    },
    view: function(ctrl) {
        return m("div.menu", ctrl.categories().map(function(item) {
            return m("a", {
                href: "#",
                class: 'link-button',
                onkeydown: MyApp.menu.keydown
            }, item.title)
        }));
    },
    keydown: function(e){
        e.preventDefault();
        var code = e.keyCode || e.which;
        switch(code){
            // ...
        }
    }
};

m.mount(document.body, MyApp);
Dave Newton
  • 158,873
  • 26
  • 254
  • 302
pelón
  • 393
  • 2
  • 5
  • Totally unrelated, but I'd probably just `showMenu(!showMenu())`. I'm just now evaluating Mithril for a project and this is great input. – Dave Newton Oct 28 '15 at 15:36
0

See the custom component,

var Example = {
    view: function (vnode) {
        return m("div", "Hello")
    }
}

When you need some dynamic data in the component use attributes that are different from life cycle methods, like,

m(Example, {name: "Floyd"})

An in the component, use it as,

var Example = {
    view: function (vnode) {
        return m("div", "Hello, " + vnode.attrs.name)
    }
}

Reference
https://mithril.js.org/components.html#passing-data-to-components

Henshal B
  • 1,540
  • 12
  • 13