Created
July 9, 2011 21:12
-
-
Save regularfry/1073970 to your computer and use it in GitHub Desktop.
Thoughts on a first pass-through of the Sproutcore getting started guide
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
=begin | |
I worked through this Getting Started guide on 2011-07-08, on an | |
up-to-date Debian Squeeze desktop with ruby 1.9.2-p180 installed via | |
RVM, and Sproutcore 1.6.0.rc.2 installed via Rubygems. The Getting | |
Started guide was the first literature on Sproutcore I exposed myself | |
to. I've interspersed notes about where I got confused in =begin/=end | |
blocks throughout, mostly in the form of rhetorical questions as they | |
appeared in my mind at the time. I did not watch the screencast. | |
A little about me: I'm primarily a Ruby back-end developer (6ish years | |
now), but I've got enough front-end experience to be dangerous; my JS | |
knowledge is fair. I've come at this not having played with the new | |
breed of JS app frameworks before, so I've got no preconceptions in | |
that department. However I am familiar with Rails and the MVC pattern | |
in general. | |
Feel free to use this feedback as you will, including ignoring it. I | |
hope it's useful. If you have any questions to be directed at a | |
fresh brain, I'm more than happy to answer them, and I'll shortly be | |
going through the follow-on tutorials so I may have more then. | |
-- | |
Alex Young | |
alex at blackkettle dot org | |
=end | |
Getting Started | |
After reading this guide, you will be able to: | |
Use SproutCore’s templating support to describe the appearance of your application. | |
Delegate handling of user events with views. | |
Use bindings to update views when your models change. | |
You will learn all of this by building a real Todo list application from the ground up using SproutCore. | |
1 Following Along | |
You can see the finished source code for this application on Github. To see it in action, click here. | |
There is also a screencast available on Vimeo. | |
2 Installing SproutCore | |
This guide assumes you have already installed SproutCore. If you have not, please install it now. | |
This tutorial requires the 1.6.0 beta. The Windows and Mac installers are both for the beta version, but if you install via RubyGems, you’ll need to use gem install sproutcore --pre to get the beta. | |
3 Generate Your New Application | |
First, generate your new HTML-based application. | |
$ sc-init todos --template | |
This will create several files needed for your application, inside the apps/todos directory. | |
apps/ | |
todos/ | |
todos.js – The JavaScript file that will define your models, views, and controllers. | |
resources/ | |
templates/ – Place all Handlebars templates here. | |
todos.handlebars – The main template for the application. | |
images/ – Place all images here. | |
stylesheets/ – Place all stylesheets here. | |
todos.css – The main stylesheet for the application. | |
tests/ | |
Buildfile – Tells SproutCore how your application should be built. Usually, you can just use the defaults provided. | |
README – The description for the project. | |
Go ahead and open todos.js now. You should see the following code: | |
apps/todos/todos.js | |
Todos = SC.Application.create(); | |
SC.ready(function() { | |
Todos.mainPane = SC.TemplatePane.append({ | |
layerId: "todos", | |
templateName: "todos" | |
}); | |
}); | |
=begin | |
Ok, here's my first problem. Why is "todos" duplicated here? Would it | |
matter if it was "todos_layer" and "todos_template"? Where do the | |
different entries show up later in the tutorial? I can assume from | |
the key names that layerId is implicated in managing the DOM and | |
templateName is for resource lookup (is that right? Still don't know), | |
but there's no indication as to where this all comes from. A link to | |
documentation would be good - what other properties are valid here? | |
=end | |
This code creates a namespace for your application (called Todos), then appends a new pane. A pane is responsible for event delegation and placing your templates into the DOM, but you’ll learn more about that later. | |
If you pass a camelcase name to sc-init, such as ToDos — notice the uppercase ‘D’ —, the namespace would instead be ToDos and the directories would be named with underscores (i.e. apps/to_dos) | |
4 Defining Your Model | |
In this tutorial, we want to create a list for managing todos. Users should be able to create a new todo with a specific task, then check it off once it’s done. | |
Let’s define our model as a new subclass of SC.Object: | |
apps/todos/todos.js | |
Todos = SC.Application.create(); | |
Todos.Todo = SC.Object.extend({ | |
title: null, | |
isDone: false | |
}); | |
SC.ready(function() { | |
Todos.mainPane = SC.TemplatePane.append({ | |
layerId: 'todos', | |
templateName: 'todos' | |
}); | |
}); | |
Make sure you insert the new code after the Todos object is created. | |
We’ve now defined a class with two properties: title, a String, and isDone, a Boolean. | |
5 Managing the Model Using a Controller | |
Now that we know what our data looks like, let’s create a controller to manage it. Since we want to maintain an ordered list of todos, we’ll use an instance of SC.ArrayController. | |
in apps/todos/todos.js | |
// at the end of the file | |
Todos.todoListController = SC.ArrayController.create({ | |
// Initialize the array controller with an empty array. | |
content: [] | |
}); | |
=begin | |
Again, this is confusing - where does the name "content" come from? Is | |
that required by SC.ArrayController.create? Having defined this, do | |
subsequent ArrayController instances (if that's a valid concept) share | |
the same storage array, or is a new array is allocated each time? | |
=end | |
In MVC frameworks, like SproutCore, the controller layer bridges the model layer, which is only concerned with a pure-data representation of objects, and the view layer, which is only concerned with representing those objects. | |
Now we have an array controller with no content. Let’s add a method to create a new todo: | |
in apps/todos/todos.js | |
// updating previous code | |
Todos.todoListController = SC.ArrayController.create({ | |
// Initialize the array controller with an empty array. | |
content: [], | |
// Creates a new todo with the passed title, then adds it | |
// to the array. | |
createTodo: function(title) { | |
var todo = Todos.Todo.create({ title: title }); | |
this.pushObject(todo); | |
} | |
}); | |
SC.ArrayController acts as a proxy onto its content Array. SproutCore will propagate any modifications made to the ArrayController to the content Array. | |
=begin | |
Are we taking advantage of this knowledge anywhere in this tutorial? A | |
pointer to precisely where this matters would be useful. At this | |
point, I don't know if I should be looking out for ArrayController | |
being used like an array. Also, where has pushObject() come from? | |
=end | |
6 Creating New Todos with a Text Field | |
We’ve provided a simple stylesheet to give the application some style. At this point, you should download the CSS and replace the empty file that sc-init generated for us at apps/todos/resources/stylesheets/todos.css. | |
We’ve got our model and controller set up, so let’s move on to the fun part: creating the interface for our users. The first step is to create a text field into which the user types the name of their todo. SproutCore’s TemplateView uses Handlebars templates to quickly define the application’s interface. While Handlebars makes it easier to markup HTML quickly, you’ll see that it has been extended to take advantage of SproutCore with very little additional effort. | |
To start building our views, let’s open resources/templates/todos.handlebars. You’ll see that the initialization pre-populated the file with a snippet of HTML: | |
apps/todos/resources/templates/todos.handlebars | |
<h1>Welcome to SproutCore!</h1> | |
Go ahead and replace that with: | |
apps/todos/resources/templates/todos.handlebars | |
<h1>Todos</h1> | |
<input id="new-todo" type="text" | |
placeholder="What needs to be done?" > | |
=begin | |
Is "new-todo" significant? Is there magic action being hung off this | |
because of that specific name? | |
=end | |
For more information about using Handlebars, visit the Handlebars website. To learn more about using Handlebars with SproutCore, make sure to check out the Using Handlebars Template guide. | |
Now that we’ve got model, view, and controller represented, it’s time to open the app in our browser and see how it looks. | |
During the development process, sc-server makes it easy to test your application. Just run the following inside of your project directory: | |
$ sc-server | |
Starting server at http://0.0.0.0:4020 in debug mode | |
To quit sc-server, press Control-C | |
>> Thin web server (v1.2.1 codename Bat-Shit Crazy) | |
>> Maximum connections set to 1024 | |
>> Listening on 0.0.0.0:4020, CTRL+C to stop | |
=begin | |
The first time I did this, I went *straight away* to | |
http://localhost:4020/. Yes, that was wrong, but it wasn't signposted | |
that I might see something unexpected there that has apparently very | |
little to do with the code we're writing. | |
=end | |
Open your web browser and navigate to http://localhost:4020/todos. You should see your application load. Once you’ve verified that the application is up and running, it’s time to tell SproutCore how to handle events for your <input> tag. When the user types in the field and presses return, we will create a new Todo and have it inserted into the content of the array controller. | |
In SproutCore, view objects are responsible for updating the DOM and handling events. Among other things, this allows us to buffer changes to the DOM for maximum performance and to support generic cross-platform event handling. Whenever you want to display dynamic content or handle events, you will use a view object. | |
in apps/todos/todos.js | |
// before SC.ready | |
=begin | |
Why is it important that the view is defined before SC.ready? What | |
does SC.ready do, exactly? | |
=end | |
Todos.CreateTodoView = SC.TextField.extend({ | |
insertNewline: function() { | |
var value = this.get('value'); | |
if (value) { | |
Todos.todoListController.createTodo(value); | |
this.set('value', ''); | |
} | |
} | |
}); | |
Since CreateTodoView contains a text field, we create a subclass of SC.TextField, which provides several conveniences for working with text fields. For example, you can access the value property and respond to higher level events such as insertNewline, when the user presses return. | |
=begin | |
It'd be handy at this point to know where I should be looking for more | |
available events. | |
Also, what are these get() and set() methods? Where have they come | |
from? Are there side-effects to using them? Are they intrinsic to | |
TextField? | |
=end | |
Now that we have defined our view, we need to attach it to HTML in our Handlebars template. Wrap the <input> like so: | |
apps/todos/resources/templates/todos.handlebars | |
<h1>Todos</h1> | |
{{#view Todos.CreateTodoView}} | |
<input id="new-todo" type="text" | |
placeholder="What needs to be done?" /> | |
{{/view}} | |
#view is a Handlebars block helper that assigns a SC.TemplateView to a section of HTML. This means that the behavior described in the specified view, such as event handling, will get associated with the HTML inside the block. | |
=begin | |
What else would be valid contents for that block? Could I use a | |
textarea? Again, is the id important? Does the #view helper in | |
general, or the CreateTodoView in particular, assume that there will | |
be a single DOM node for it to latch on to? If more than one, how does | |
it know where to attach events? If only one, why isn't the DOM node | |
inserted by the view helper, rather than relying on us typing it in | |
and potentially getting an ID typed wrong, or something (if that | |
matters)? | |
=end | |
Now that we have an interface to create new todos, let’s create the interface to display them. We’ll use the Handlebars #collection helper to display a list of items. #collection will create an instance of SC.TemplateCollectionView that renders every item in its underlying Array using the enclosed HTML. | |
in apps/todos/resources/templates/todos.handlebars | |
<!-- at the end of the file --> | |
{{#collection SC.TemplateCollectionView ¬ | |
contentBinding="Todos.todoListController"}} | |
{{content.title}} | |
{{/collection}} | |
We are using a continuation character (¬) to indicate that the lines should be entered unbroken. | |
=begin | |
Why does this matter all of a sudden? Is there something about | |
Handlebars templates which makes multiline entries problematic? | |
=end | |
Notice that we’ve also told the collection to bind its content property to our todoListController. For every item in the array controller, the collection view will create a new child view that renders the {{content.title}} template. | |
You set up bindings by creating a property whose name ends in Binding. In this case, we bind Todos.todoListController to the collection view’s content property. When one end of a binding changes, SproutCore will automatically update the other end. | |
=begin | |
It took me a couple of read-throughs of these two paragraphs to get | |
this; I think it's just worded unclearly. It's also confusing because | |
we haven't seen TemplateCollectionView before, so it's unclear what's | |
done by the #collection helper and what by the TemplateCollectionView, | |
and why we need both. | |
It might read better like this (assuming, of course, that this is | |
correct; I've had to read between the lines a lot): | |
Here, the <a href="somedocs">#collection</a> helper sets up a <a | |
href="someotherdocs">TemplateCollectionView</a> to render the | |
'{{content.title}}' template for each element in | |
Todos.todoListController.content. In the | |
'contentBinding="Todos.todoListController"' attribute, | |
'contentBinding' tells #collection that 'content' in the template | |
should be filled with each element of the 'content' field in | |
todoListController. Because we've given an attribute ending in | |
'Binding', #collection can set up bidirectional handlers to keep our | |
model and view correctly in sync. | |
=end | |
This is a good time to visit http://localhost:4020/todos in your browser (or reload if you still have it open). It should look the same as before. Type a todo into the text field and hit return. | |
Look at that! As soon as we create a new todo and insert it into the array controller, the view updates automatically. | |
You’ve now seen a little bit of the power of SproutCore. By using SproutCore’s bindings to describe the relationship between your data and your views, you were able to change the data layer and let SproutCore do the hard work of updating the view layer for you. | |
This is actually a core concept in SproutCore, not just something that demos well. SproutCore’s binding system is designed with the view system in mind, which makes it easy to work directly with your data and not need to worry about manually keeping your view layer in sync. You will see this concept over and over again in the rest of this tutorial and in other guides. | |
7 Getting Things Done | |
We now have the ability to add todos, but no way to mark them as done. Before the frustration of a never-ending todo list gets the better of us, let’s add the ability to mark todos complete. | |
The first thing we need to do is add a checkbox to each todo list item. As was mentioned earlier, if we want to handle events, such as user input, we need a view to manage that portion of HTML. In this case, we are adding a checkbox and want to be notified whenever the value of the checkbox is changed by the user. Remember that we can assign views using the #view helper and provide the HTML content for the assigned view in our template. However, we can also reference views that provide their own HTML content using the view helper — notice the lack of the #. | |
=begin | |
This is confusing. Is it as general as for any helper 'foo', '#foo' | |
takes a template block, but 'foo' doesn't? Is that enforced by the | |
framework? By Handlebars? Is it just a convention? | |
=end | |
For instance, we may want to create a complex view that will be used repeatedly and don’t want to have to update all the templates whenever it needs to be updated. Here’s what the todo list looks like after updating it in todos.handlebars: | |
in apps/todos/resources/templates/todos.handlebars | |
<!-- replacing the previous code --> | |
{{#collection SC.TemplateCollectionView ¬ | |
contentBinding="Todos.todoListController"}} | |
{{view Todos.MarkDoneView}} | |
{{/collection}} | |
Now let’s implement the Todos.MarkDoneView, which we just referenced. Since our view implements a checkbox, we will subclass the SC.Checkbox control. This gives the view a value property that reflects the value in the DOM, as well as a title property that displays a label for the checkbox. | |
=begin | |
It sounds like this is referring to some quite specific things, but | |
the ambiguity makes me wonder if I've understood correctly. What I | |
*think* this is saying is "The SC.Checkbox control encapsulates an | |
<input type='checkbox'> with event handlers to make data binding work, | |
and a <label> for a title. when we extend SC.Checkbox, | |
this.get('value') will reflect the 'value' attribute of the input DOM | |
node, and this.get('title') will give the label contents." | |
Again, a pointer to some documentation where I can read more about | |
SC.Checkbox, and what *precisely* is meant by "property" (implied | |
getters/setters? event handling? binding?) would be useful here. | |
=end | |
Under the hood, SproutCore binds an event handler to the change event and updates value when the event occurs. This may change as needed for browser compatibility, but working with a SproutCore property insulates you from those concerns. | |
For every item in its underlying Array, SC.TemplateCollectionView will create a new child view whose content property contains the object the view should represent. In our case, there will be a child view for each todo, and each will have a content property set to a corresponding Todo object. | |
This makes it easy to bind the properties of the checkbox to the properties of the Todo object we’re representing. In this case, we bind value to isDone and title to title, so that when one changes, the other changes automatically. Let’s tie it all together: | |
in apps/todos/todos.js | |
// before SC.ready | |
Todos.MarkDoneView = SC.Checkbox.extend({ | |
titleBinding: '.parentView.content.title', | |
valueBinding: '.parentView.content.isDone' | |
}); | |
=begin | |
I feel like I've got to do some mental gymnastics to understand what's | |
going on here, and it's not clear enough which parts of the | |
description help on the first read-through. 'titleBinding': Ok, so the | |
xxxBinding pattern applies in JS views as well as Handlebars helper | |
calls. '.parentView': what's parentView? We're extending Checkbox, so | |
maybe that's it. '.parentView.content': wait, hang on. Does Checkbox | |
have a content property? Maybe the TemplateCollectionView is our | |
parentView. '.parentView.content.title': wait, hang on. Won't | |
'content' on a TemplateCollectionView be a collection object? 'title' | |
and 'isDone' must be coming from our model, so... the content must be | |
a Todo instance, so '.parentView' must be referring to a single | |
instance of the template we passed to TemplateCollectionView, which | |
I've just been told is a "child view for each todo..." | |
Now, '.parentView...' looks like a chained method call. What's it | |
going to be called on? Who am I storing this for? If it's 'this', then | |
why doesn't 'this' appear as part of the binding path? | |
=end | |
Before you reload the browser to see the new checkbox todo list items, the stylesheet we provided includes a CSS class meant to give completed todos a unique look, so lets also bind the class of each item to the object’s isDone property. | |
We’ll use a property on the collection helper to set up this binding: | |
in apps/todos/resources/templates/todos.handlebars | |
<!-- replacing the previous code --> | |
{{#collection SC.TemplateCollectionView ¬ | |
contentBinding="Todos.todoListController" ¬ | |
itemClassBinding="content.isDone"}} | |
{{view Todos.MarkDoneView}} | |
{{/collection}} | |
This property defines a binding on each of the item views. Each item will get the class is-done if its associated content object’s isDone property is true. SproutCore will automatically dasherize property names into class names. | |
=begin | |
What does "dasherize" actually cover? When (other than writing CSS) | |
does the CSS class name matter? I can vaguely guess at this point, but | |
a doc link would be helpful. | |
=end | |
All views have a number of properties, including id, class, and classBinding. The collection helper allows you to prefix any of those properties with item, which will then apply them to each of the child item views. For instance, if you use the itemClass property on a collection, each item will get that class. | |
=begin | |
It would be good to have this before presenting the code, otherwise | |
I'm left thinking "itemClassBinding? I haven't seen itemClass before, | |
this is more magic." | |
=end | |
Now reload your application in the browser and try it out. As soon as you click a todo’s checkbox, the text will become crossed out. Keep in mind you didn’t need to update any views when marking the Todo as done, bindings did it for you. If a different part of the app changes the Todo item’s isDone property, your list item would automatically update without any more work on your part. | |
8 The More You Know | |
We can now create todos and mark them as being complete. While 76% of all statistics are made up, let’s see if we can display more accurate information from the data we have. At the top of our list, let’s display the number of todos remaining. Open todos.handlebars and insert this new view: | |
in apps/todos/resources/templates/todos.handlebars | |
<!-- after the Todos.CreateTodoView --> | |
{{#view Todos.StatsView id="stats"}} | |
{{displayRemaining}} remaining | |
{{/view}} | |
=begin | |
Why do we need suddenly an 'id="stats"'? We haven't seen an id in a | |
helper call before. | |
=end | |
Handlebars expressions, like {{displayRemaining}}, allows us to automatically update the DOM when a property on our view changes. In this case, we’re saying that the content of the Todos.StatsView should be bound to the value of the view’s displayRemaining property. As with the other bindings, this will be updated for us automatically whenever the value changes. | |
=begin | |
If I understand this right, what we have here is the first time we've | |
seen a binding which is created by something *not* called | |
xxxBinding. Is {{displayRemaining}} a binding in the same sense as our | |
titleBinding and so on from before were? If so, why doesn't it follow | |
the xxxBinding pattern? | |
=end | |
Let’s go ahead and implement that view in todos.js now: | |
in apps/todos/todos.js | |
// before SC.ready | |
Todos.StatsView = SC.TemplateView.extend({ | |
remainingBinding: 'Todos.todoListController.remaining', | |
displayRemaining: function() { | |
var remaining = this.get('remaining'); | |
return remaining + (remaining === 1 ? " item" : " items"); | |
}.property('remaining') | |
}); | |
=begin | |
Carrying on from before, we've now got further confusion: not only is | |
displayRemaining *not* called displayRemainingBinding, but we've got a | |
new field called 'remainingBinding'. That follows the previous | |
pattern, so what's going on? | |
=end | |
displayRemaining contains a pluralized string, based on the number of remaining todos. Here is another core piece of SproutCore in action, this one called a computed property. Computed properties are properties whose value is determined by running a function. For example, if remaining equals 1, displayRemaining will contain the string "1 item". | |
=begin | |
...and *now* it makes sense. If I was told up front "look out, here | |
comes a computed property!" before displayRemaining was mentioned, all | |
that confusion could have been avoided. Something along the lines of | |
"Because we want our strings to be properly pluralised, we can't | |
simply display the property. We need to compute a value based on a | |
property, and that involves a custom method call. That method will be | |
called displayRemaining" would have done the trick. | |
=end | |
We say that displayRemaining depends on remaining because it requires another value to generate its own value. We list these dependent keys inside the property() declaration. | |
=begin | |
The 'property' method lists dependencies, but it isn't called | |
"depends_on()" or "dependencies()" or "upstream_properties()". That | |
implies to me that it does more than simply allow you to list | |
dependencies, but what? We've not explicitly referred to a 'remaining' | |
property anywhere that looks like it should be in scope here, is | |
*that* what it does? Where is this method documented? | |
=end | |
We’ve also bound the view’s remaining property to the todoListController‘s remaining property. This means that, because of the dependency chain we’ve described, if Todos.todoListController.remaining changes, displayRemaining will automatically update. | |
When we have information that views need but is based on aggregate information about our models, the array controller is a good place to put it. Let’s add a new computed property to todoListController in todos.js: | |
in apps/todos/todos.js | |
// updating previous code | |
Todos.todoListController = SC.ArrayController.create({ | |
// ... | |
remaining: function() { | |
return this.filterProperty('isDone', false).get('length'); | |
}.property('@each.isDone') | |
}); | |
=begin | |
Minor point: when I first typed this in (and I was typing it in, I | |
don't believe in learning by copy and paste), I typoed 'Length' for | |
'length' because the typeface used doesn't make the case distinction | |
clear. That made the field appear as "undefined". | |
=end | |
Here, we specify our dependent key using @each. This allows us to depend on properties of the array’s items. In this case, we want to update the remaining property any time isDone changes on a Todo. | |
The @each property also updates when an item is added or removed. | |
=begin | |
Is "@each" ArrayController magic? Are there any other "@" signifiers I | |
can use on ArrayController? On any other controller types? Where are | |
they documented? | |
=end | |
It’s important to declare dependent keys because SproutCore uses this information to know when to update bindings. In our case, our StatsView updates any time todoListController’s remaining property changes. | |
Here’s how it all fits together: | |
=begin | |
The diagram has directional arrows. Aren't bindings supposed to be | |
*bi*directional? Have I missed something? Why haven't I seen a | |
diagram like this before? | |
=end | |
As we build up our application, we create declarative links between objects. These links describe how application state flows from the model layer to the HTML in the presentation layer. | |
9 Clearing Completed Todos | |
As we populate our list with todos, we may want to periodically clear out those we’ve completed. As you have learned, we will want to make that change to the todoListController, and let SproutCore automatically propagate those changes to the DOM. | |
Let’s add a new clearCompletedTodos method to the controller. | |
in apps/todos/todos.js | |
// updating existing code | |
Todos.todoListController = SC.ArrayController.create({ | |
// ... | |
clearCompletedTodos: function() { | |
this.filterProperty('isDone', true).forEach(this.removeObject, this); | |
} | |
}); | |
=begin | |
filterProperty? forEach? removeObject? All new to me. Where have they | |
come from? Docs? | |
=end | |
Next, let’s add a button to our template. Open todos.handlebars and add a button inside the HTML for StatsView as shown here: | |
in apps/todos/resources/templates/todos.handlebars | |
<!-- updating existing code --> | |
{{#view Todos.StatsView id="stats"}} | |
{{#view SC.Button classBinding="isActive" ¬ | |
target="Todos.todoListController" ¬ | |
action="clearCompletedTodos"}} | |
Clear Completed Todos | |
{{/view}} | |
{{displayRemaining}} remaining. | |
{{/view}} | |
We’ve defined an instance of SC.Button, which calls a method on an object when it is clicked. In this case we’ve told the button to call the clearCompletedTodos method (its action) on the Todos.todoListController object (its target). | |
=begin | |
Ok, so this could be clearer - why not just say "The SC.Button calls | |
Todos.todoListController.clearCompletedTodos when it's clicked?" | |
=end | |
We’ve also told it to add an is-active class to the view when it is clicked or tapped. Every SC.Button has an isActive property that will be true when it is in the process of being clicked. This allows us to display a visual cue to the user that they have hit the right target. | |
Go back to your browser and try it out. Add some todos, then mark them done and clear them. Because we previously bound the visual list to the todoListController, making a change through new means has the expected effect. | |
=begin | |
That's a garden path sentence - I had to re-read "making a change | |
through new means has the expected effect" 3 times to be sure I wasn't | |
missing anything. | |
=end | |
10 Marking All as Done | |
Let’s say you’ve decided to actually get all of your work done. Wouldn’t it be nice to have a way to easily mark every todo as complete? | |
It turns out that, due to our application’s declarative nature, all the hard work is already done. We just need a way to mark every Todo complete. | |
Let’s first create a new computed property on our controller that describes whether or not every todo is done. It might look something like this: | |
in apps/todos/todos.js | |
// updating existing code | |
Todos.todoListController = SC.ArrayController.create({ | |
// ... | |
allAreDone: function() { | |
return this.get('length') && this.everyProperty('isDone', true); | |
}.property('@each.isDone') | |
}); | |
SproutCore has many enumerable helpers. everyProperty(‘isDone’, true) returns true if every item in the array has an isDone property that evaluates to true. You can find out more in the Enumerables guide. | |
=begin | |
Hurrah! A documentation link! | |
=end | |
Next, we’ll create a checkbox view to mark all items complete and bind its value to the controller’s property: | |
in apps/todos/resources/templates/todos.handlebars | |
<!-- directly below the Todos.StatsView --> | |
{{view SC.Checkbox class="mark-all-done" ¬ | |
title="Mark All as Done" ¬ | |
valueBinding="Todos.todoListController.allAreDone"}} | |
If you were to reload your browser and use the app now, | |
=begin | |
This is, in fact, precisely what I did. Without reading the rest of | |
the paragraph. Consequently, when the very first thing I did (click | |
the "Mark All as Done" checkbox) didn't work, I assumed I'd screwed | |
something up and re-checked my typing rather than reading on. It | |
actually took me a couple of minutes to get back to the text, because | |
I was so sure I must have missed a mistake I'd made. | |
=end | |
you’d notice that clicking all of the checkboxes of your todos will cause the “Mark All as Done” checkbox to become checked. However, it doesn’t work in the other direction: clicking “Mark All as Done” has no effect. | |
So far, our computed properties have described how to calculate a value from dependent properties. However, in this case, we want to accept a new value, and update dependent properties to reflect it. Lets update our allAreDone computed property to also accept a value. | |
in apps/todos/todos.js | |
// updating existing code | |
Todos.todoListController = SC.ArrayController.create({ | |
// ... | |
allAreDone: function(key, value) { | |
if (value !== undefined) { | |
this.setEach('isDone', value); | |
return value; | |
} else { | |
return this.get('length') && this.everyProperty('isDone', true); | |
} | |
}.property('@each.isDone') | |
}); | |
When you set a computed property, your computed property function is called with the property key as the first parameter and the new value as the second. To determine if SproutCore is asking for the value or trying to set it, we examine the value parameter. If it is defined, we iterate through each Todo and set its isDone property to the new value. | |
Because bindings are bi-directional, SproutCore will set allAreDone to true when the user clicks the “Mark All as Done” checkbox. Conversely, unchecking it will make SproutCore set allAreDone to false, unchecking all todos. | |
Reload the app and add some todos. Click “Mark All as Done”. Wow! Each of the individual check boxes gets checked off. If you uncheck one of them, the “Mark All as Done” checkbox un-checks itself. | |
When you use SproutCore, as you scale your UI, you never need to wonder whether a new feature will work consistently with parts of the UI you already implemented. Since you build your view layer to simply reflect the state of your models, you can make changes however you want and see them update automatically. | |
=begin | |
That's where I stopped. I have no interest in mobile sites right now, | |
so if there are any gotchas here then I didn't run into them. | |
=end | |
11 Mobile Specific Styles | |
At this point, we actually have the app fully functional on all modern mobile devices. Let’s optimize the experience for all of the different screen sizes we might encounter. Download the CSS for mobile and save it as apps/todos/resources/stylesheets/todos_mobile.css. Now load your app on a mobile device and you should see the same app, but styled properly for mobile devices and responding to touch events. | |
The SproutCore development server will be available on your local network at your machine’s IP address. For example, you might visit http://192.168.1.1:4020/todos. | |
12 Resources | |
Now that you have finished the first part of the Getting Started tutorial, you will want to acquaint yourself with the resources available to the SproutCore community. | |
Sign up for the SproutCore Mailing List | |
Follow @sproutcore on Twitter | |
Visit the #sproutcore IRC channel to get real-time help | |
Bookmark the SproutCore API Documentation and the SproutCore Guides | |
13 Continue On | |
Now that you’ve built a basic SproutCore app, continue on to the next part of this tutorial to learn about working with SproutCore’s model layer. | |
14 Changelog | |
March 1, 2011: initial version by Tom Dale and Yehuda Katz | |
March 2, 2011: fixed formmating and added paths to filenames by Topher Fangio and Peter Wagenet | |
March 22, 2011: cleaned up demo based on new features by Yehuda Katz | |
April 11, 2011: consistently use view classes and extend, update to reflect better Handlebars integration by Yehuda Katz and Tom Dale | |
May 6, 2011: clarifications, minor inconsistency fixes, updated CSS for older browsers, plus new mobile section by Tyler Keating | |
May 9, 2011: update for recent changes in SproutCore 1.6 by Tom Dale and Yehuda Katz | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment