• 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
 

Todos 04-Hook Up Your Data

Page history last edited by Kevan Stannard 13 years, 4 months ago

Now we want to show the tasks in your data. To do this, you are going to need a controller. Controllers are used for data binding, filtering bound data, event handling, and acting as a delegate. They help to shuffle data between your models object and your views, and help to manage committing these changes back to the server when necessary.

 

Creating a Controller

 

In our case, we are going to need one controller for now called the tasksController. This will be a ArrayController since it helps to display a collection of records held in an array. The SproutCore generator will help you create one of these. On the command line, type the following:

 

sc-gen controller Todos.tasksController SC.ArrayController

 

This will create a file in apps/todos/controllers/tasks.js. Open the file and take a look at it. It should now read:

 

Todos.tasksController = SC.ArrayController.create( 

/** @scope Todos.tasksController.prototype */ {    

 

  // TODO: Add your own code here.  

 

}) ; 

 

IMPORTANT: SC.ArrayController and the related SC.ObjectController behave as a proxies for the object you set as their "content" property.  This means that when you work with controllers, instead of accessing properties on the controller content you will access the controller itself.   For example, you can normally bind views in your UI directly to the ArrayController and they will work as if you bound directly to the content array.  Later, you can change the content of the controller to point to another array and UI will update appropriately without needing you to reconfigure each UI item individually.

 

In the Todos app, this ArrayController is going to represent our list of todos.  We will eventually set the content property of this array to point to the list of todos.  For now though, we need to connect our ListView to the controller so that it can display that content.

 

Hooking Up Your View

 

Back in your main_page, you created an SC.ListView.  We want this list view to display the contents of the collection controller we just created.  We do this using binding.

 

Bindings are like wires that connect parts of your application. They automatically relay changes from one part of your app to the other. An ArrayController in particular has two properties that we need to bind to the list view:

 

  • arrangedObjects contains the array of Tasks for the current collection.
  • selection contains the currently selected Tasks.

 

To bind your ListView to these new properties, you simply need to add the bindings to your design. Open up your main page design again (in apps/todos/resources/main_page.js) and edit the SC.ListView design like so:

 

in apps/todos/resources/main_page.js:

contentView: SC.ListView.design({   

  contentBinding: 'Todos.tasksController.arrangedObjects',

  selectionBinding: 'Todos.tasksController.selection' 

}) 

 

Go ahead and reload your page now. You will notice that you task list is still empty. Your list view is now getting some content, but it is an empty array! The controller still doesn’t have any content. Let’s fix that.

 

Creating an array of tasks.

 

When you load your SproutCore application in your web browser, SproutCore loads your views and all of your classes and then finally calls your main() function. This is defined in apps/todos/main.js. The last thing you will do in this function is get the initial set of data you want to display and plug it into a controller. Let’s do that now. Open your main.js file and add the following to the bottom of the Todos.main() function in main.js:

 

in apps/todos/main.js:

var tasks = Todos.store.find(Todos.Task);

Todos.tasksController.set('content', tasks);

 

In your fully developed application, this code would reach out to your server and return an array of tasks from the server. Since we are still using fixtures, this method returns an SC.RecordArray with the task objects found in your fixtures. It then sets that record array as the content of your tasksController.

 

Remember that a controller will “proxy” its content property to any view that is bound to it. In this case, since you set the set of tasks as the content of the Todos.tasksController, the controller will proxy those records to the list view that is bound to it.  Go ahead and refresh now to see what happens:

 

Todos

 

Eureka! We have data! All of your tasks are listed there. You can even click on them to select.

 

Configuring Your List Items

 

So far so good, but the tasks do look pretty ugly. That’s because we haven’t told the list view how it should interpret the task object to display it yet. Since we haven’t given it any clues, it just converts the task to a string and you get what you see here.

 

As you might guess, you can configure how the ListView should display this by adding options in your main page again. Edit your SC.ListView in the main page like so:

 

in apps/todos/resources/main_page.js:

contentView: SC.ListView.design({     

  contentBinding: 'Todos.tasksController.arrangedObjects',     

  selectionBinding: 'Todos.tasksController.selection',     

  contentValueKey: "description",     

  contentCheckboxKey: "isDone",

 

  rowHeight: 21 

}) 

 

And refresh. Now we’re cooking! You can select your items, and check the boxes.

 

Todos

 

The contentFOOKey syntax here basically tells the ListView which properties on your content object map to the properties needed by its list items. In this case, the “value” property on a list item is set to show the description and the “checkbox” property is set to show the isDone state.

 

 

Create a calculated Property for the item count

 

Now that we have some data loaded we can have some calculated properties based on the data, like the number of records loaded or the number of selected items in the list. To do this we will create a computed property called "summary" that will compose the appropriate string to be set at the bottom view.

 

A computed property is a value that is generated dynamically whenever you try to access it.  Other languages have you implement computed properties by writing accessor methods for every property on your object, even those that you don't want to compute.  This often leads to a lot of boiler plate code like this Java-like example:

 

 getName: function() {

    return this._summary;

 },

 

 setName: function(newValue) {

    this._summary = newValue;

    return this;

 } 

 

Rather than force you to write accessor methods for every property on your object, SproutCore implements two generic accessor methods called get() and set().  You can use get() and set() to work with any property on any SproutCore object, even if you've never explicitly defined that property before.

 

Normally, when you call object.get('foo'), SproutCore will simply lookup the value of "foo" on your object and return it.  Likewise, when you call object.set('foo', newValue), it will simply update the value of foo.  This is the equivalent of the boiler-plate accessors shown above.

 

