mojojs

modular mv+ JavaScript framework for node, and the web

npm install mojojs
67 downloads in the last day
321 downloads in the last week
1 304 downloads in the last month

Alt ci Alt ci

Mojo.js is a JavaScript framework for building Single Page Applications, or static websites in Node.js. It's inspired by Angular.js, Derby.js, Knockout.js, Meteor.js, Ember.js, jQuery, Backbone.js, and many other JavaScript, and non-JavaScript frameworks. The core is small, while third-party modules allow you to customize Mojo depending on your requirements. Mojo was built initially to phase out old code, and itself - hence the modularity. The philosophy behind Mojo is to allow you to build on top of your old code base, and slowly strangle out your old application until you have a new, highly maintainable application.

Features

  • Supported in all major browsers: IE 8+, Firefox, Chrome, Safari, and Opera.
  • Supported in Node.js. Run the same code on the front-end & backend. See the mojo site source code for an example.
  • Flexible bi-directional data-bindings.
  • Plays nicely with other frameworks such as Backbone.js, Spine.js, and jQuery. Easily build new application code on top of old code.
  • No magic. No assumptions. Mojo.js was built around explicitness and modularity.
  • 100% JavaScript - paperclip.js templates are also translated to JavaScript.
  • Small core. Modules make up the rest.
    • Decorators are core - they control everything from computed properties, creating children of a view, and even setting up templates. There are a few built-in decorators (for now - we'll take them out later): drag & drop, paperclip.js (template), bindings (computed properties), transition, and events (Backbone style). You can also create your own if you want to extend the core, but there isn't a single decorator that's required for Mojo.js to function properly.
    • The framework itself is broken into multiple repositories - this makes it easier to encapsulate, re-use bits of functionality.

Core Libraries

Examples:

TODO:

  • route docs
  • API docs
  • starter kit
  • testing docs
  • style guidlines

Installation

You can get started with Mojo.js by installing the starter kit. In terminal run:

git clone git@github.com:classdojo/mojo-starter.git && cd mojo-starter && npm install;

View Usage

Views extend bindable objects. The best way to create a view is to first create a sub-class, then instantiate it. For example:

var SubView = mojo.View.extend({
  name: "craig"
});
var view = new SubView();
console.log(view.get("name")); //craig

view.attach(selector)

Renders, and adds the view to the specific DOM element. Here's an example:

var view = new mojo.View({
  paper: paperclip.compile("hello!")
});
view.attach($("#application"));

DocumentFragment view.render()

Renders the view. For example:

var view = new mojo.View({
  paper: paperclip.compile("hello!")
});
$("#application").append(view.render());

view.section

The loaf section. This is where everything is rendered to.

view.remove(callback)

Removes the view from the DOM.

view.emit(event [, data...])

emits an event

view.on(event, listener)

listener for an event. For example:

var view = new mojo.View();
view.on("hello", function() {

});
view.emit("hello"); //trigger listener

view.bubble(event [, data...])

bubbles an event up to the root view.

view.parent

reference to the parent view

events

  • render - emitted when view.render() is called.
  • remove - emitted when view.remove() is called.
  • dispose - emitted when the view is removed, and not used anymore.

protected methods

Mojo.js has a few methods you can override if you need to something durring render / remove.

var view = new mojo.View({
  _onRender: function() {
    //called on render
  },
  _onRendered: function() {
    //called on rendered
  },
  _onRemove: function() {
    //called on remove
  },
  _onRemoved: function() {
    //called on removed
  }
});

View Decorators

Decorators are extensions to the Mojo.js framework - they help you describe how your view should function, but aren't necessary for Mojo.js to work. Therefore, you can easily mix decorators, or even create your own. This design was picked to allow you, the coder to pick whatever style suites you best. There are however a few built-in decorators that might help you get started.

Templates

By default, Mojo.js uses paperclip.js for the template engine. Here's a basic example:

var view = new mojo.View({
  paper: paperclip.compile("hello world!")
});
view.attach($("#application"));

You can also dynamically change the template. Say for instance you want to change the template depending on a model type, here's what you can do:


var templates = {
    notice  : paperclip.compile("notice"),
    default : paperclip.compile("notice"),
    warning : paperclip.compile("warning"),
    error   : paperclip.compile("error")
};

var NotificationView = mojo.View.extend({
    "bindings": {
        "model.type": {
            "paper": {
                "map": function(type) {
                    return templates[type] || templates.default;
                 }
             }
         }
     }
});

var alertView = new NotificationView({ model: new bindable.Object({ type: "alert" }) });
var photoView = new NotificationView({ model: new bindable.Object({ type: "photo" }) });

You can add your own template - just create a custom decorator.

Bindings

The bindings decorator is similar to Ember's computed properties feature. For example:

var TestView = mojo.View.extend({
  paper: paperclip.compile("hello-world"),
  bindings: {

      //join first & last name
      "firstName, lastName": {
          "fullName": {
              "map": function(firstName, lastName) {
                  return [firstName, lastName].join(" ");
              }
          }
      },

      //uppercase & lowercase fullName
      "fullName": {
          "fullNameUpper": {
              "map": function(fullName) {
                  return String(fullName).toUpperCase();
              }
          },
          "fullNameLower": {
              "map": function(fullName) {
                  return String(fullName).toLowerCase();
              }
          }
      },

      //wait for fullNameUpper to change
      "fullNameUpper": function(fullNameUpper) {
          console.log("CHANGE!");
      }
  }
});

//init view somewhere

Sections

Sections are what make up your application - they allow you to break down your app into smaller, more modular pieces. Here's a basic example:

//views/main/header/logo.js
var LogoView = mojo.View.extend({
    paper: paperclip.compile("header-logo")
});

//views/main/header/index.js
var HeaderView = mojo.View.extend({
    paper: paperclip.compile("header"),
    sections: {
        logo: LogoView
    }
});

//views/main/content/index.js
var ContentView = mojo.View.extend({
    paper: paperclip.compile("content")
});

//views/main/index.js
var MainView = mojo.View.extend({
    paper: paperclip.compile("main"),
    sections: {
        header: HeaderView,
        content: ContentView
    }
});

var mainView = new MainView();
mainView.attach($("#application"));

Mojo comes with a few built-in components: lists, and states.

List Component

List of views. Here's an example:

var TodosView = mojo.View.extend({
  todos: todoCollection,
  sections: {
    items: {
      type: "list",
      source: "todos",
      modelViewClass: TodoView
    }
  }
})

Note that each model item in the source collection is assigned as model for each list item.

list.filter(fn)

Filters the list. For example:

var TodosView = mojo.View.extend({
  todos: todoCollection,
  sections: {
    items: {
      type: "list",
      source: "todos",
      modelViewClass: TodoView,

      //filter items that are NOT done.
      filter: function(model) {
        return !model.get("done");
      }
    }
  }
});

list.sort(fn)

Sorts the list. For example:

var TodosView = mojo.View.extend({
  todos: todoCollection,
  sections: {
    items: {
      type: "list",
      source: "todos",
      modelViewClass: TodoView,
      sort: function(a, b) {
        return a.get("priority") > b.get("priority") ? -1 : 1;
      }
    }
  }
});

States Component

The states component allow you to toggle between multiple views. This is useful if you want to introduce something like routes into your application. Here's an example:

var MainView = mojo.View.extend({
  sections: {
    pages: {
      type: "states",
      index: 0,
      views: [
        { class: ContactView , name: "contact" },
        { class: HomeView    , name: "home"    }
      ]
    }
  }
})

states.index

the current index of the state. For example:

var MainView = mojo.View.extend({
  sections: {
    pages: {
      type: "states",
      index: 0,
      views: [
        { class: ContactView , name: "contact" },
        { class: HomeView    , name: "home"    }
      ]
    }
  }
});

var view = new MainView();
console.log(view.get("sections.pages.index")); //0

Custom Components

Mojo.js allows you to register your own components. Here's a basic example:

//views/main/header/logo.js
var HelloView = mojo.View.extend({
    paper: paperclip.compile("hello")
});

mojo.models.set("components.hello", HelloView);

var MainView = mojo.View.extend({
    paper: paperclip.compile("main"),
    sections: {
        hello1: {
            type: "hello",
            message: "craig"
        },
        hello2: {
            type: "hello",
            message: "john"
        }
    }
});

var mainView = new MainView();
mainView.attach($("#application"));

Note that options provided for each section are automatically set to the component being created. The above equivalent might be:

var view = new HelloView({
  message: "john"
});

Custom Decorators

There are some cases you might want to add your own decorator. Say for instance you want to add your own custom template engine. No problem:

decorator:

var handlebarsDecorator = {

    //returns the handlebar options. This decorator is ignore if the options are
    //undefined
    getOptions: function(view) {
        return view.handlebars;
    },

    //decorates the view with the given options
    decorate: function(view, sourceName) {

        //compile the template
        var template = Handlebars.compile($("script[data-template-name='" + sourceName + "']").html());

        //wait for the view to render, then add the elements
        view.on("render", function() {

            //temporary placeholder for the elements - use innerHTML to compile the template.
            var div       = document.createElement("div");
            div.innerHTML = template(view.context());

            //append JUST the child nodes to the view section
            view.section.append.apply(view.section, div.childNodes);
        });
    }
}

mojo.decorator(handlebarsDecorator);

usage:

var MainView = mojo.View.extend({
    name       : "craig",
    handlebars : "main"
});

models singleton

Allows for models to be referenced anywhere in the application. See the variable scope example.

Property Scope

Child views inherit properties from the parent view, just like variable scope in JavaScript. Therefore, you should always define properties you want to use within your views. For example:

var user = new mojo.bindable.Object({
    name: "john"
});

mojo.models.set("user", user);

var HeaderView = mojo.View.extend({
    paper: paperclip.compile("header")
});

var MainView = mojo.View.extend({
    define: ["user"],
    paper: paperclip.compile("main"),
    bindings: {
        "models.user": "user"
    },
    sections: {
        header: HeaderView
    }
});


var view = new MainView();
view.attach($("#application"));

Checkout what happens when we define user in HeaderView. Notice that user isn't inherited anymore, and remains undefined.

npm loves you