• If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

How-to-do-views-and-controllers

Page history last edited by Erich Ocean 14 years, 5 months ago

NOTE: The API calls in this article are extremely out of date (from SproutCore 0.9) and not compatible with SproutCore 1.0. 

 

Introduction

This tutorial / concept description is intended for starters with Sproutcore. It is assumed you don't know much about the foundations and origins of Sproutcore, such as Cocoa.

Using the basic example of a Contacts applications, an attempt is made to describe how a few very essential elements of Sproutcore (The Store, Views and Controllers) can be used together to write a working application.

 

The Store

It may seem a bit strange to start at the Store, but this is the place where all data in your application will come together.

When records are loaded from either fixtures or from a data backend, they usually end up in the Store. Although the name may be suggest otherwise, the Store is actually not a storage facility, but rather an indexer: it holds all references to all records in an application.

Moreover, the Store object provides methods to search for records, create new ones, updating existing ones and delete records no longer useful.

The Store object is part of the Sproutcore framework and therefore its path is SC.Store.

 

You can regard the Store as a kind of local database, containing a subset of all records in the data backend.

SC.Store could have been implemented as a storage facility, but that would have meant not using one of the very powerful features of the JavaScript programming language: pointers.

 

In JavaScript everything is both a function, a variable and a pointer. So when you retrieve a set of records from the Store, you are actually getting a set of pointers to those records and not the records themselves. This is really powerful, as it means that changes to that record are automatically available everywhere you use a specific record in your application.

 

There are two ways of retrieving records from the Store:

 

Using the SC.Store object

You can use the SC.Store object functions to retrieve records, for example by searching:

 

SC.Store.findRecords(MyApp.Contact);

 

This call returns all records in the Store of which the Model (the record template) is defined as MyApp.Contact.

Please note: make sure the Model supplied is an object that extends from SC.Record. If you get errors like 'recordType._storeKey is not a function', the object passed as a parameter is not a proper SC.Record object.

 

It is also possible to search more specifically by supplying a search hash:

 

SC.Store.findRecords( { 'guid' : 1 } , MyApp.Contact);

 

You can add more properties to match in the search hash, but be aware this call only returns records if all the properties are set to that value.

A call like

 

SC.Store.findRecords( { 'guid' : 1, 'firstName' : 'John'}, MyApp.Contact)

 

is equivalent to an SQL query like

 

"SELECT * FROM Contact WHERE 'guid' = 1 AND 'firstName' = 'John';

 

Using the Model

Another method of getting records from the Store is using the Model:

 

MyApp.Contact.findAll();

 

will return all records from the Store based on that Model.

To search records using a hash, you can use the find function:

 

MyApp.Contact.find( { 'guid' : 1 });

 

The search hash follows the same rules as the SC.Store.findRecords search hash.

There is one abbreviated call possible to get a single record by guid:

 

MyApp.Contact.find(1);

 

will return the Contact record having guid 1.

 

Views

 

The way Views work in Sproutcore is very much alike the way they do in the Mac OSX application framework Cocoa.

Every view is a separate object providing all functionality concerning that particular view. A button for example will have to be able to call an action when it is pressed, so it will have functionality to handle calling a given function. A list view will need functionality to handle multiple items as its content and selection of items. A view also needs to be able to draw itself on the screen.

All this functionality is contained within the view object itself and being itself is the only thing a view should do.

So, a view itself will never retrieve data from the Store or perform actions on records. As you see, there is a strict separation between objects.

 

As a view will never connect to the Store itself, a connection needs to be created between a view and the records in the Store to be able to actually display information from records. This connection is created using Controllers.

 

Controllers

A controller is a special type of object intended to handle other objects. In the Sproutcore Framework you will mostly use the ArrayController and the ObjectController. It plays the role of intermediate between views and records.

 

SC.ArrayController

The array controller is a special controller for handling arrays and sets of records. It is a proper intermediate between a list view and a set of records. You can set the content of an array controller using an array of records retrieved from the Store:

 

MyApp.myContactListController.set('content', SC.Store.findRecords(MyApp.Contact));

 

or

 

MyApp.MyContactListController.set('content', MyApp.Contact.findAll());

 

SC.ObjectController

An ObjectController is a controller intended to handle a single object, which very often is a record.

