Introduction

KnockoutJS is a fantastic library but it lacks a proper structure for building modern web apps and every time you end up writing the same code for models, syncing data with the back-end…

KnockoutApp changes this. Heavily inspired by BackboneJS it provides a Model class, a Collection class and a Sync method.

The code is hosted on Github and it comes with annotated source code, a test suite and the classic example application (with annotated source code here and source code in here).

Github Issues are used for discussions, feature requests and bug reports, see Contributing if you want to contribute to this project.

Download v0.2.5

Production 4.8kb (1.9kb gzipped) minified

Development 12.8kb with a lot of comments

The development happens on the master branch. Older versions are tagged in git and can be found here.

Getting started

Just include the file in you’re html page and you’re ready:

<script type="text/javascript" src="knockout.app.min.js"></script>

It is also avalaible as an AMD and CommonJS module.

While it won’t work server-side KnockoutApp is avalaible as an npm module, it is intended for use with Browserify or similar tools:

npm install knockout.app

The only dependency is KnockoutJS while jQuery is only necessary when using Knockout.Sync for ajax calls.

Model KnockoutApp.Model

A model is one of the most important parts of any javascript application, in KnockoutApp it stores the model’s data and provides methods for syncing it with the server.

A model can be extended to add or to modify any of its properties or methods:

var MyModel = KnockoutApp.Model.extend({
  prototypeProperty: "prototype property"
},{
  classProperty: "class property"
});

MyModel.classProperty // -> "class property"

var modelInstance = new MyModel();

modelInstance.prototypeProperty // -> "prototype property"

For more info about extending classes see Utils.extendClass

When creating a new model you can its attributes:

new MyModel({
  attr1: "the first attribute"
});

The second parameter is used to pass options to the model, as of now the only option you can set is a reference to a collection:

new KnockoutApp.Model({
  //attributes
}{
  collection: MyCollection
});

This way a reference to MyCollection will be set in model.collection.

If you define a initialize function in the class’ prototype it will be executed when model is created:

var MyModel = KnockoutApp.Model.extend({
  initialize: function(attributes, options){
    console.log("initialized");
  }
});

new MyModel(); // "initialized" will be logged in the console

You can access the parameters passed to the model in initialize and inside it this refers to the model itself.

Model’s attributes are stored in a plain javascript object:

var model = new KnockoutApp.Model({
  attr1: "an attribute"
});

model.attributes.attr1; // "an attribute"

This object store the default value for model’s attributes and it’s very important because here you can define observable properties that when the model is created will be correctly set in the attribute’s object removing the need to redefine them every time:

var Task = KnockoutApp.Model.extend({
  defaults: {
    name: ko.observable("a task"),
    done: ko.observable(false)
  }
});

var myTask = new Task({
  name: "my task",
  done: true
});

// myTask.attributes.name is an observable property:
myTask.attributes.name(); // -> "my task"

defaults also works with observable arrays and standard javascript properties.

An observable property, by default set to false, that stores the model’s idAttribute value.

The model’s attribute used for model.id, by default it is id, but can be set to everything else.

This is useful when the server provide the model’s id field under another name like _id when using MongoDB:

It can be set passing the idAttribute fields with the attributes object when creating the model:

var Model = KnockoutApp.Model.extend({
  idAttribute: "_id"
});

var model = new model({
  _id: "123"
});

model.id(); // "123"

A computed observable that re-evaluate itself every time the value of model.id changes and return true if model.id is false, false in any other case.

Useful to detect if the model exists on the server.

var model = new KnockoutApp.Model({id: "1"});

model.isNew(); // -> false, id is set

model = new KnockoutApp.Model();

model.isNew(); // -> true, id isn't set

model.save();

model.isNew(); // -> false, the model has been saved on the server and id has been set

If the model is contained into a collection it stores a reference to the collection.

var collection = new KnockoutApp.Collection();

