mithril-coat
v0.2.3
Published
A minimalistic frontend web framework that builds on Mithril
Downloads
58
Maintainers
Readme
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:
- Base Views - coat.View
- 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 usecoat.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 display
state.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="!" />
</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.