Search code examples
javascriptmithril.js

Mithril: render component on event/dynamically


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?


Solution

  • 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);