var model = new KnockotuApp.Model({
  id: "1",
  name: "one model"
});

collection.add(model);

// model.collection === collection

The sync method used by the model.

If the model is contained into a collection then it uses collection’s sync method while if it isn’t KnockoutApp.Sync is used.

Can be overriden to use a custom method.

Returns the model’s url that is used to make ajax calls based on the value of model.baseUrl (by default not defined) or, if it isn’t defined and the model is contained into a collection, on model.collection.url’s value, if both of them are undefined it throws an error.

Can be overriden with a custom method/value.

model = new KnockoutApp.Model({
  baseUrl: "/models"
});

model.url(); // -> "/models"

model.id(1);

model.url(); // -> "/models/1"

var coll = new KnockoutApp.Collection();
coll.url = "/collection";

var model.baseUrl = undefined;

coll.add(model);

model.url(); // -> "/collection/1"

If defined, when syncing the model with the server, the data will be wrapped in an object which name is model.name.

It’s useful, for example, when using Ruby On Rails that expect the data to be wrapped in a parameter which name is model’s name.


var Task = KnockoutApp.Model.extend({
  defaults: {
    name: ko.observable("a task"),
    done: ko.observable(false)
  },
  name: "tasks"
});

var task = new Task({
  id: 1
});

task.save();

The server will receive this data:

task: {
  id: 1,
  name: "a task",
  done: true
}

Validate a model, should return true if the model is valid, any other value if it isn’t.

Fetches the model on the server using model.sync and replace the model’s attributes with the ones returned from the server.

It accepts an object of option that will be passed to model.sync, when using KnockoutApp.Sync these options will be set as params for the ajax call.

If you pass, inside options, a success callback it will be executed in addition to the default success callback (that consists in setting the attributes returned from the server in the model), if you want to override the default success callback you must override the whole model.fetch method.

Save the model on the server using model.sync, if the model isNew it will be created while in the other case it will be updated.

Before saving the model it checks for it to be valid using model.validate and if it isn’t it returns false.

It accepts an object of option that will be passed to model.sync, when using KnockoutApp.Sync these options will be set as params for the ajax call.

If you pass, inside options, a success callback it will be executed in addition to the default success callback (that consists, when the model has been created, in setting the id according to the response from the server), if you want to override the default success callback you must override the whole model.save method.

If the model is saved on the server, delete it using model.sync.

If it’s stored in a collection, remove it from the collection.

It accepts an object of option that will be passed to model.sync, when using KnockoutApp.Sync these options will be set as params for the ajax call.

If you pass, inside options, a success callback it will be executed in addition to the default success callback (that consists, when the model has been created, in setting the id according to the response from the server), if you want to override the default success callback you must override the whole model.destroy method.

If you set wait: true in the options then it will wait for the server response before removing the model from the collection.

If the model isn’t saved on the server it returns false.

Despite what the name may suggests it is used to serialize the model’s attributes and idAttribute.

var Task = KnockoutApp.Model.extend({
  defaults: {
    name: ko.observable("a task"),
    done: ko.observable(false)
  }
});

var task = new Task({
  name: "task to serialize",
  done: true,
  id: 1
});

task.toJSON();

Produces:

{
  id: 1,
  name: "task to serialize",
  done: true
}

Collection KnockoutApp.Collection

A collection store a group of models in an observable array and provides methods to show, filter, sync... the observable array and its models.

Like a model, a collection can be extended to add or to modify any of its properties or methods:

var MyCollection = KnockoutApp.Collection.extend({
  prototypeProperty: "prototype property"
},{
  classProperty: "class property"
});

MyCollection.classProperty // -> "class property"

var collection = new MyCollection();

collection.prototypeProperty // -> "prototype property"

For more info about extending classes see Utils.extendClass

Creating a new collection simply set up an observable array to store the models.

If you define a initialize function in the class’ prototype it will be executed when collection is created:

