Skip to content

Tutorials

Bryce edited this page Jun 9, 2016 · 29 revisions

Mithril & CoffeeScript Tutorial

Introduction

This page is a tutorial for starting to develop with CoffeeScript and Mithril for our application. For those of us new to CoffeeScript and Mithril, the challenges inherent with learning a new language, and a new framework can lead to more than a few headaches. The target audience is for this is a new Coop, with experience programming, but not necessarily any experience with Mithril CofeeScript, or webapp development.

The reason we are writing a wiki article is because you are going to have a great time coding if you follow this mindset:

It is far easier to work with legacy code when you use the same coding conventions. I will do this, and thus I will be able to understand how they worked more easily.

I won't promise that we do things in the most efficient way possible, but we will try to at least be consistent.


Lesson 1: Getting Started

You should have an understanding of the following topics in order to get a base of knowledge to work with for this lesson:

*The links provided may or may not conform to the styles we are using in SuperPhy.

Terminology:

@ is the CoffeeScript version of JavaScript's this keyword. So when CoffeeScript is compiled into JavaScript, @ becomes this.

When we refer to a static component. It is a component that doesn't have a controller to deal with interactions. All the the data displayed in the view is either passed in as arguments from a parent component, or is static data in the class. See this lesson's exercise for an example of that.

If you want to start testing things out, an empty project with the correct JSFiddle settings are linked above.

Exercise: Hello World!

This exercise will show you how to make the simplest implementation of a Mithril view. This simply populates the document.body DOM element with a div containing a string "Hello World", when the user is at the root of the URL.

m.route(document.body, "/"
    "/": view: -> m('.', "Hello World!")
)

Lesson 2: Separation of Responsibilities.

You should have an understanding of the following topics in order to get a base of knowledge to work with for this lesson:

*The links provided may or may not conform to the styles we are using in SuperPhy.


We won't get far using just the m.route method. In this lesson we are going to make a component class, and a model class. This will designate distinct responsibilities for different components.

Let's make a name model:

class Name
    constructor: (name) ->
        this.name = name or "Bryce"
        return
    greet: () ->
        return  "Hello #{@name}!"

Since you read the links, you know that this class has two instance methods constructor and greet. You know you can have multiple instances of different Names, and you know how to make a new Name(). This is what a model is in our application. You probably have some familiarity with this format from school. This particular example has no Mithril in it, but models can and will have Mithril.

Next, let's make a Component that uses our Name model.

class Hello
    @controller: () ->
        @me = new Name()
        @you = new Name("Coop Student")
        return this
    @view: (ctrl) ->
        m('.'
          m('.', ctrl.you.greet())
          m('.', ctrl.me.greet())
        )

m.route(document.body, "/"
    "/": Hello
)

Again, you read the links and know that the @methods are class methods. This is similar to having a dictionary object with functions as it's values, and keys as it's method name, but it allows us to use inheritance as well.

All views return mithril, not strings or lists, but one mithril object.

All view

Lesson 3: Routing Object.

How do I define a route?

How do I go from page to page?

Routes = {}
Routes.add = (route, class_, args) ->
    args = args || {}
    Routes.routes ?= {}
    if route.substring(0,1) is '/'
        route = route.substring(1)
    Routes.routes["/#{route}"] = view: -> m.component(class_, args)

class Hello
    Routes.add("/Hello", this)
    class Name
        constructor: (name) ->
            this.name = name or "Bryce"
            return
        greet: () ->
            return  "Hello #{@name}!"

    @controller: () ->
        @me = new Name()
        @you = new Name("Coop")
        return this
    @view: (ctrl) ->
        m('.'
            m('.', ctrl.you.greet())
            m('.', ctrl.me.greet())
        )

class Api
    Routes.add('/', this)
    @view: (ctrl, args) ->
        m('.'
            m('.', key) for key in Object.keys(Routes.routes).sort()
        )

m.route(document.body, "/", Routes.routes)

Lesson 4: Inheritance

Routes = {}
Routes.add = (route, class_, args) ->
    args = args || {}
    Routes.routes ?= {}
    if route.substring(0,1) is '/'
        route = route.substring(1)
    Routes.routes["/#{route}"] = view: -> m.component(class_, args)

class SuperComponent
    @view: (args) ->
        args = args or {}
        m('.', "This is From the SuperComponent", args)

class SubComponent extends SuperComponent
    Routes.add('/', this)
    @controller: () ->
        @data = m.prop('')
        return this
    @view:(ctrl, args) ->
        return super(
            m('.', "This is From the SubComponent", args)
        )

m.route(document.body, "/", Routes.routes)

Lesson 5: Promise Objects.

Mithril - Auto-redrawing

There are two ways to bind data asynchronously into the model:

  • User input
  • Ajax

Both of these methods are handled by the mithril promise object: m.prop()

In the following example, we have a fake Ajax request, and a field that you can fill out. Both of these are going to be behind an object that sets and gets the data.

Routes = {}
Routes.add = (route, class_, args) ->
    args = args || {}
    Routes.routes ?= {}
    if route.substring(0,1) is '/'
        route = route.substring(1)
    Routes.routes["/#{route}"] = view: -> m.component(class_, args)

fake_request = () ->
    return m.prop({
        headers: ['a','b','c']
        rows: [
            {'a':"Foo", 'b':"Bar", 'c':"Bing"}
            {'a':"Alpha", 'b':"Beta", 'c':"Gamma"}
            {'a':"Lorem", 'b':"Ipsum", 'c':"Dolor"}
        ]
    })

class Promises
    Routes.add('/', this)
    @controller: (args) ->
        @data = fake_request()
        @name = m.prop("")
        return this
    @view: (ctrl) ->
        return m('.'
            m('table', {border:"1"},
                m('tr', m('th', header) for header in ctrl.data().headers)
                m('tr', m('td', row[header]) for header in ctrl.data().headers
                ) for row in ctrl.data().rows
            )
            m('input[type=text]'
                oninput:m.withAttr('value', ctrl.name)
                value:ctrl.name()
                placeholder:"Name"
            )
            m('.', "Name = #{ctrl.name()}")
        )

m.route(document.body, "/", Routes.routes)

Lesson 6: Requests

How do I get data from a database? (model How do I change/update some data?

Clone this wiki locally