Skip to content

Latest commit

 

History

History
426 lines (322 loc) · 17.8 KB

README.md

File metadata and controls

426 lines (322 loc) · 17.8 KB

About Mithril Coat

Mithril Coat is a minimalistic frontend web framework that builds on Mithril. Mithril Coat provides two tools that are meant to be used in conjunction with each other.

  • A lightweight library around Mithril that provides code structure and a flux like architecture using pubsub-js.
  • A templating language that uses HTML tags that compiles to Mithril templates.

Mithril Coat requires jQuery and is expected to be used in conjunction with Browserify (we provide a browserify plugin, Coatify to require Mithril Coat templates via Browserify).

NOTE: the documentation assumes a familiarity with Mithril and terminology surrounding Mithril.

Install

To install the front-end package via bower bower install Mithril-coat.

To install the template compiler npm install Mithril-coat -g.

Mithril Coat

Mithril Coat is composed of three primary objects (View, Controllers, and Models) as well as some utility functions. Views use Model properties to correctly display the correct html through a Mithril Coat template. Views also listen for dom events and using Mithril Coat's global pubsub system, publish that a dom interaction has occcured. Controllers can listen to specific events that the views publish and determine whether a model should be manipulated and whether an autoredraw should occur. This allows Mithril Coat to have a flux like architecture and allows for Mithril Coat to have "stateless" controllers (since controllers just need to know they are manipulating a model, but not which specific model).

Mithril Coat also provides some utility functions for routing and initializing Mithril components.

Here are a few notions around Mithril Coat:

  • Views should never manipulate Models
  • Models should only be manipulated by Controllers
  • Views should not call methods on Controllers and vice versa
  • Views and Controllers should interact via Mithril Coat's pubsub system
  • Mithril autoredraw function should only be called from a Controller

NOTE: Views, Controller, and Models all follow prototypical inheritance. They all accept an object when initialized. All key, values of that object will be bound as properties of that Object.

Views

Mithril Coat Views are very similar to Backbone and provide a very simple interface to work with dom events. Mithril Coat has a notion of 2 different types of views:

  1. Base Views - coat.View
  2. Templated Views - coat.TemplatedView

new coat.View({})

Base Views provide a number of convenience methods for interacting with existing dom nodes.

All views expect to be initialized with a $el key and value.

var view = new coat.View({ 
    $el: $("body")
})

coat.View.prototype.domEvents()

Dom events takes a similar approach to Backbone dom events and serves as a way to have event delegation on the view's $el.

domEvents returns a mapping of "[events] [selector]": "functionOnView".

view.prototype.domEvents = function() {
    return {
        "click a": "onClickLink"
    }
}

view.prototype.onClickLink = function(e) {
    console.log('linked clicked')
}

coat.View.prototype.$(jquerySelector)

returns a jQuery selector that match the selector inside the $el.

new coat.TemplatedView({})

Extends coat.View and adds additional functionality for views that use Mithril templates.

All coat.TemplatedView expect a template property on instantiation. This will be the template that the view renders.

If you want to pass model data to a view it should be done via the state property which is exposed to the Mithril Coat template.

Some things to note about Mithril Coat templated views:

  • each Mithril Coat template is wrapped in a div generated by Mithril Coat.
  • Mithril Coat ensures that events are cleaned up on each redraw.
  • Mithril Coat templates have a view tag to generate subviews, templated views clean up their subviews and all the events for these subviews.
  • for views that are part of an SPA it is not necessary to pass in an $el, however for views that use coat.initModule() to initialize a Mithril Coat component it's necessary to pass an $el to the view object.
var sampleTemplate = require("./template.coat")
var templatedView = new coat.TemplatedView({
    $el: $("body"),
    template: sampleTemplate,
    state: new coat.Model({
        name: "daily muse",
        version: "1.0.1"
    })
})

coat.TemplatedView.prototype.render()

Should be called to render the Mithril template.

coat.TemplatedView.prototype.config(element, isInit, context)

The config method is called whenever the current template is rendered to the page.

NOTE: these arguments are the same as Mithril passes to the config attribute.

coat.TemplatedView.prototype.onunload()

The current templated view was unloaded. Can be used if you need to call methods on a third party library when a view is unloaded.

Controllers

Mithril Coat controllers are meant to be used to manipulate Models and to initiate Mithril redraws (either via Model requests or via autoredraws).

new coat.Controller({})

Controllers do not take any default arguments.

var controller = new coat.Controller({
    model: new coat.Model({
        url: "api/confirm"
    })
})

coat.Controller.prototype.events()

Events are bound to a module using Pubsub-js, which is a global pubsub system and therefore events are not Module specific. Modules expect the events to return an object mapping of event names to functions to call when the event is published.