If you want to override this behavior and compute the property value instead, you can do so easily by defining a computed property method.  A computed property method is simply a method that ends with a special notation "property()".  This tells SproutCore that when you call get() on that property, it should run this function and return the result instead.

 

We are going to use the computed property facility now to implement the "summary" property on our tasksController.  The summary property will return a string that we can show in the UI indicating the number of items in the UI.  Here is the code you want to add:

 

 

in apps/todos/controllers/tasks.js:

Todos.tasksController = SC.ArrayController.create({

  ... 

  summary: function() {

    var len = this.get('length'), ret ;

 

    if (len && len > 0) {

      ret = len === 1 ? "1 task" : "%@ tasks".fmt(len);

    } else ret = "No tasks";

  

    return ret;

  }.property('length').cacheable()  

});

 

First we get the number of items in the array.  After that it goes through some logic to build the final string.

 

At the end of the function we call .property() , converting this function into a computed property. This property is recalculated if the 'length' changes. It is also cacheable, meaning that it won't be calculated everytime the property is called.

 

Note: Here 'length' refers to SC.ArrayController length.  A computed property can be calculated with one or more controller native properties and will be recalculated whenever native properties state changes.

 

To reflect the changes in your UI we have to bind the text in your summary view to the calculated property.   Find the "summaryView in your page design and replace the "value" property with a binding:

 

in apps/todos/resources/main_page.js:

summaryView: SC.LabelView.design({

  layout: { centerY: 0, height: 18, left: 20, right: 20 },

  textAlign: SC.ALIGN_CENTER,  

  valueBinding: "Todos.tasksController.summary"

}) 

 

Reload your app, you should see at the bottom of your screen the total of loaded records.

 

Now that you have your basic data setup, next we’ll work on wiring up the remaining bits of the UI.

 

Continue to next step: Step 5: Finishing the UI »

 

Comments (16)

Louis Chen said

at 8:10 pm on Sep 22, 2009

I followed the tutorial exactly, and I checked everything. Also, the debugger in Safari didn't show any error...
but "contentCheckboxKey: "isDone"" is not showing in Safari, FF, or Chrome. The checkbox seems to be there but you can't see it.
Why is that? Am I doing something wrong?

Charles Jolley said

at 10:51 am on Oct 2, 2009

There was a bug in SproutCore 1.0 Beta for a few weeks that broke the checkbox. It has been fixed as of Sep 28.

cornbread said

at 10:05 pm on Oct 9, 2009

in apps/todos/controllers/tasks.js this doesn't exist:
Todos.tasksController = SC.ArrayController.extend({

Do we add it below SC.ArrayController.create?
Please advise

Nate Milbee said

at 7:57 pm on Oct 12, 2009

For me, the example didn't work until I changed 'SC.ArrayController.extend' to 'SC.ArrayController.create'. I took the liberty of changing the code example, hope thats OK.

Charles Jolley said

at 8:16 am on Oct 27, 2009

That was the right fix. Thanks for updating it!

Larry Siden said

at 7:05 am on Oct 27, 2009

"At the end of the function we call .property() , converting this function into a computed property. This property is recalculated if the length or selection changes."

I think the author meant "This property is recalculated if the length changes." Changing the selection does nothing.

Charles Jolley said

at 8:15 am on Oct 27, 2009

Fixed. Thanks.

Jim Tobin said

at 8:47 am on Nov 9, 2009

"Back in your main_page you created a SC.ListView" -- I think I've been following along closely, but I don't see where that happened. I'll move along using the non-bold and the bold changes, assuming that's what everyone else has been doing, but thought I'd ask if a step was dropped in an edit of the wiki?

Jim Tobin said

at 8:50 am on Nov 9, 2009

Doh. middleView has a contentView of SC.ListView with an empty design...

Andrew Porter said

at 12:52 pm on Dec 3, 2009

Nothing shows up in SC.ListView. I finished out this section and went on to hook up the "Add Task" button. The summary looks accurate—it shows 3 tasks to start with, and adds a task every time I hit the "Add Task" button—but nothing shows up in the ListView.

I installed the sproutcore (1.0.1037) gem, not the abbot code from GitHub. This happens in Safari 4, Camino 2, and Firefox 3 on Mac OS X Snow Leopard.

Ido Ran said

at 3:41 pm on Dec 14, 2009

Hi, I've just complete this page and everything works great.
I did had problem that my application didn't refresh after changes, to overcome that I've stop sc-server, rm -R tmp, and start sc-server again.
I'm running on Mac 10.6 with Safari and SproutCore 1.,0.1037).
Enjoy.

sigUVA said

at 11:14 pm on Mar 21, 2010

Thanks Ido Ran. Deleting that tmp folder helped me out. I ran into the same issue.

Naresh said

at 11:26 am on Jun 10, 2010

I'm running this in Firefox 3.6.3 and when I resize the window, I don't see the Vertical Scrollbar appearing. Viewing the same in Chrome or Safari shows the scrollbar. Anybody knows what could be causing this?

Gregorio said

at 1:32 am on Jul 8, 2010

Hi,
I'm new. I have a problem at the "Creating an array of tasks." point. I followed the tutorial very strictly, but If I refresh the page after added "var query = SC.Query.local(Todos.Task); var tasks = Todos.store.find(query); Todos.tasksController.set('content', tasks);" to main.js I can't see anything. The Console Javascript of Google Chrome gives me this error: "Uncaught TypeError: Cannot call method 'set' of undefined".

Can you help me?
Thanks,
Greg

Jacob said

at 8:29 am on Aug 17, 2010

I have the same problem. :/

Paulmichael said

at 2:47 pm on Jul 23, 2010

User leankyle added a link to "antique furniture mall"...Seems some spammers have made their way in in the past few days...

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