Every repository with this icon (
Every repository with this icon (
SproutCore’s Modern Model Layer: Part 2
by Erich Ocean (onitunes)
One of the advantages of the modern MVC approach is the ability to lazily load Model objects into your application, and SproutCore supports this out-of-the-box with someting called the Store.
SC.Store
An SC.Store is a predefined object that keeps a reference to all of the model objects loaded into your application, regardless of their recordType (What’s recordType?). The reason this matters is that SC.Store gives you a nice target for your controllers to observe. This in turn keeps your controllers isolated from the details of how your model objects get loaded into your application, and that’s a Good Thing™.
$ sc-init sample_appNow create a new model class:
$ cd sample_app $ sc-gen model sample_app/my_recordNow let’s run the application and start playing with our app in FireBug:
$ sc-serverOpen up FireFox (with FireBug already installed) to http://localhost:4020/sample_app. Click on the FireBug icon to start up FireBug. At the console, run the following:
>>> SC.Store.records();
[ ]
You should see an empty array. Let’s create a new record:
>>> SampleApp.MyRecord.create({ 'name': "Greek God" });
Record({ guid="@1402" }) name=Greek God _bindings=[0] _observers=[0]
>>> SC.Store.records();
[ ]
Hmm. Not what we expected. Let’s try this instead:
>>> SC.Store.addRecord( SampleApp.MyRecord.create({ 'name': "Greek God" }) );
>>> SC.Store.records();
[Record({ guid="@2455" }) name=Greek God _bindings=[0] _observers=[0]]
That’s better! So, we have to add a newly-created record to the store if we want it to show up there. Wouldn’t it be great if we didn’t need to remember to do that? Here’s how:
>>> SampleApp.MyRecord.newRecord({ 'name': "Greek God" });
>>> SC.Store.records();
[Record({ guid="@2455" }) name=Greek God _bindings=[0] _observers=[0]]
As you probably guessed, the newRecord() helper both creates a new object and adds it to the store.
Observing SC.Store
So, we’ve figured out how to add records to the store. Now let’s add a controller to the equation. First, generate the controller:
sc-gen controller sample_app/my_records SC.CollectionController
Open up clients/sample_app/controllers/my_records.js. Add this to the top of the file:require('models/my_record');
Now replace the following line:
with:// TODO: Add your own code here.
content: SC.Collection.create({ recordType: SampleApp.MyRecord })
Basically, we want SampleApp.myRecordsController to be an SC.CollectionController whose content property is an SC.Collection of records of type SampleApp.MyRecord. Although there are other options we could configure, the defaults are good enough for our purposes.
Back in FireFox, refresh the page to get our updates. Then open up FireBug and type this at the console:
>>> SampleApp.myRecordsController.get('arrangedObjects');
[ ]
That’s to be expected, since we haven’t added any records yet. Let’s do that now:
>>> SampleApp.MyRecord.newRecord({ 'name': "Greek God" });
>>> SampleApp.myRecordsController.get('arrangedObjects');
[ ]
Hmm. That’s not what we expected. Let’s see if the collection we created as the content for our collection controller is being updated:
>>> SampleApp.myRecordsController.getPath('content.count');
1
Okay, that’s a little better. At the very least, our collection is being updated. SC.CollectionController doesn’t have a refresh() (or similar) method, so that’s out. Let’s try re-setting the collection as the content property and see what happens:
>>> SampleApp.myRecordsController.set('content', SampleApp.myRecordsController.get('content') );
>>> SampleApp.myRecordsController.get('arrangedObjects');
[Record({ guid="@310" }) name=Greek God _bindings=[0] _observers=[0]]
Bingo! Okay, so our collection controller can project the records in our collection, but for whatever reason it’s not being notified when our collection gets updated. Hmm.
Before we go any further in “fixing” this problem, let’s do a little more work at the console. So far, we’ve only added one object, and we also started out empty. Both zero and one are edged cases when working with a set of objects. By testing with more objects, we’ll better be able to characterize the behavior.
At the console, type:
>>> SampleApp.MyRecord.newRecord({ 'name': "Roman God" });
>>> SampleApp.myRecordsController.getPath('content.count');
2
>>> SampleApp.myRecordsController.get('arrangedObjects');
[Record({ guid="@28829" }) name=Roman God _bindings=[0] _observers=[0], Record({ guid="@310" }) name=Greek God _bindings=[0] _observers=[0]]
Wow! Not what we expected. This time, our arrangedObjects property updated as expected, automatically. What’s going on here?
In the course of developing this tutorial, I uncovered the above anomaly but decided to leave this approach in anyway. The underlying problem here is that we set the content property on our collection controller too early in the loading process, so SproutCore wasn’t able to hook it up properly at that point.
The solution is to do what we would have done anyway (later in the tutorial) and set up our collection elsewhere. So, here are the changes we’re going to make. First, change clients/sample_app/controllers/my_records.js back to its original state, removing the line we added:
// ==========================================================================
// SampleApp.MyRecordsController
// ==========================================================================
require('core');
/** @class
(Document Your View Here)
@extends SC.CollectionController
@author AuthorName
@version 0.1
@static
*/
SampleApp.myRecordsController = SC.CollectionController.create(
/** @scope SampleApp.myRecordsController */ {
// TODO: Add your own code here.
}) ;
In clients/sample_app/models/my_record.js, create our collection:
// ==========================================================================
// SampleApp.MyRecord
// ==========================================================================
require('core');
/** @class
(Document your class here)
@extends SC.Record
@author AuthorName
@version 0.1
*/
SampleApp.MyRecord = SC.Record.extend(
/** @scope SampleApp.MyRecord.prototype */ {
// TODO: Add your own code here.
}) ;
SampleApp.MyRecord.allRecords = SC.Collection.create({ recordType: SampleApp.MyRecord }); // returns all records
And finally, in clients/sample_app/main.js, set our collection to the content property of our collection controller:
// ==========================================================================
// SampleApp
// ==========================================================================
// This is the function that will start your app running. The default
// implementation will load any fixtures you have created then instantiate
// your controllers and awake the elements on your page.
//
// As you develop your application you will probably want to override this.
// See comments for some pointers on what to do next.
//
function main() {
// Step 1: Load Your Model Data
// The default code here will load the fixtures you have defined.
// Comment out the preload line and add something to refresh from the server
// when you are ready to pull data from your server.
SampleApp.server.preload(SampleApp.FIXTURES) ;
// TODO: refresh() any collections you have created to get their records.
// ex: SampleApp.contacts.refresh() ;
SampleApp.MyRecord.allRecords.refresh(); // awaken the collection
// Step 2: Instantiate Your Views
// The default code just activates all the views you have on the page. If
// your app gets any level of complexity, you should just get the views you
// need to show the app in the first place, to speed things up.
SC.page.awake() ;
// Step 3. Set the content property on your primary controller.
// This will make your app come alive!
// TODO: Set the content property on your primary controller
// ex: SampleApp.contactsController.set('content',SampleApp.contacts);
SampleApp.myRecordsController.set('content', SampleApp.MyRecord.allRecords);
} ;
Now reload your application in FireFox and type in the following:
>>> SampleApp.MyRecord.newRecord({ 'name': "Greek God" });
>>> SampleApp.myRecordsController.get('arrangedObjects');
[Record({ guid="@310" }) name=Greek God _bindings=[0] _observers=[0]]
Success! That leaves one final nitpick. In our model definition, we’re defining a collection like this:
SampleApp.MyRecord.allRecords = SC.Collection.create({ recordType: SampleApp.MyRecord }); // returns all records
Instead, SproutCore has a helper that does the same thing, with less typing:
SampleApp.MyRecord.allRecords = SampleApp.MyRecord.collection(); // returns all records
Note: Each call to SampleApp.MyRecord.collection() returns a new collection object.
And that concludes Part 2 of the Modern Model Layer series. Continue on to Part 3.
Comments
Maybe it would be useful to explain why the property you are setting is the ‘content’ property in MyRecordsController. As far as I understand the entire concept, the content property is the main vehicle to talk between several objects in the framework. By the way: I think it is a bit confusing to talk about a anomaly the ‘standard’ reader of this tutorial will have no knowledge about. -Maurits
It seems a bit strange to me to create collections as a member of a type. As the type is actually a function, it is quite hard to find it when using the tab key at the command line of FireBug. My approach would be like the one in the REST tutorial: create the collection in main.js:
SampleApp.allRecords = SampleApp.MyRecord.collection();
SampleApp.myRecordsController.set('content',SampleApp.allRecords);
- Maurits
Something else I realised is that there is no explanation of the syntax used. I don’t think it is necessary to add a complete chapter about it to this tutorial though, as it would be off topic a bit, but a small explaination of the ({ 'name' : 'value', 'name2':'value2'}) and calling it a hash would be helpful to the programmer relatively new to JavaScript. The only programming language I have seen where things like this were possible is Lisp. It took me some time to link the name hash with what it actually was, and it turned out I was doing it all the time. It would also help in navigating the sc-docs, as ‘hash’ is quite frequently used there (for example the findRecords() function).
- Maurits
I second your thought, Maurits. I found that the decisions for determining what is lowercase and what is uppercase appears rather random and I still don’t follow the logic. For example, MyRecords and myRecordsController, I spent more that a little bit of time tracking this one down. I am assuming there is a reason for this type of naming convention and I would really enjoy knowing what that is. Thanks, Anita.
- My comment here would be in the beginning of the article, it talks about the SC.Store class, what it is and why it is important… so the controller has something to observe. Then the article goes on to create a controller that is sub classed from the SC.CollectionControler class, who’s content is a SC.Collection object and how the controller’s content is updated when new records (model object) are added. However, the article never goes on to explain how the SC.Collection object is linked to the store (how it magically gets the records it displays). Also, it would be worth while for the article to mention what a controller subclassed from the SC.CollectionController class can be used for – like using it as the content for a SC.ListView, etc. —gskluzacek