Controller events can be published via coat.publish('some-event-name') or coat.publishSync('some-event-name'). To see the differences between publish and publishSync please checkout pubsub-js. Views should typically be the ones that publish controller events, although there are exceptions (like when a controller wants to interact with another controller, or in a components controller function).

controller.prototype.events = function() {
   return {
      "on-button-clicked": this.buttonClicked
   }
}

controller.prototype.buttonClicked = function() {
   console.log('called when a view publishes an event via coat.publish("on-button-clicked")');
}

coat.Controller.prototype.autoredraw(cb, opts)

Redraws a view using Mithril's startComputation() and endComputation in a try, finally block as recommended by Mithril. Calls the callback and passes opts as an argument to the callback.

// inside some function in a controller

controller.prototype.buttonClicked = function() {
   this.autoredraw(function(opts) {
       console.log(opts);
   }, opts);
}

Models

Mithril Coat models provide convenience methods for interacting with Mithril models. And dealing with state thorughout your application.

new coat.Model({})

All keys and values that are passed in the opts object are set as properties on the Model as Mithril props respectively.

var model = new coat.Model({
    name: "Mithril-coat",
    version: 1.0,
});

console.log(model.name()) // prints "Mithril-coat" to the console

There are also some keys that are available on every model:

  • this.modelKeys is a list of all keys that are in the object passed into the constructor. All json keys that is returned from model requests are also added to the modelKeys list.
  • this.loading() a boolean Mithril prop that indicates that the model is currently being requested. Mithril Coat automatically sets this the value of this property. It can be used to show a loading spinner in a templated view.
  • this.requestError() a boolean Mithril prop that indicates whether there was a request error

model.setProps({})

Method to set properties on the model. Accepts a key value object that are set as Mithril props on the model.

model.setProps({
   updatedVersion: 2.0
});

console.log(model.updatedVersion()) // prints 2.0

model.getProps()

Returns all the Mithril properties on the model as an object mapping key to value.

model.url || model.url()

the url can be set as a property on the Model oras a function. It should return a url string to request

model.prototype.url = function() {
    return "/api/Mithril-coat/" + this.version();
}

model.xhrConfig(xhr)

This function should be extended if it is necessary to configure the xhr request.

model.prototype.xhrConfig = function(xhr) {
    // in order to avoid caching issues in ie10
    xhr.setRequestHeader("Content-Type", "application/json");
}

Model Requests

In addition to allowing you to set the underlying xhr requests - Mithril Coat provides a few other convenience methods. All method calls accept an object of key values. Possible keys that Mithril Coat will place in the options m.request include:

  • "user"
  • "password"
  • "data" - data to be submitted (expected to be an object of key value mappings)
  • "background"
  • "initialValue"
  • "unwrapSuccess"
  • "unwrapError"
  • "serialize"
  • "extract"
  • "type"

Mithril Coat also allows you to pass in success and error keys that are mapping to function callbacks. Each callback receives two arguments success: function(response, model) where the response is the response from the server and the model is the current requested model.

model.get({}})

Submits a get request and optionally specifies the method to use in opts