var MyCollection = KnockoutApp.Collection.extend({
  initialize: function(){
    console.log("initialized");
  }
});

new Collection(); // "initialized" will be logged in the console

You can access the parameters passed to the collection in initialize and inside it this refers to the collection itself.

A reference to the model class defined in the prototype. It is used, for example, when adding a model to the collection.

var Taskslist = KnockoutApp.Collection.extend({
  model: Task
});

var taskslist = new TasksList();

// taskslist.model === Task

The observable array storing all the models in the collection.

The sync method used by the model, by default it uses KnockoutApp.Sync.

Can be overriden to use a custom method.

The url property used by the collection and all the models inside it (unless in a model you defined model.baseUrl), by default it’s left undefined.

var Collection = KnockoutApp.Collection.extend({
  url: "/collection"
});

Fetches the collection’s models on the server using collection.sync and replace the current models store in collection.models with the one fetched.

It accepts an object of option that will be passed to collection.sync, when using KnockoutApp.Sync these options will be set as params for the ajax call.

If you pass, inside options, a success callback it will be executed in addition to the default success callback (that consists in updating collection.models with the models returned from the server), if you want to override the default success callback you must override the whole collection.fetch method.

The model are added to the collection using collection.add, before adding the new models to the collection it is resetted using collection.reset.

Add on or more models to the collection, you can add a model by passing to this method either model’s attributes or directly the model.

Multiple models can be added passing an array.

collection.add([{
  number: 1
},{
  number: 2
},{
  number: 3
}]);

var model4 = new KnockoutApp.Model({
  number: 4;
});

var model5 = new KnockoutApp.Model({
  number: 5;
});

collection.add([model4, model5]);

On every model created using this method will be set a reference to the collection, accessible at model.collection.

If you pass true as the second parameter then after adding a model to the collection it will be called model.save and the model will be created/updated using model.sync, you can pass option for model.save in the third parameter.

Remove on or more models from the collection by calling model.destroy on each model passed to this method.

Accepts also an array of models.

var collection = new KnockoutApp.Collection();

var model1 = new KnockoutApp.Model({id: 1});
var model2 = new KnockoutApp.Model({id: 1});

collection.add([model1, model2]);

collection.remove([model1, model2]);

As the second parameter you can pass options for model.destroy.

Empties a collection by removing all the models stored inside collection.models and remove from all the models the reference to the collection.

var collection = new knockoutApp.Collection();

model1 = new KnockoutApp.Model();

collection.add(model1);

collection.models().length; // 1 (the number of items in collection.models())

model1.collection === collection;

collection.reset();

collection.models().length; // 0

model1.collection === undefined;

Returns the first model that match the passed attributes.

collection.add({
  name: "John",
  gender: "male"
},
{
  name: "Bob",
  gender: "male"
},
);

collection.find({name: "John"}) // -> returns John's model

If no match is found returns false.

Returns an array of models that match the passed attributes.

collection.add({
  name: "John",
  gender: "male"
},
{
  name: "Bob",
  gender: "male"
},
{
  name: "Ann",
  gender: "female"
});

collection.where({gender: "male"}) // -> returns Bob's and John's model

If no match is found returns an empty array.

Like model.toJSON() it is used for serialization.

It calls model.toJSON() on each model in the collection and puts the result in an array that is then returned.

var collection = new KnockoutApp.Collection();

collection.add({
  id: 1,
  attr1: "model 1"
},{
  attr1: "model 2"
});

collection.toJSON();

Produces:

[{
  id: 1,
  attr1: "model 1"
}, {
  attr1: "model 2"
}]

Sync KnockoutApp.Sync( method, model, options )

This is the method used by KnockoutApp for communicating with the server, it uses jQuery’s $.ajax to make ajax calls.

It accepts three parameters:

  • model: the model or the collecion that made the request

  • method: can be fetch, create, update or destroy

  • options: this one is optional, you can pass an object of options that will be passed to $.ajax

