Mithril Tips & Tricks 1

  • Apr 8

There are many javascript framework doing bi-directional data binding such as Angular.js. But many of them are too heavy to use with Rails. It creates a lot of redundancy. Fortunately, Mithril is an light-weight framework which suits Rails very well. Here is a CoffeeScript version of Mitrhil todo which you can start with. Just add a div#mithril-todo in one of your view and you are ready to go.

todo = {};
todo_element = null;

todo.Todo = (data) ->
  this.description = m.prop(data.description);
  this.done = m.prop(false);
  this

todo.TodoList = m.prop([])

todo.vm = (->
  vm = {}
  vm.init = ->
    vm.list = todo.TodoList;
    vm.description = m.prop("");

    vm.add = ->
      if vm.description()
        vm.list.push(new todo.Todo({description: vm.description()}));
        vm.description("");

  return vm
)()

todo.controller = ->
  todo.vm.init()

todo.view = ->
  return m("div", [
    m("ul", [
      todo.vm.list().map (task, index)-> 
        return m("li", [
            m("input[type=checkbox]", {onclick: m.withAttr("checked", task.done), 
checked: task.done()})
            m("span", {style: {textDecoration: task.done() ? "line-through" : "none"}}, task.description
()),
          ])
    ]),
    m("input", {onchange: m.withAttr("value", todo.vm.description), value: todo.vm.description()}),
    m("button", {onclick: todo.vm.add}, "Add")
  ]);


loadMithrilTodo = -> 
  todo_element = document.getElementById('mithril-todo')
  if todo_element
    m.module(todo_element, {controller: todo.controller, view: todo.view});
    todo_id = todo_element.getAttribute('data-todo');
    m.request({
      method: 'GET', 
      url: '/todos/'+todo_id+'.json',
      type: todo.Todo,
      unwrapSuccess: (response)->
        return response.todos;
      unwrapeError: (response)->
        return response.error;
    }).then(todo.TodoList).then((data) ->
      console.log(data);
    )

# initialize the application
$(document).ready(loadMithrilTodo);
$(document).on('page:load', loadMithrilTodo);

You might need some modification for your application. While it works fine, how do I sent the change of task back ? The action is here:

m("input[type=checkbox]", {onclick: m.withAttr("checked", task.done),

And you can easily substitute task.done with any function like this:

m("input[type=checkbox]", {onclick: m.withAttr("checked", todo.vm.toggle),
...
todo.vm = (->
  vm = {}
  vm.init = ->
    ...
    vm.toggle = (data) ->
      console.log(data);
  return vm
)()

But you will find that todo.vm.toggle will receive only true and false. How do you know which task is checked ?

You can find the voodoo like this:

m("input[type=checkbox]", {onclick: m.withAttr("checked", todo.vm.toggle.bind(this, task)),
...
todo.vm = (->
  vm = {}
  vm.init = ->
    ...
    vm.toggle = (task, data) ->
      task.done(data)
      # do something here
  return vm
)()

As you can see, you can not only set the task.done, but also have chance to do something else, such as post the result back to Rails.