Skip to content

Instantly share code, notes, and snippets.

@kkemple
Last active August 29, 2015 14:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kkemple/7d0fa7af6287f873b505 to your computer and use it in GitHub Desktop.
Save kkemple/7d0fa7af6287f873b505 to your computer and use it in GitHub Desktop.

BehaveJS

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 */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment