Every repository with this icon (
Every repository with this icon (
SproutCore's Modern Model Layer
by Erich Ocean (onitunes)
The model layer of an MVC application contains so-called “model objects”. A Model object is an ordinary object meant primarily to store data (usually persistent data), computed properties, relationships to other objects, as well as the various transformations that are valid on that data.
For example, in a To-Do list manager, you would probably define a model object called Todo that contained the following data:
- the name of the todo (a String)
- a dueDate (a Date)
- whether or not the todo item isCompleted (a Boolean)
- whether or not the todo item currently isOverdue (a Function that returns a Boolean)
In SproutCore, you define new models using sc-gen:
$ sc-gen model my_app/todo
Here’s what the resulting clients/my_app/models/todo.js file contains:
// ==========================================================================
// MyApp.Todo
// ==========================================================================
require('core');
/** @class
(Document your class here)
@extends SC.Record
@author AuthorName
@version 0.1
*/
MyApp.Todo = SC.Record.extend(
/** @scope MyApp.Todo.prototype */ {
properties: ['name', 'dueDate', 'isCompleted'],
// isOverdue function (implemented below) goes here...
}) ;
Key-Value Coding and Key-Value Observing
Central to a modern model layer implementation are two complementary technologies: key-value coding and key-value observing. SproutCore has comprehensive, mature support for both.
NOTE: To take advantage of key-value coding and key-value observing, your JavaScript objects must be created from either
SC.Objector an object that extends it, likeSC.Record.
Key-Value Coding
Key-value coding simply means an object’s attributes (stored properties, computed properties, and relationships) are accessed indirectly using strings (the “key”) instead of directly, using normal object property access in JavaScript.
For example, to access the name property of a Todo object called todo using key-value coding, you would write:
var n = todo.get(‘name’) ;
You would not write:
// DON’T DO THIS IN SPROUTCORE
var n = todo.name ;
Similarly, to set the name property of a Todo object called todo, you would write:
todo.set(‘name’, “something else”) ;
In both cases, strings are used to identify the attribute in question.
Almost everything you want to do with SproutCore relies on the fact that you will only access an object’s attributes using key-value coding compliant methods. You must take care not to invalidate the assumptions behind key-value coding when you write your own code to manipulate the data stored in your model objects.
NOTE: Key-value coding is oftentimes abbreviated as “KVC”.
Key-Value Observing
Key-Value Observing allows one object to observe changes to an attribute of another object automatically. This is most useful in the Controller and View layers of an MVC application, whose purpose in the overall application architecture is to manage and display attributes of model objects.
Using key-value observing, controllers and views can automatically stay in sync with the attributes of the model objects in your application, without writing the “glue code” usually required.
To use key-value observing in your application, you need to keep your model objects key-value observing compliant. If you use the standard set() method of SC.Object, you will automatically be key-value observing compliant for stored properties. Stored properties are these properties: ['name', 'dueDate', 'isCompleted'] and isOverdue is a computed property.
To be key-value observing compliant for computed properties, you need to tell SproutCore what attributes your computed properties depend on. Let’s look again at the isOverdue attribute for our Todo object, which is a computed property. Here’s one likely implementation:
isOverdue: function() {
if ( this.get(‘isCompleted’) ) return NO ;
else return ( this.get(‘dueDate’) < new Date() ) ? YES : NO ;
}.property( ‘isCompleted’, ‘dueDate’ )
There are two parts here that need to be explained.
First, in order to make the isOverdue function into a computed property, accessible using get('isOverdue'), we called the property() method SproutCore predefined for us on all Function objects. The general usage pattern looks like this:
computedPropertyName: function() { }.property()
Second, to make the computed property key-value observing compliant, we passed the keys for the attributes we used to implement the computed property as parameters to the property() method, like this:
isOverdue: function() { }.property( ‘isCompleted’, ‘dueDate’ )
And that’s all there is to it! Define an attribute of an object as a computed property using the property() method of Function objects, and pass in the keys you depended on to compute the property as parameters to the property() method.
NOTE: Key-value observing is oftentimes abbreviated as “KVO”.
Setting computed values
User interface elements do not only read computed values, they also set computed values. In this case the computed property function can be extended to also function as a setter. Instead of having a separate getter and setter function, get and set is handled in one function. The general usage pattern looks like:
computerPropertyName: function(key, value) { if (value !== undefined) { // setter code } else { return computedValueExpression; } }.property()
Suppose there would be a button in toggle mode bound to the isOverdue property. It would retrieve its status from the isOverdue value, either true or false. When the button is pushed, it will change the value of the isOverdue property to true or false. The function to change the underlying attributes could look like:
isOverdue: function(key, value) {
if (value !== undefined) { // setter
if (value) {
this.set('isCompleted',false);
this.set('dueDate', new Date());
} else {
this.set('isCompleted', true);
}
} else {
if ( this.get(‘isCompleted’) ) return NO ;
else return ( this.get(‘dueDate’) < new Date() ) ? YES : NO ;
}
}.property('isCompleted', 'dueDate')
So to extend computed properties to be writable, the arguments key and value have to be added.
Comments
I think it would help to explain why the .get() and .set() functions are necessary. As far as I understand it, these functions are necessary for the Bindings to work. Something has to be kicked for these links to ‘magically’ work and these functions provide the hooks. – Maurits
—
Can an explanation be added as to why we need to pass in the keys of the attributes computed properties require, as arguments to the property() function? It would appear that they are not needed directly by the isOverdue() function, since they are gathered using, for example this.get(‘isCompleted’). – Jonathan Lister
property() is necessary so that the framework can update observers of the computed property when the attributes it depends on change. As you say, it is not for the benefit of the isOverdue implementation itself, but rather of any code (views, etc.) which are bound to the isOverdue value. – Rhett
Added the section about setters. Please correct or remove if I’m mistaken. Chris
—
We might want a bit of explication for custom getter/setters that aren’t computed properties. E.g., you have a “telephone” property in your model, and you want a #telephone method on the model to modify the input or output in some way. There are 2 principle considerations, from what I can tell: (1) the method has to have an trailing (empty) .property(); and (2) the method will override the default set behaviour as well as get behaviour, so the method has to incorporate setter functionality - endash
—
KVC: It’d be nice if Key Value Coding could happen automatically when you do todo.name because that’s less to type than, todo.get(‘name’). but i’m not sure if that’s possible and may create ambiguity… but if u still need to be able to have todo.name mean something else than todo.get(‘name’), then you could switch the meanings because most of the time we mean todo.get(‘name’)... but then this also may be counter-intuitive/confusing for people who really know javascript well (not me :) ) -MattDiPasquale (MattCollegeWikis)





