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) minifiedDevelopment 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.
extend KnockoutApp.Model.extend( [ prototype properties, class properties] )
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
constructor/initialize new KnockoutApp.Model( attributes, [ options ] )
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.
attributes model.attributes
Model’s attributes are stored in a plain javascript object:
var model = new KnockoutApp.Model({
attr1: "an attribute"
});
model.attributes.attr1; // "an attribute"
defaults model.defaults
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.
id model.id( [ new value ] )
An observable property, by default set to false, that stores the model’s idAttribute value.
idAttribute model.idAttribute
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"
isNew model.isNew()
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
collection model.collection
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
sync model.sync( model, method, options )
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.
url model.url()
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"
name model.name
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 model.validate()
Validate a model, should return true if the model is valid, any other value if it isn’t.
fetch model.fetch( [ options ] )
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 model.save( [ options ] )
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.
destroy model.destroy( [ options ] )
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.
toJSON model.toJSON()
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.
extend KnockoutApp.Collection.extend( [ prototype properties, class properties] )
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
constructor/initialize new KnockoutApp.Collection()
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.
model collection.model
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
models collection.models
The observable array storing all the models in the collection.
sync collection.sync( model, method, options )
The sync method used by the model, by default it uses KnockoutApp.Sync.
Can be overriden to use a custom method.
url collection.url
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"
});
fetch collection.fetch( [ options ] )
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 collection.add( model_s, [ create, options ] )
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 collection.remove( model_s, [ options ] )
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.
reset collection.reset()
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;
find collection.find( attrs )
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.
where collection.where( attrs )
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.
toJSON collection.toJSON()
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.
unwrapValue KnockoutApp.Utils.unwrapValue( object, value )
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!"
extendObjKnockout KnockoutApp.Utils.extendObjKnockout( destination, params )
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.
cloneObjKnockout KnockoutApp.Utils.cloneObjKnockout( obj )
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"
extendClass KnockoutApp.Utils.extendClass( prototype properties, class properties )
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.
isObservableArray KnockoutApp.Utils.isObservableArray( obj )
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.fetchusescollection.reset
0.2.0
-
collection.find -
collection.where -
collection.reset -
model.idAttribute -
model.destroynow doesn’t wait for a server response untilwait: trueis passed as an option -
model support passing a more generic
options(where you can setcollection) instead ofcollectionas 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.modelisn’t defined anymore by passing it as a parameter when creating -
error if missing
urlwhen usingKnockoutApp.Sync -
defaultAttributesrenamed todefaultsand 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:
- Download source from github
- Open the terminal in the directory where you have downloaded the code
- Install dependencies with
npm install
(remember to installgrunt-cliglobally before, see the migration guide for Grunt 0.4 linked above)
Now you can run:
grunt build
to build KnockoutApp into /buildgrunt test
to run testsgrunt run
to start a webserver at http://localhost:8000 and rebuild/test every time a file changegrunt connect:server:keepalive
to start a webserver at http://localhost:8000 but don't rebuild/test anythinggrunt 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!