-
Notifications
You must be signed in to change notification settings - Fork 3
Tutorials
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.
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.
@
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!")
)
You should have an understanding of the following topics in order to get a base of knowledge to work with for this lesson:
- CoffeeScript Class and Instance Methods
- Mithril 103 - Components (Specifically Classic MVC)
*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 Name
s, 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: (args) ->
@me = new Name()
@you = new Name("Coop Student")
return this
@view: (ctrl, args) ->
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. The whole result 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. When you see this other style of component in the mithril.js tutorial pages, you will now know the equivalent for us in CoffeeScript.
All views return one mithril virtual element. It will not work to return strings or even lists of mithril virtual elements.
Not all controllers return themselves, but most do in our application. We are able to build a dictionary object out the the controller itself. It is also perfectly valid to return a model if that is the only model being used, but for clarity purposes, this is how we makes controllers.
When you tell mithril to try to make a virtual element out of your class, it looks for the two class methods @view
and @controller
. It goes through the following process:
- It tries to run
@controller
if it exists, passing in any arguments that you give it (args
). - It passes the return of
@controller
to the@view
as(ctrl)
, as well as the arguments it tried to give to controller. (args
). - It returns the virtual element object from the view.
When you see a an@view
, or an @controller
, you know that it is a component class.
All view
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)
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)
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)
How do I get data from a database? (model How do I change/update some data?