# Alloy Collection and Model Objects
# Models
In Alloy, models inherit from the Backbone.Model (opens new window) class. They contain the interactive data and logic used to control and access it. Models are specified with JavaScript files, which provide a table schema, adapter configuration and logic to extend the Backbone.Model class. Models are automatically defined and available in the controller scope as the name of the JavaScript file.
The JavaScript file exports a definition object comprised of three different objects. The first object, called config
, defines the table schema and adapter information. The next two objects extendModel
and extendCollection
define functions to extend, override or implement the Backbone.Model and Backbone.Collection classes, respectively.
Example of the anatomy of a model file
exports.definition = {
config : { // table schema and adapter information
},
extendModel: function(Model) {
_.extend(Model.prototype, { // Extend, override or implement Backbone.Model
});
return Model;
},
extendCollection: function(Collection) {
_.extend(Collection.prototype, { // Extend, override or implement Backbone.Collection
});
return Collection;
}
}
To access a model locally in a controller, use the Alloy.createModel
method. The first required parameter is the name of the JavaScript file minus the '.js' extension. The second optional parameter is the attributes for initializing the model object. For example, the following code creates a model object initialized with the specified title and author, retrieves the title and author fields from the model, then sets the label to the book title and author:
Basic model usage
var book = Alloy.createModel('book', {title:'Green Eggs and Ham', author:'Dr. Seuss'});
var title = book.get('title');
var author = book.get('author');
// Label object in the view with id = 'label'
$.label.text = title + ' by ' + author;
The book
model object is a Backbone object wrapped by Alloy, so it can be treated as a Backbone.Model object. You can use any Backbone Model or Events APIs with this object.
You can also create a global singleton instance of a model, either in markup or in the controller, which may be accessed in all controllers. Use the Alloy.Models.instance
method with the name of the model file minus the extension as the only parameter to create or access the singleton. For example:
Working with globally registered models
// This will create a singleton if it has not been previously created,
// or retrieves the singleton if it already exists.
var book = Alloy.Models.instance('book');
For examples using markup, see Alloy XML Markup: Model Element.
# Configuration object
The config
object is comprised of three different objects: columns
, defaults
and adapter
.
The columns
object defines the table schema information. The key is the record name and the value is the data type. The following data types are accepted and mapped to the appropriate SQLite type: string
, varchar
, int
, tinyint
, smallint
, bigint
, double
, float
, decimal
, number
, date
, datetime
and boolean
. By default, any unknown data type maps to the SQLite type TEXT
. Alternatively, the SQLite sync adapter accepts the SQLite keywords.
The optional defaults
object defines the default values for a record if one or more record fields are left undefined upon creation. The key is the record name and the value is the default value.
The adapter object defines how to access persistent storage. It contains two key-value pairs: type
and collection_name
. The type
key identifies the sync adapter and the collection_name
key identifies the name of the table in the database or a namespace. See Alloy Sync Adapters and Migrations for more information.
For example, suppose there is a model object called book (book.js
) defined as:
book.js
exports.definition = {
config: {
"columns": {
"title": "String",
"author": "String"
},
"defaults": {
"title": "-",
"author": "-"
},
"adapter": {
"type": "sql",
"collection_name": "books"
}
}
}
The code above describes a book object, which has two string
(or TEXT
) fields: title
and author
. If either field is left undefined, it will be assigned with the default value, a dash ("-"). The sql
type configures Backbone to use the SQL adapter to sync with the SQLite engine on Android and iOS devices to access a table in the database called "books".
You may add custom properties to the config
object, which are available to the application as the model or collection's config
property or can be processed by a custom sync adapter during application initialization. See Alloy Sync Adapters and Migrations: Custom Sync Adapters for more information.
# Extending the Backbone.Model class
The Backbone.Model class can be extended using the extendModel
object, which implements the Backbone.Model extend
method. This allows the Backbone.js code to be extended, overridden or implemented.
For example, the validate
method is left unimplemented by Backbone.js. The model JS file can implement validate(attrs)
, where the parameter attrs
are changed attributes in the model. In Backbone.js, if validate
is implemented, it is called by the set
and save(attributes)
methods before changing the attributes and is also called by the isValid
method. For the save
method, validate is called if the attributes
parameter is defined.
In the example code book.js
below, the JavaScript file implements the validate method, and adds a custom property and function.
Extending a model
exports.definition = {
config : { // table schema and adapter information
},
extendModel: function(Model) {
_.extend(Model.prototype, {
// Implement the validate method
validate: function (attrs) {
for (var key in attrs) {
var value = attrs[key];
if (key === "title") {
if (value.length <= 0) {
return "Error: No title!";
}
}
if (key === "author") {
if (value.length <= 0) {
return "Error: No author!";
}
}
}
},
// Extend Backbone.Model
customProperty: 'book',
customFunction: function() {
Ti.API.info('I am a book model.');
},
});
return Model;
}
}
In the controller, to access the model, do:
var book = Alloy.createModel('book', {title:'Green Eggs and Ham', author:'Dr. Seuss'});
// Since set or save(attribute) is not being called, we can call isValid to validate the model object
if (book.isValid() && book.customProperty == "book") { // Save data to persistent storage
book.save();
}
else {
book.destroy();
}
For more details, see the Backbone.Model API (opens new window).
# Collections
Collections are ordered sets of models and inherit from the Backbone.Collection class. Alloy Collections are automatically defined and available in the controller scope as the name of the model. To access a collection in the controller locally, use the Alloy.createCollection
method with the name of the JavaScript file minus the '.js' extension as the required parameter. The second optional parameter can be an array of model objects for initialization. For example, the code below creates a collection using the previously defined model and reads data from persistent storage:
Creating collections
var library = Alloy.createCollection('book');
library.fetch(); // Grab data from persistent storage
The library
collection object is a Backbone object wrapped by Alloy, so it can be treated as a Backbone.Collection object. You can use any Backbone Collection or Events APIs with this object.
You can also create a global singleton instance, either in markup or in the controller, which may be accessed in all controllers. Use the Alloy.Collections.instance
method with the name of the model file minus the extension as the only parameter to create or access the singleton. For example:
Working with globally registered collections
// This will create a singleton if it has not been previously created,
// or retrieves the singleton if it already exists.
var library = Alloy.Collections.instance('book');
For examples using markup, see Alloy XML Markup: Collection Element.
# Extending the Backbone.Collection class
Like the Backbone.Model class, the Backbone.Collection class can be similarly extended in the model JavaScript file. For example, the comparator
method is left unimplemented in Backbone.js. The code below sorts the library by book title:
Extending a collection
exports.definition = {
config : { // table schema and adapter information
},
extendModel: function(Model) {
_.extend(Model.prototype, { // Extend, override or implement the Backbone.Model methods
});
return Model;
},
extendCollection: function(Collection) {
_.extend(Collection.prototype, { // Implement the comparator method.
comparator : function(book) {
return book.get('title');
}
}); // end extend
return Collection;
}
}
# Underscore.js functionality
Additionally, the Backbone.Collection class inherits some functionality from Underscore.js (opens new window), which can help simplify iterative functions. For example, to add the title of each book object in the library collection to a table, you could use the map
function to set the table:
Iterating over a collection with underscore
var data = library.map(function(book) {
// The book argument is an individual model object in the collection
var title = book.get('title');
var row = Ti.UI.createTableViewRow({"title":title});
return row;
});
// TableView object in the view with id = 'table'
$.table.setData(data);
For more details, see the Backbone.Collection API (opens new window).
# Event handling
When working with Alloy Models and Collections, use the Backbone.Events on
, off
and trigger
methods. For example:
Using events with collections
var library = Alloy.createCollection('book');
function event_callback (context) {
var output = context || 'change is bad.';
Ti.API.info(output);
};
// Bind the callback to the change event of the collection.
library.on('change', event_callback);
// Trigger the change event and pass context to the handler.
library.trigger('change', 'change is good.');
// Passing no parameters to the off method unbinds all event callbacks to the object.
library.off();
// This trigger does not have a response.
library.trigger('change');
Alloy Model and Collection objects don't support the Titanium addEventListener
, removeEventListener
and fireEvent
methods.
If you are using Alloy's Model-View binding mechanism, the Backbone add, change, destroy, fetch, remove, and reset events are automatically bound to an internal callback to update the model data in the view. Be careful not to override or unbind these events.
If you want to fire or listen to multiple events, Backbone.js uses spaces to delimit its events in the event string; therefore, do NOT name any custom events with spaces.
For more details, see the Backbone.Events API (opens new window).