This framework is an effort to provide common services needed in native web applications while remaining truly independent of each other.
BehaveJS
services may tie together seamlessly, but each service can be added in to any application with a few extra lines of code (and in some cases none).
/* Building a TODO app */
import BehaveHistory from 'behave-history';
import BehaveRouter from 'behave-router';
import BehaveImmutable from 'behave-immutable';
import BehaveEvents from 'behave-events';
import BehaveCollection from 'behave-collection';
import dispatcher from 'behave-dispatcher';
// you can use any view layer you want with BehaveJS
// ractive and react fit best
import ractive from 'ractive/ractive.runtime';
import templates from './templates';
/* build our todo store */
class TodoStore extends BehaveEvents {
constructor() {
this.collection = new BehaveCollection();
}
addTodo(data) {
data._id = this.collection.count();
var todo = new BehaveImmutable(data);
this.collection.add(todo);
}
editTodo(id, data) {
this.collection.each(todo => {
if (todo.toJS()._id === id) todo.set(data);
});
}
deleteTodo(id) {
this.collection.each(todo => {
if (todo.toJS()._id === id) delete todo;
});
}
toggleTodo(id) {
var todo = this.collection.each(todo => {
if (todo.toJS()._id === id) todo.set({ done: !todo.done });
});
}
_update(evt) {
switch (evt.type) {
case 'TODO_ADD':
_this.addTodo(evt.data);
this.emit('change');
break;
case 'TODO_EDIT':
_this.editTodo(evt.id, evt.data);
this.emit('change');
break;
case 'TODO_DELETE':
_this.deleteTodo(evt.id);
this.emit('change');
break;
case 'TODO_TOGGLE':
_this.toggleTodo(evt.id);
this.emit('change');
break;
}
}
}
/* create app history and router */
// should only be one history instance
var history = new BehaveHistory({ dispatcher: dispatcher });
// there can be infinite routers
var router = new BehaveRouter({ dispatcher: dispatcher });
/* instantiate todoStore and register with dispatcher */
var todoStore;
// use middleware for things you want to happen
// on every route in router
router.middleware((ctx) => {
if (!todoStore) {
todoStore = new TodoStore();
dispatcher.register('TodoStore', todoStore.update.bind(todoStore));
}
// attach todoStore collection to context object for easier retrieval
ctx.collection = todoStore.collection;
});
/* set up routes */
// routes support common routing globs, including regex
router.use('todos', (ctx) => {
var todosView = new Ractive({
el: 'body',
data: ctx.collection.toJS(),
template: templates.index
});
todoStore.on('change', () => {
todosView.set(ctx.collection.toJS());
});
});
router.use('todos/:id', (ctx) => {
var detailView = new Ractive({
el: 'body',
data: ctx.collection.findWhere({ _id: ctx.params.id }).toJS(),
template: templates.detail
});
todoStore.on('change', () => {
detailView.set(ctx.collection.findWhere({ _id: ctx.params.id }).toJS());
});
});
router.use('todos/:id/edit', (ctx) => {
var editView = new Ractive({
el: 'body',
data: ctx.collection.findWhere({ _id: ctx.params.id }).toJS(),
template: templates.edit
});
todoStore.on('change', () => {
editView.set(ctx.collection.findWhere({ _id: ctx.params.id }).toJS());
});
});
// example of catching all routes that don't match, and redirecting
router.use('*', (ctx) => {
// no route matched so must be incorrect url
console.warn('No route for: ' + ctx._canonicalPath);
dispatcher.dispatch({
evt: 'ROUTE_CHANGE',
route: 'todos',
options: {
// replace window state so we don't get caught in loop
replace: true
}
});
});
// example breaking down todoStore and unregistering with dispatcher
// when we leave the routers scope (any routes not in the router)
router.exit((ctx) => {
if (todoStore) {
delete todoStore;
dispatcher.unregister('TodoStore');
}
ctx.collection = null;
});
/* start up app */
history.start();
router.start();
/* initial route change on page load */
dispatcher.dispatch({
type: 'ROUTE_CHANGE',
route: (/^todos/.test(window.locaton.pathname)) ?
window.location.pathname :
'todos',
data: {},
options: {}
});
/* and you have a todo app */