If the model is contained into a collection then it uses collection’s sync method while if it isn’t KnockoutApp.Sync is used.

To use a custom method, you can override globally KnockoutApp.Sync or only for a specific model or collection respectively model.sync or collection.sync.

Utils KnockoutApp.Utils

A set of utils methods used by KnockoutApp.

Return the value (first parameter) in the object (second parameter) if it’s a property, invoke it if it’s a function.

var obj = {
  url: function(){return "url!"};
}

KnockoutApp.Utils.unwrapValue(obj, url); // -> "url!"

obj.url = "url! string!";

KnockoutApp.Utils.unwrapValue(obj, url) // "url! string!"

Extend an object containing observable properties (also observable array).

var original = {
  a: ko.observable("a"),
  b: "b"
}

var extend = {
  a: "a extend",
}

KnockoutApp.Utils.extendObjKnockout(original, extend);

original.a() // -> " a extended"

Nested objects are also supported.

Clone an object containing observable properties.

var obj = {
  a: ko.observable("a");
};

var cloned = KnockoutApp.Utils.cloneObjKnockout(obj);

cloned.a("cloned a");
obj.a() // "a"

Extend a class by providing prototype properties and class properties.

The class constructor can be overriden passing a constructor method in the prototype properties.

You can access the parent prototype with Class.__super__.

An extended class can be further extended.

This method is used for KnockoutApp.Model.extend and KnockoutApp.Collection.extend.

Returns true if the passed object is an observable array.

Changelog

0.2.1, 0.2.2, 0.2.3, 0.2.4, 0.2.5

  • new build process

  • integration with Travis CI

  • tests updated to work with jQuery 1.9

  • collection.fetch uses collection.reset

0.2.0

  • collection.find

  • collection.where

  • collection.reset

  • model.idAttribute

  • model.destroy now doesn’t wait for a server response until wait: true is passed as an option

  • model support passing a more generic options (where you can set collection) instead of collection as the second parameter

  • removed Utils.wrapError, plan to make something better in the next release

  • added tests

  • can be loaded as an AMD or CommonJS module

  • collection.model isn’t defined anymore by passing it as a parameter when creating

  • error if missing url when using KnockoutApp.Sync

  • defaultAttributes renamed to defaults and doesn’t need to be a function anymore

  • added Utils.cloneObjKnockout

  • npm module published

0.1.1

  • fixes various bugs in KnockoutApp.Sync

  • now you can set a modelName property in the model so that the model properties sent via Ajax will be wrapped in an object which name is the value of model.modelName (useful for Ruby on Rails which expects parameters to be wrapped in an object es. task: {id: 1, name: “a task”})

  • KnockoutApp.Utils.unwrapValue now uses the code of Underscore.js’s result

  • Added package.json to handle dependencies

  • Upgraded to Grunt 0.4

  • grunt-docco is incompatible with grunt 0.4 so the annotated source code has to be generated manually using docco from the command line, I hope to fix that in the next version.

0.1.0

  • Initial release

Contributing

KnockoutApp uses Grunt for building and docco to generate annotated-source-code.

It uses version 0.4 of Grunt which is still unreleased but quite stable.

Here there’s the migration guide from Grunt 0.3 to 0.4, read it carefully because there are several breaking changes.

To build it:

  1. Download source from github
  2. Open the terminal in the directory where you have downloaded the code
  3. Install dependencies with
    npm install
    (remember to install grunt-cli globally before, see the migration guide for Grunt 0.4 linked above)

Now you can run:

  • grunt build
    to build KnockoutApp into /build
  • grunt test
    to run tests
  • grunt run
    to start a webserver at http://localhost:8000 and rebuild/test every time a file change
  • grunt connect:server:keepalive
    to start a webserver at http://localhost:8000 but don't rebuild/test anything
  • grunt publish
    which I use to publish a new version of KnockoutApp

On github the development happens on the master branch, so if you want to send some code send it there!