Models contain the interactive data of your application as well as a large part of the logic surrounding it: conversions, validations, computed properties, and access control.
You extend UWA.Class.Model class with your domain-specific methods, and Model provides a basic set of functionality for managing changes.
Furthermore Models benefit from a great syncing system with the backend data, built as close as possible to the RESTful pattern, that can be easily customized to suit your needs.
Events triggered by instances of UWA.Class.Model:
First of all, please note that any event that is triggered by an instance of UWA.Class.Model that is in a collection will also be triggered on the collection directly, for convenience. This allows to any code to listen to a collection for changes to specific attributes in any of its models! Please see UWA/Class/Collection for a description.
Event name | Arguments passed to listener callback | Description |
---|---|---|
'onAdd' | model, collection, options | Triggered when the model is added to a Collection. |
'onRemove' | model, collection, options | Triggered when the model is removed from a Collection. |
'onChange' | model, options | Triggered when any model's attribute has changed. |
'onChange:attr' | model, new_attr_value, options | Triggered when the specific attribute attr of the model has been modified. |
'onDestroy' | model, model.collection if any, options | Triggered when the model is destroyed. |
'onSync' | model, backend response, options | Triggered whenever the model is successfully synced to/from the backend (saved, destroyed or fetched). |
'onRequest' | model, XmlHttpRequest, options | Triggered by default implementation of sync as the XHR begins to go to the backend. |
'onError' | model, backend error, options | Triggered when a save/fetch/destroy operation fails in the backend. |
'onValidationFailure' | model, validation error, options | Triggered when validate() failed. Note that the error is also available in options.validationError. |
'onAnyEvent' | event name, event data... | Triggered for any triggered event, passing the event name as the first argument. |
Define your own custom model by sub-classing UWA.Class.Model:
A UWA.Class.Model can be extended (i.e. sub-classed), and it must be in 99% of your cases, using UWA.Class.extend mechanism and defining an setup function when extending that will be invoked when a model is created.
The methods and properties you should override/implement when defining your custom models by sub-classing UWA.Class.Model are :
- {@link module:UWA/Class/Model.UWA.Class.Model#setup|setup}, to initialize a newly created instance of model.
- sync, to implement, customize or fine-tune the manner in which models are persisted in or fetched from the backend.
- parse, to transform the raw backend response into an hash of attributes for the model.
- validate, to implement your custom validation logic of the attributes.
- url, to return the relative or absolute URL of the model in the backend.
- urlRoot, if you’re using a model outside of a collection.
- idAttribute, to transparently map from the unique identifying key in the backend to this model’s id.
- defaults, to specify the default attributes values hash for the model.
By extending/subclassing UWA.Class.Model, you can define your own business logic on top of UWA.Class.Model, see an example below that will show you the benefits of defining your custom models for your business logic.
Example
// The following is a brief and contrived example, but it demonstrates defining a model
// with a custom business logic method (adopt method), setting an attribute,
// saving it to the backend, ...
define('MyProject/Model/Person', [
'UWA/Class/Model',
'UWA/Class/Debug',
'UWA/String'
], function (Model, Debug, UWAString) {
'use strict';
var Person = Model.extend(Debug, {
urlRoot: '/person',
defaults: {
name: 'Foetus',
age: 0,
child: ''
},
setup: function () {
var that = this;
that.log("Welcome to this world");
that.addEvent("onChange:name", function (person) {
that.log(UWAString.format('Changed my name to {0}', person.get("name")));
});
that.addEvent('onValidationFailure', function (person, error) {
person.log(error);
})
},
// Validate data before you set or save it :
validate: function (attributes) {
if (attributes.age < 0 && attributes.name !== 'Benjamin Button') {
return "Only Ben Button can be negative years old!";
}
},
// some business logic on my model :
adopt: function (newChildsName) {
this.set({
child: newChildsName
});
this.log(UWAString.format('{0} just adopted {1}', this.get(name), newChildsName));
}
});
return Person;
});
require(['MyProject/Model/Person'], function (Person) {
var angelina = new Person({
name: "Jolie",
age: 38,
child: 'Shiloh'
});
alert(angelina.isNew()); // alerts 'true'
angelina.adopt('Pax Thien');
var child = angelina.get("child"); // returns 'Pax Thien'
// A request PUT /user with a payload containing the attributes
// will be performed.
// The backend should persist the data and return a response containing the new `id`
angelina.save({
age: 39
}, {
wait: true,
onComplete: function (person, response) {
person.log('Successfully saved!');
},
onFailure: function (person, error) {
person.log(person.toJSON());
person.log(error.responseText);
}
});
// Assuming that there is already a person saved in DB
// with an ID that is '2' :
var brad = new Person({
id:2
});
alert(brad.isNew()); // alerts 'false'
// This fetch call will perform a GET /user/1 request
// The backend should return the id, name and email from the database.
brad.fetch({
onComplete: function (brad) {
brad.log('I have been fetched from IMDB.');
}
});
// Because brad has an ID, a request DELETE /person/2 will be
// performed :
brad.destroy({
onComplete: function (bradZombie) {
bradZombie.log('I have been removed from IMDB.');
}
});
});