model.get({
    success: function(response, model) {
        console.log(response)
    }, 
    error: function(error, model) {`
        console.log(error)
    }
})

model.save({})

By default if a "method" key is not set in opts and an id is set on the model then Mithril Coat will set the method request to "PUT" and if there is not id it will set the method request to "POST". However there are times when you should submit a "PUT" request even though an id exists - therefore we provide the flexibility to determine which method to use.

model.delete({})

Submits a delete request to the server.

Routing and Modules

Mithril Coat provides some nice utility methods that wrap around Mithril's routing and components.

coat.setRoutes($rootEl, routes)

Sets the Mithril Coat routes for the single page application. Mithril Coat sets Mithril to route on the pathname.

$rooEl is a jQuery object in which the the Mithril templates will be rendered. routes should be an object mapping route names to Mithril a component ({controller: function() {}, view: () -> console.log('render some view here via view.render()')}).

template = require("./some_template.coat")

view = new coat.TemplatedView({
   template: template,
   state: new coat.Model({
      name: "The Muse"
   })
});

coat.setRoutes($("body"), {
   "/": {
      controller: function() {
         console.log('take some actions that need to be taken on each route update')
      }, view: function() {
         view.render();
      }
   }
})

coat.getParams()

Gets the current query parameters in the url. This function can only be called after coat.setRoutes has been called. Returns a mapping of query parameter keys to their respective values.

coat.updateRoute(route, params, shouldReplaceHistory)

Routes to the new route and the query parameters listed.

route should be a string representing the new path. params should be an object mapping of query parameter keys to values. shouldReplaceHistory is a boolean whether the history should be replaced.

coat.updateParams(params, shouldReplaceHistory)

Is a convenience method that allows you to update a select group of query parameters. Sometimes you may have 10+ query parameters, but you only are routing to update a value or two. Furthermore you may not want to have to deal with the existing params object. This is just a convenience method that can be used to update select query parameters.

params should be an object mapping of query parameter keys to values that you want to update. shouldReplaceHistory is a boolean whether the history should be replaced.

coat.initModule(view, controllerCb)

A convenient wrapper to initialize a mithirl component.

    model = new coat.Model({
        url: "api/some-request"
    })

    view = new coat.TemplatedView({
        $el: $("body"),
        state: model
    })

    /*
    @param [coat.TemplatedView] view a Mithril Coat view that's used to mount the 
    Mithril component
    @param [Function] controllerCb a controller callback that's called in the 
    Mithril component controller
    */
    coat.initModule(view, function() {
        console.log('some action to take before the view is rendered')
        model.get({})
    });

Mithril Coat Templates

Mithril Coat templates are Mithril templates that are written in HTML. The files are saved with a .coat extension and are then compiled to JavaScript by the Mithril Coat compiler.

Mithril Coat templates are simply html templates that are compiled to Mithril. The templating language is expected to be used in conjunction with Browserify as it module exports all the templates. You can use the Coatify plug-in to require Mithril Coat Templates.

All Mithril Coat templates receive two variables: (view, state). view is a reference to the view object that has the template so any property on the view is available to you via the view object. state is the state that was passed in the view on initialization. state is the place where any model or ui data should exist.

Mithril Coat template support all html tags and have an additional 7 html tags

* if, elif, else
* val
* raw
* map
* nbsp
* template
* view

<if expr=""></if>, <elif expr=""></elif, <else expr=""></else>

Every if and elif tag need to have an expr attribute on it and the value of expr should be a JavaScript expression. All if, elif, and else tags need to be closed. All tags must also have valid content placed in between their tags.

<!-- remember state is a varibale that's passed into a Mithril-template from the view -->
<if expr="state.window()">
    <h1>The Window objext exist</h1>
</if>
<elif expr="state.hello().length > 0">
    <h1>Hello World</h1>
</elif>
<else>
    <h1>In the else</h1>
</else>

<!-- 
NOTE THIS IS WILL FAIL COMPILATION IN Mithril Coat because there are no contents in between 
<if expr="state.window">

</if>
-->

<val expr="" />

val is a self closing tag that evaluates a JavaScript. The only attribute it accepts is expr.

<!-- 
would evaluate the JavaScript state.hello() expression and place the value in 
the paragraph tag
-->
<p>
    <val expr="state.hello()" />
</p>

<raw val||expr="" />

raw wraps a string or a JavaScript expression in Mithril.trust. The two different attributes are val and expr.

val accepts a JavaScript expression, so if state has a property message: "hello world <span>The Muse</span>", valshould be used to displaystate.message()`.

expr should be used to display a string such as html character entities.

<!-- let's stay state.message = coat.prop("hello world <span>The Muse</span>") -->
<p>
<!-- To display html content in a JavaScript expression -->
    <raw val="state.message()" />
</p>
<span>
    <raw expr="&excl;" />
</span>

<map expr="", key="", val=""></map>

Allows for iterating over an object or an array.

If you are iterating over an object key is the current key in the iteration and val is the current value of that key.

If you are iterating over an array key is the current index and val is the current value of that index.

<!-- if state.numbers = coat.prop([1, 2, 3, 4]) -->
<map expr="state.numbers()" val="num" key="index">
    <p>Value is: <val expr="num" /> at index: <val expr="index" /></p>
</map>

<!-- if state.person = coat.prop({name: "The Muse", age: "3"}) -->
<h2> The Properties of The Muse are: </h2>
<map expr="state.person()" val="prop" key="key">
    <p><val expr="key" />: <val expr="prop" /></p>
</map>

<nbsp count="" />

Add the &nspb; character the number of times specified in the count property.

<!-- will add an nbsp HTML character 5 times -->
<nbsp count="5" />

<template path="[pathName]" />

Allows other templates to be included as partials in your current template. The path value should not include a file extension and the paths should be relative to the current directory.

<!-- assuming there is another file in the same directory name 'hello_world.html' - note there are two ways that are both valid to include this file, the one above -->
<template path="hello_world" />
<template path="./hello_world" />

<view name="" args="{}" />

Views are a means to include other templated views in the current view. There are several reasons why you might want to do this including having event binding correctly for sub-views and to allow sub-views to only have access to the states that they "control."

<!-- assuming that the `view` object has a reference to the templated view that we want to include and that the templated class is assigned to the `subview` property. Also let's assume that `state` has a property called `subview` that we want to pass as the state for the sub-view. Then we can generate the sub view with the following code. -->
<view name="view.subview" args="{state: state.subview}" />

The $el for the view is automatically created and bounded by Mithril Coat.