The Object Controller provides a transparent interface to the properties of an object. This means that if you have a Contact record with a property called firstName, this property will appear on the outside of the Object Controller as soon as a Contact record is in its content.

So, you can get the firstName property of the record in the Object Controller by getting it directly from the Object Controller:

 

MyApp.myContactObjectController.get('firstName');

 

An Object Controller also handles functionality about saving changes in the record to the Store among others. It is very suitable for handling detail views: a view composed of a few views displaying or editing properties of a record.

 

But how to connect a controller to the views? You could write specific functions to update the view, but that would be rather slow and would hughely increase the amount of code needed for your application. Let's introduce an important part of the magic of SC: Bindings.

 

Bindings

A binding is a special connection provided by Sproutcore. A binding will connect a source and a destination or multiple destinations. It will also update the destination or destinations after the source has changed.

This makes it a very useful tool for connecting views and controllers, because a binding will cause the view to update as soon as the source of the binding - a controller - has changed content.

 

To create bindings in JavaScript, just add the word Binding to a property.

If we want to bind a list view to an array controller, we have to define a contentBinding.

 

contentBinding: 'MyApp.MyContactListController.arrangedObjects'

 

In this key-value pair the addition Binding to the key causes Sproutcore to create a binding to the object in the value.

 

This means that you won't have to update the view system manually, because if the content of the controller changes, the views bound to that controller will change too. Let's have a look at how to do this.

 

Practical Example

In the application MyApp there is an ArrayController for the list of contacts in that application as well as a list view (side note: which always need to be in a scroll view). 

 

The list view is set up in such a way that the content of that list view object is bound to a special property called arrangedObject on the ArrayController. To be able to capture the selection on that list view too, the selection of the List view is bound to the selection property on the ArrayController. So, as soon as anything is set as content of the ArrayController, the view updates automatically.

 

In the application MyApp there is also a view for editing the properties of the Contact, the details view.

There is a TextFieldView for the first name, last name and phone number. As these views only show properties of one record, we need an Object Controller to handle that one object.

We bind the TextFieldView for the first name to the firstName property on the ObjectController itself, and do the same for the lastName and phoneNumber property. So, whenever a record is set to be the content of that ObjectController, the values of the properties within that record will now automatically appear in the text field views.

 

But how to set up the quite common display of having a list of contacts and have the items appear in the details view whenever an item is selected in the list?

We do have the set up for the detail view, we have also a list with items, but there is no connection yet between the list view and the detail view.

What do we exactly need?

 

We want the selected record to be the record to be displayed in the detail view. So that selected record has to become the content of the Object Controller. The selection in the List view is automatically forwarded to the ArrayController using a binding, so we can get the record from MyApp.MyContactListController.get('selection');

 

We could write an observer function to put the selection into the content of the ObjectController as it changes, but there is of course an easier way that does exactly the same without needing to write a special function: the binding.

 

When we set up the ObjectController to have

 

contentBinding: 'MyApp.MyContactListController.selection'

 

in it's definition, the content is automatically changed whenever the selection changes.

 

As a side note: if you type

 

MyApp.MyContactListController.get('selection');

 

into the FireBug command line, you will notice that the selection is returned as an array. So the content of the ObjectController actually is an array and not a record. This is however taken care of in the ObjectController itself, so you can both set a normal record object as a content or an array with one object in it and it will work the same. 

 

Comments (3)

Luke Randall said

at 4:51 pm on Jun 14, 2009

> MyApp.Contact.findAll();
> MyApp.Contact.find( { 'guid' : 1 });
> MyApp.Contact.find(1);

With master (as at 13 June), these require passing the store as the first argument (ie MyApp.Contact.findAll(MyApp.store)). Is this a bug or has the behaviour changed? If so, what is the reasoning behind the change (since I would have thought MyApp.store is implied by MyApp.Contact)?

Erich Ocean said

at 6:18 pm on Jun 14, 2009

It's no longer implied, because SproutCore 1.0 supports multiple stores (especially, nested stores). Thus you need to specify both the store and the model object in a findAll call.

Larry Siden said

at 1:03 pm on Nov 6, 2009

This doc makes several references to SC.Store.findRecords(), but there is no such function defined for SC.Store.

>>> SC.Store.findRecords.toString()
TypeError: SC.Store.findRecords is undefined

You don't have permission to comment on this page.