Espresso.js is a tiny MVC framework inspired by Backbone and React with a focus on simplicity and speed.
We've aimed to bring the ideas of unidirectional data flow of Flux to a simple, Backbone-style library.
If you're using Browserify or Node/CommonJS, simply install the package:
$ sudo npm install --save espresso.js
Alternatively grab the standalone version that you can import with a <script> tag or checkout the GitHub Repo.
Now dive in and check out the To-Do Example app in action or read the source code.
A view is a DOM Node. For example:
<div class="commentBox">
<h1>Comments</h1>
<div data-ref="commentList">
<div data-ref="comment">
<a style="float:right" href="#" data-ref="remove">[x]</a>
<h2 data-ref="author"></h2>
<span data-ref="html"></span>
</div>
</div>
</div>
Being a DOM node, there's no string interpolation to be done at runtime, nor does the browser need to parse the HTML from a string. It also means designers can actually mockup a site which you can make interactive without messing with templating languages.
A data-ref is a special attribute that allows us to refer to that node by name in the Controller.
A controller is the mediator between model and view. You can extend the controller using tranditional JavaScript prototype inheritance, or use the built-in extend method:
var Comment = Espresso.Controller.extend({
init: function() { ... }
})
new Controller(options)¶Options over-ride anything defined on the class.
If options.view is a DOM node, then the controller is bound to that node.
If options.view is a string, then it is used to locate the DOM node by ID and clone its first child. This is much faster than doing templating and parsing templates using innerHTML.
Gets called automatically as soon as the controller is bound to a view. The perfect place to initialize your views.
Object containing all DOM nodes with data-ref attribute, keyed by name. Faster than doing view.querySelector as all nodes with ref attribute are fetched just once when the controller is initialised.
this.ref.author.textContent = 'hello world'
Note that for convenience, ref.view refers to controller's DOM node.
The view property refers to the view DOM Element
Model property refers to the Model instance backing the controller. Whenever the model changes, the controller's render method is called automatically.
include(controller, [view])¶Add child controllers and views:
var page = this.include(new PageController({ view: 'page-view', name: 'Page 1' }), this.ref.page);
If a view node is specified, the child controller will have its view set to that node at the same time.
remove()¶Removes the view and any included controllers, and removes any listeners attached with listenTo
listenTo(object, event, fn)¶The listenTo method of the controller lets you listen to events raised by other objects:
this.listenTo(this.model, 'change', function() { ... })
this.listenTo(document.body, 'click', function() { ... })
Target objects must provide on of the following method forms: addListener addEventListener on.
The callback function is automatically bound to the controller for you, so this should refer to the controller instance unless you bind it to something else ahead of time.
stopListening(object, event)¶Stops listening to particular object and/or event.
setView(node)¶setView is called automatically as soon as the controller has a view specified either in the constructor options or when it's being included in render. The function computers the ref property, calls init, render and then listens to model changes.
You should not need to call setView manually.
set({ key: val })¶Shortcut for this.model.set
You should over-ride render with your own implementation that updates this.view node. You can do this imperatively as in Backbone, or declaratively as follows:
render: function(model) {
return {
list: { include: this.list.set(items) },
view: { classList: { editing: model.editing, completed: model.done } },
label: { onclick: this.edit, text: 'click here', display: true },
input: { value: model.text, onkeydown: this.key },
toggle: { onclick: this.toggle, checked: this.model.done }
count: { html: '<strong>'+store.active().length+'</strong> items left' },
}
}
If your render function returns an Object, Espresso assumes that the object represents current state of this.view and peforms the required updates to the DOM after diff-ing against previous DOM object returned by render. The declarative form specifies the name of the node on the left (as per the data-ref property) and attributes of that node on the right. Apart from HTML attributes, some attributes offer special features:
include - include a child Controller instance with that node as its viewon[event] - binds an event handler to that nodeclassList: { className: true|false } - specifies which classes the node should havedisplay: true|false - whether to display the nodetext - sets the textContent of the nodehtml - sets the innerHTML content of the nodechecked - sets the checked value of a checkboxModels are thin wrappers over objects providing getters and setters and firing change events. Note that getters are optional (if you wish to make use of the defaults) but set must be called in order to fire a change event.
new Model({ key: val })¶Creates a new model with specified attributes
This property is used to set the default properties of the model, which are used when get is called.
set({ key: val }); set(key, val)¶Sets new values on the model and fires a change event if necessary. Since changing the model will cause the controller to re-render, you should aim to set all required properties in one set() call.
get(attr)¶Returns the value of the objects attr attribute or its default
Returns the model attributes as a pure Object.
Collections are thin wrappers over native arrays and fire a change method when they have been modified. The change method specifies the index and which elements have been added, removed and updated.
this.listenTo(collection, function(e) {
console.log(e.index, e.added, e.removed, e.updated)
})
new Collection([ .. ])¶Creates a new collection with specified items
defaults to "id"¶This property is used to find and update items by their primary key.
count()¶Returns the number of items in the collection
get(index); get({ id: 1 })¶Returns an item from the collection at the specified index or matching the given object
set(index, item)¶Sets an item in the collection. Alternatively an item in the collection with id = 1 can be updated as follows:
set({ id: 1, value: 2 })
Or you can set all items in the collection as follows:
set([ ... ], [idAttribute])
which performs a smart update of the collection by performing splice operations in order to arrive at the target set.
reset([ ... ])¶Resets all the items in the collection.
findIndex({ id: 1 })¶Returns the index of matching item in the collection or -1
find({ id: 1})¶Returns the matching item in the collection or undefined
remove(index); remove({ id: 1 })¶Removes an item from the collection
Available array methods are:
A List provides a simple way to display an array of objects, and updates the DOM as necessary when objects are added, removed or updated.
new List(ControllerClass, [items])¶Creates a new list of type ControllerClass, using each item as the model for the controller. ControllerClass can also be a function that returns a controller instance:
new List(ToDoItem)
new List(function(model) { new ToDoItem({ model: model }) })
If items are passed in, they are used as the default items in the list. items can also be an instance of a Collection class, in which the list binds directly to that collection.
set([ ... ])¶Sets the items in the list, and returns the list.
init: function() {
this.todos = [ { text: 'shop' }, { text: 'code' } ]
this.list = new List(ToDoItem)
},
render: function() {
return {
todos: { include: this.list.set(this.todos) }
}
}
reset([...])¶Delegates to Collection.reset
All classes mixin the EventEmitter, which is a tiny implementation of node's equivalent module.
addListener(event, fn)¶removeListener(event, fn)¶emit(event, [arg1], [arg2])¶A comments example based on the React tutorial
A simple to-do example in 130 lines based on ToDoMVC