Understanding TruckJS Models
Truck’s $.Model method is an object factory that provides encapsulation and abstraction. When you initialize a new model, this method returns an object with your data stored in its private scope. The data is then only accessible through the model’s accessor methods.
Although it has become a fashion for web frameworks to eschew models in favor of using raw JavaScript objects, this has several disadvantages. First and foremost - the data can be easily and inadvertently modified without your knowledge. Secondly, implementing data binding will require workarounds involving cloning of the data to compare against the original and polling to run the comparison. Truck does not have this problem because it has real models. The methods that modify a model’s data also publish those changes to the system. So the system just needs a passive subscription service to listen for changes published by the models. This event-based approach makes Truck’s data binding very light and efficient.
Creating a model is quite simple. You need to provide the model two things: data and a handle. The handle is used by the system to identify the model. When the model changes, it notifies the system using this handle. Any mediators listening for this handle will intercept the model’s changes. Normal we like to name a model handle with the post fix "-handle".
var data = [1,2,3,4,5]; var MyDataModel = $.Model(data, 'my-data-handle');
Models are of two types - objects or arrays. Use an array when your data is key value pairs. Use an array when your data is a collection of similar items:
var obj = {name: 'Joe', age: 32}; var MyObjModel = $.Model(obj, 'my-obj-model'); var people = [ {name: 'Joe', age: 32}, {name: 'Sam', age: 26}, {name: 'Mary', age: 25} ]; var PeopleModel = $.Model(people, 'people-handle');
You can create empty models in which you can put data later. Provide and empty object or array and a handle:
// Empty object model: var MyObjModel = $.Model({}, 'my-obj-model'); // Empty collection object: var PeopleModel = $.Model([], 'people-handle');
You can use the model methods to add content to an empty model. These methods are discussed below.
After you have assigned your data to a model, you really don’t need it anymore, so you could just get rid of it:
<pre class="prettyprint linenums">var data = [1,2,3,4,5]; var MyDataModel = $.Model(data, 'my-data-handle'); data = undefined; </pre>
Models can hold two types of data: a single object of key value pairs, or an array of data, usually objects, such a merchandise, employees, etc. Models have some methods that work for objects and others that work for collections. Object methods only work when the data is an object, same for collection methods. But some methods work for both. When accessing and manipulating your data you do need to be aware of what kind of data you are dealing with. If you’re not sure, you can query a model to discover its type:
var obj = {name: 'Joe', age: 32}; var MyObjModel = $.Model(obj, 'my-obj-model'); MyObjModel.getType(); // returns "object" var people = [ {name: 'Joe', age: 32}, {name: 'Sam', age: 26}, {name: 'Mary', age: 25} ]; var PeopleModel = $.Model(people, 'people-handle'); PeopleModel.getType(); // returns "array"
Truck’s views can be bound to a model. When you bind a view to a model, Truck creates a mediator to listen to the model. By default, when you modify the data of a model, the model dispatches and event with its handle to notify the system of the change. If a view is bound to that model, the mediator will intercept that handle message and update the view with the new version of the model’s data.
However, you can control whether a model announces its changes or not. When performing any modification of a model, you can provide an optional last parameter of “true”. This tells the model not to publish its changes. This is useful where you need to make a number of changes to a model that a view is bound to and don’t want all the intermediate changes being propagated to the view. When you’re done you can simple re-render the view and it will get the latest state of the model.
If you put an object in your model, you can access and modify its data with the following properties:
setProp(property, doNotPropogate)
This method takes two arguments: a property name and a value. If the property does not exist on the object, it will be added. If the property exists, its value will be changed to the provided value.
MyObjModel.setProp('firstName', 'John'); MyObjModel.setProp('lastName', 'Doe');
If we pass an optional, third parameter of “true”, the model will not propagate its changes:
// Do not propagate the modification: MyObjModel.setProp('firstName', 'John', true);
This method lets you get a property from a model. If the property doesn’t exist you get undefined.
var firstName = MyObjModel.getProp('firstName');
setObject(object, doNotPropogate)
This method sets the content of the model to the provided object. This is useful where you have an empty object model and want to add a number of properties quickly. If the model already has content, this will replace that content with the provided object.
var MyObjModel = $.Model({}, 'my-obj-handle'); MyObjModel.setObject({ firstName: 'John', lastName: 'Doe', age: 23 });
If we pass an optional, second parameter of “true”, the model will not propagate its changes:
// Do not propagate the modification: MyObjModel.setObject(obj, true);
mergeObject(object, doNotPropogate)
This method lets you merge an object into a model’s object. This is useful where you want to update a number of properties with new values quickly, or add new properties.
var MyObjModel = $.Model({firstName: 'John'}, 'my-obj-handle'); MyObjModel.mergeObj({ lastName: 'Doe', age: 32 });
If we pass an optional, second parameter of “true”, the model will not propagate its changes:
// Do not propagate the modification: MyObjModel.setObject(obj, true);
Collections of objects are the most likely candidate for consumption in a mobile web app. We’re talking lists, long lists. Having a model that handles collections enables easy data binding. With a model, you would have to duplicate the collection and run a poll to check for changes. The more collections and the bigger the collections, the longer it takes for the dirty checking to discover changes. In contrast, Truck’s model handles notifying the system when a model’s collection changes.
As we saw above, you can initialize a collection model by assigning it an array, or you could make an empty array. Collection models have a wide number of methods for manipulating them. These mirror many common array methods, while some are unique to models:
The size method lets you get the number of items in a model’s collection. This is equivalent to [].length.
if (myModel.size()) { console.log('We got items!'); }
push(obj, doNotPropogate)
This method allows you to push data onto the end of a model’s collection. If you pass a second optional true value, the model will not publish it changes to the system.
// Propagate the changes: myModel.push({name: 'Joe', age: 32}); // Don't propagate the changes: myMode.push({name: 'John', age: 25}, true);
This method lets you pop off the last item in a model’s collection.
var lastValue = myMode.pop();
unshift(obj, doNotPropogate)
This method lets you push data onto the begining of a model’s collection. If you pass an optional second true value, the model will not propagate its changes.
// Add data to the start of the colleciton. // Propagate the changes: myModel.unshift({name: 'Joe', age: 32}); // Don't propagate the changes: myMode.unshift({name: 'John', age: 25}, true);
This method will remove the first item from the beginning of a model’s collection. If you pass a true value, the model will not propagate its changes to the system.
concat(array, doNotPropogate)
This method lets you concatenate an array of data to your model. This allows you to add a large set of items to a model. By passing an optional second true value you can tell the model not to propagate its changes.
// Add more items to the model, // and propagate the change: PeopleModel.concat([ {name: 'Joe', age: 28}, {name: 'Suzie', age 26}, {name: 'Sam', age 31} ]); // Add more items to the model, // but do not propagate the changes: PeopleModel.concat([ {name: 'Joe', age: 28}, {name: 'Suzie', age 26}, {name: 'Sam', age 31} ], true);
insert(position, data, doNotPropogate)
This method allows you to insert an object into your model at the provided position. You can also provide an optional third parameter to tell the model not to propagate its changes. The position value is a zero-based number.
// Insert object at the fifth position: PeopleModel.insert(4, {name: 'Joe', age: 18}); // Don't propagate the change: PeopleModel.insert(4, {name: 'Joe', age: 18}, true);
Running this method on a collection eliminates any duplicates. This is really useful in cases where the user might have submited the same value multiple times without realizing.
// Eliminate duplicates in the model: PeopleModel.unique();
This method allows you to loop over the items in the collection while executing a callback. The callback gets two parameter values - the context and the index. This works exactly like the Array.forEach method.
PeopleModel.forEach(function(person) { console.log(person.name); if (person.age < 21) { console.log('Not old enough!'); });
This method allows you to filter the contents of a collection. This works the same as Array.filter.
// Function to filter model objects by id: var filterModel = function(id) { var whichPerson = MyModel.filter(function(person) { return person.guid === id; }); console.log(whichPerson.name); }; // Get object with this id: filterModel('asd234asd241');
This method allows you to map a function to each item in the collection. This works the same as Array.map.
MyModel.map(function(person) { console.log(person.name); });
This method allows you to pluck all the values of a property from a collection. For example, if you wanted to get the age of all people in PeopleModel, you would do the following:
var ages = PeopleModel.pluck('age');
This method lets you get an item from a collection based on its numerical position. The numbers are zero-based.
This method lets you find the index value of a collection item based on its property value. It takes two arguments, a property and a value.
var position = PeopleModel.index('name', 'Joe');
This method performs a normal array sort on the model’s collection.
// Sort the model in ascending order: PeopleModel.sort();
This model allows you to reverse the order of a model’s collection.
// Reverse the order of the model: PeopleModel.reverse();
This model allows you to sort a model by a property or multiple properties. By default the sort is in ascending order. You can tell Truck to sort a property set in descending order by prefixing the property with a hyphen.
// Sort by name and age: PeopleModel.sortBy('name', 'age'); // Sort in descending order by age: PeopleModel(-age);
You can sort a collection by as many properties as makes sense for what you are doing.
This method allows you to run an arbitary callback on the method.
MyModel.run(function(model, data) { var names = model.pluck('name'); names.forEach(function(name) { console.log(name); }); data.forEach(function(job) { console.log(job); }); }); </pre>
setItemProp(index, prop, value, doNotPropogate)
This method allows you to set a property and value on an object at a particular index in a collection. By providing an optional forth parameter of true, you can tell the model not to propagate its change.
MyModel.setItemProp(23, 'price', '300.00'); // Tell the model not to dispatch the changes: MyModel.setItemProp(23, 'price', '300.00', true);
This method lets you get the value of a property on an object at a particular index in the collection.
// Get the name property from // object at position 15: MyModel.getItemProp(14, 'name');
deleteItemProp(index, prop, doNotPropogate)
This method lets you delete a property from an object at a particular index in a collection. An optional third true parameter tells the model not to propagate its change.
// Delete name off of object at position: 27: MyModel.deleteItemProp(26, 'name');
The following methods are shared by models of objects and collections.
delete(data, doNotPropogate)
This method allows you to delete a collection item from the model. You can tell the model to delete and item based on its index value. Index values are zero-based numbers.
// Delete property from model's object: MyModel.delete('firstName'); // Delete 10th object from model's collection: MyModel.delete(9); // Don't propagate the change: MyModel.delete(9, true);
If you were making a lot of changes to a model and had told it not to propagate its changes, when you are done you can tell the model to publish its current state by poking it. This takes no arguments.
// Modify a model and do not propagate its changes: PeopleModel.pop(true); PeopleModel.unshift({name: 'Dan', age: 42}, true); // Let publish the model's current state: PeopleModel.poke();
Find out what handle a model is using.
var handle = PeopleModel.getHandle();
Like most things in Truck, you can change the handle a model is using. When would you want to do this? If you want the models changes to be captured by a mediator listening to a different handle. <pre class="prettyprint linenums">`// Switch the people handel to the employee handle: PeopleModel.setHandle('employee-handle');
This method allows you to empty a model. This sets an object or array to empty. The object will have no properties and the array will have no length.
// Purge a model's object: ObjectModel.purge() // object is now {} // Purge a model's collection: ArrayModel.purge() // array is now []
This method lets you find out whether you model has any data. If the data has an empty object or array, this will return false.
You can find out if a model is iterable or not using this method. This is similar to using getType() to find out if the model is a collection. An object model will always return false. In contrast, even an empty model collection will return true because even an empty array supports forEach, etc.
This method returns whatever the current data of a model is.
var people = PeopleModel.getData();
This method lets you find out the last time the model was changed.
Model provide powerful features to enable you to accomplish tasks not possible with raw JavaScript objects. Frameworks that don’t have models integrated into them are missing out. Of course you can use standalone model libraries with such frameworks, but they lack the integration of a ground up architecture design based on models. Truck was built on the model. From models everything else was created.