In many cases when you are building an application for the desktop or the web, the need arises to display a list of content to a user in which the user can scroll through, sort, search, select and modify content among other things depending on the needs of a given feature. A good framework that is designed to help build desktop or web-based applications will usually come stock with a list component that is both easy to use and easy to extended for each application’s unique needs. SproutCore is one such framework that is ready to provide you with such a component — the SC.ListView.
If you go to the official SproutCore site and follow the todo tutorial you’ll get a good initial sense of how simple the list view is to use. Right out of the box you can give the list view an array of objects and set it up pretty quickly for users to browse through content and interactive with. And most of what you need to program can be done through the list view’s exposed properties. However, as you work with the list view you may come to a point where the the way the content is listed and how you interact with it in the view can only go so far. You may feel like you want to come up with a unique way to list content that makes more sense for your particular needs. Does the list view allow you to create unique items in the list? Is it difficult to do? For the first question: Yes. For the second question: No.
In order to display each item in the list view, the list view makes use of a default list item called SC.ListItemView (located in the list_item.js file in the frameworks/desktop/view directory). If you look through the JS file you’ll notice a lot of code. This is because the list item view was designed to be very flexible so that you can do a lot with it out of the box. At first this might seem intimidating because it makes you think you will also have to write as much code yourself. Don’t be concerned. A good chunk of the code is devoted to rendering the list item’s many features and getting it set up so that you can edit content inline. What I’m here to do it get you introduced with the basics, and it really doesn’t take much. So with that being said, let’s dig in!
- Part 1.1: Getting Ourselves Setup
- Part 1.2: Creating Our Custom List Item View
- Part 1.3: Becoming Selected
- Part 1.4: Enabling and Disabling
- Part 1.5: Until We Meet Again
Part 1.1: Getting Ourselves Setup
Before we start writing a custom list item view, we need to get some stuff set up. First I want you to start with a fresh application by running sc-init and calling your application MyApp. Now that we have a foundation, I want you to go ahead and create a controller by doing the following:
sc-gen controller MyApp.testController SC.ArrayController
Open the newly created test.js file in the controllers directory and replace the content of the file with the following:
MyApp.testController = SC.ArrayController.create(
{
selection: null,
listEnabled: YES,
listView: null,
/**
Note: This is a hack. Currently the SC.CollectionView does not correctly respond to
changes made to its isEnabled property. Therefore we have to force the collection
view to reload to make sure the view and its list item views correctly receive the
changes.
In addition, the SC.CollectionView does not handle changes to its isSelectable
property either. Sigh.
Such is life working with a beta release. [Sept 6, 2009]
*/
listEnabledChanged: function() {
var list = this.get('listView');
list.set('isEnabled', this.get('listEnabled'));
list.reload();
}.observes('listEnabled')
}) ;
Now, open the main_page.js file in the english.lprojresources directory and replace all the code with the following:
MyApp.mainPage = SC.Page.design({
mainPane: SC.MainPane.design({
childViews: 'list toolbar'.w(),
list: SC.ScrollView.design({
layout: { top: 10, bottom: 30, left: 10, right: 10 },
contentView: SC.ListView.design({
layout: { top: 0, bottom: 0, left: 0, right: 0 },
contentBinding: 'MyApp.testController'
})
}),
toolbar: SC.View.design({
layout: { bottom: 0, left: 0, right: 0, height: 30 },
classNames: ['toolbar'],
childViews: 'enableList'.w(),
enableList: SC.RadioView.design({
layout: { height: 20, width: 200, right: 10, centerY: 0 },
items: [{ title: "Enable",
value: YES },
{ title: "Disable",
value: NO }],
valueBinding: 'MyApp.testController.listEnabled',
itemTitleKey: 'title',
itemValueKey: 'value',
layoutDirection: SC.LAYOUT_HORIZONTAL
})
})
})
});
Next, create a new CSS file in the english.lprojresources directory called style.css and copy the following code into it:
div.custom-list-item-view div.top {
position: absolute;
top: 0px;
left: 0px;
right: 0px;
height: 35px;
border-width: 0px;
}
div.custom-list-item-view div.top.standard {
background-color: #c0c0c0;
}
div.custom-list-item-view p.name {
float: left;
padding: 0px;
margin: 5px 0px 0px 5px;
font-family: Georgia;
font-size: 2em;
font-weight: bold;
line-height: normal;
}
div.custom-list-item-view div.bottom {
position: absolute;
top: 35px;
left: 0px;
right: 0px;
height: 15px;
}
div.custom-list-item-view div.bottom.standard {
background-color: #e3e3e3;
border-bottom-color: black;
border-bottom-width: 3px;
border-bottom-style: solid;
}
div.custom-list-item-view p.item {
float: left;
padding: 0px;
font-family: Helvetica;
font-size: 1em;
line-height: normal;
}
div.custom-list-item-view p.company {
margin: 0px 0px 0px 5px;
}
div.custom-list-item-view p.title {
margin: 0px 0px 0px 10px;
}
div.custom-list-item-view span.label {
font-weight: bold;
}
div.custom-list-item-view span.value {
margin-left: 5px;
font-style: italic;
}
.toolbar .sc-radio-button {
margin: 0px 10px 0px 0px;
}
Finally, let’s updated the main.js file located in the app’s root directory. Replace the file with the following code:
MyApp.main = function main() {
MyApp.getPath('mainPage.mainPane').append() ;
var content = [
SC.Object.create({
fname: 'John',
lname: 'Doe',
company: 'Google',
title: 'Senior Manager'
}),
SC.Object.create({
fname: 'Bob',
lname: 'Smith',
company: 'Microsoft',
title: 'Sales'
}),
SC.Object.create({
fname: 'Fred',
lname: 'MacDoogle',
company: 'Apple',
title: 'Developer'
})
];
var controller = MyApp.testController;
controller.set('content', content);
var listView = MyApp.mainPage.mainPane.childViews[0].contentView;
controller.set('listView', listView);
} ;
function main() { MyApp.main(); }
Awesomeness. If you run the app you won’t see much even though we have wired the list view to the controller. That’ll change soon. Just remember that we are getting things set up quickly for the purposes of this tutorial, not because we are trying to make a real, professional application :).
Here’s what you should see so far:

Part 1.2: Creating Our Custom List Item View
To start making a simple custom list view item we have to create a new view, which we’ll do by running the following command:
sc-gen view MyApp.CustomListItemView
To get us started we need two things: 1) a content property; and 2) a way to render the content. (The objects making up the content are located in the main.js file). Hmm. If you’ve read my prior posting on this blog, those two things seem familiar… Familiar like… like… like creating a custom view! This means we can leverage what we’ve learned before. (If you’re new to this blog or haven’t had a chance to read up on how to create a simple custom view, start by reading posts here and here).
For out custom list item view, we want it to look like the following:

As you can see above, our view will display a person’s first name, last name, the company the person works for, and the title the person holds at the company. Let’s update the custom list item view’s code to be the following:
MyApp.CustomListItemView = SC.View.extend(SC.ContentDisplay, {
classNames: ['custom-list-item-view'],
contentDisplayProperties: 'fname lname company title'.w(),
render: function(context, firstTime) {
var content = this.get('content');
var fname = content.get('fname');
var lname = content.get('lname');
var company = content.get('company');
var title = content.get('title');
context = context.begin().addClass('top');
context = context.begin('p').addClass('name').push('%@, %@'.fmt(lname, fname)).end();
context = context.end(); // div.top
context = context.begin().addClass('bottom');
context = context.begin('p').addClass('item').addClass('company');
context = context.begin('span').addClass('label').push('Company:').end();
context = context.begin('span').addClass('value').push(company).end();
context = context.end(); // p.item.company
context = context.begin('p').addClass('item').addClass('title');
context = context.begin('span').addClass('label').push('Title:').end();
context = context.begin('span').addClass('value').push(title).end();
context = context.end() // p.item.title
context = context.end() // div.bottom
sc_super();
}
});
This is enough to get us to the point where we can set up our list view to show our custom list view item. But how do we get our list view to actually display the custom list view item? Hmm. Well, it turns out that SC.ListView extends another view called SC.CollectionView. SC.CollectionView is the root view to display a collection of views. In SC.CollectionView it has a property called exampleView (collection.js, line 286) that when given a view, it will use it to display all the content objects in a given array. And as you’ll notice, the default setting for exampleView is SC.ListItemView — Ta-da!
Looking at the comments for the exampleView property, you’ll see that the three most important properties the example view should have are the following:
- content – The content object from the content array your view should display
- isSelected – True if the view should appear selected
- isEnabled – True if the view should appear enabled
So it would appear that the list view has a bit of a contract with the view used to display its array of content. We’ll start with the content property and work our way to using the isSelected and isEnabled property. As a side note, the list view, or actually the collection view, also supplies the example view with some additional attributes, but I’ll get to that when I post part two of this tutorial… I just had to toss that in there ;).
In the main_page.js file, update the list view so that we set the exampleView property like so:
list: SC.ScrollView.design({
layout: { top: 10, bottom: 30, left: 10, right: 10 },
contentView: SC.ListView.design({
layout: { top: 0, bottom: 0, left: 0, right: 0 },
contentBinding: 'MyApp.testController',
exampleView: MyApp.CustomListItemView,
rowHeight: 54,
rowSpacing: 0
})
})
That’s all ya need to do. (In addition to setting the exampleView property, we also set the list view’s rowHeight and rowSpacing properties). Now go ahead and refresh your browser to see the result. You should get something that looks like the following:

Pretty sweet! That really didn’t take much effort. Now, bare in mind that we are just rendering the content and not doing much else. So our custom list item view is okay but we can do better. Let’s update our custom list item view so that when the user selects it in the list view, the list item view will update to visually indicate it is currently selected. How do we actually go about doing this? Remember that contract that the list view has with its example view? Well we’re going to take advantage of the isSelected property that the list view adds to our custom list item view.
Part 1.3: Becoming Selected
When a user selects an item in the list view, the list view will set the selected list item view’s isSelected property. We can take advantage of this by updating our view to be the following:
MyApp.CustomListItemView = SC.View.extend(SC.ContentDisplay, {
classNames: ['custom-list-item-view'],
displayProperties: 'isSelected'.w(),
contentDisplayProperties: 'fname lname company title'.w(),
render: function(context, firstTime) {
var content = this.get('content');
var fname = content.get('fname');
var lname = content.get('lname');
var company = content.get('company');
var title = content.get('title');
var isSelected = this.get('isSelected');
var standard = !isSelected;
var selected = isSelected;
var classes = { 'standard': standard, 'selected': selected };
context = context.begin().addClass('top').setClass(classes);
context = context.begin('p').addClass('name').push('%@, %@'.fmt(lname, fname)).end();
context = context.end(); // div.top
context = context.begin().addClass('bottom').setClass(classes);
context = context.begin('p').addClass('item').addClass('company');
context = context.begin('span').addClass('label').push('Company:').end();
context = context.begin('span').addClass('value').push(company).end();
context = context.end(); // p.label.company
context = context.begin('p').addClass('item').addClass('title');
context = context.begin('span').addClass('label').push('Title:').end();
context = context.begin('span').addClass('value').push(title).end();
context = context.end() // p.label.title
context = context.end() // div.bottom
sc_super();
}
});
What did we just do? Basically we updated the code so that the view will re-render when the isSelected property is updated by the list view. As well, we updated the render method so that parts of the view will either have “standard” or “selected” applied to the class attribute. If isSelected is true then “selected” will be added to an element’s class attribute. If isSelected is false then only “standard” will be added to an element’s class attribute. (Notice how we took advantage of the render context object’s setClass method). Easy, right? Go ahead and refresh your browser and select the items in your list view. What did you get? Nothing changed? Uh-oh. Something’s not right. We’re missing something. Remember the CSS file I had you create earlier on? Well it needs to be updated to take advantage of the “selected” class that gets added to the HTML elements. Add the following to your CSS file:
div.custom-list-item-view div.top.selected {
background-color: #2222FF;
}
div.custom-list-item-view div.bottom.selected {
background-color: #9999FF;
border-bottom-color: black;
border-bottom-width: 3px;
border-bottom-style: solid;
}
Great. With the CSS file updated to make use of the “selected” class, let’s go back to our browser and refresh it. Select the list item views. You should get something like the following:

Success! Now we got our custom list view to be interactive and it didn’t take much work to do it. Woot! Okay, so that’s the isSelected property down. We’re almost done.
Part 1.4: Enabling and Disabling
How about we wrap up and make use of the isEnabled property. Just as a heads up, as of this writing, Sept 6, 2009, the SC.CollectionView doesn’t properly update when the isEnabled property is changed. So I had to do a bit of a hack that you can see if you look at the code in the test controller. I was working on fixing the problem, but I decided to just go ahead and shoot out this post. SproutCore is still in beta after all. With that being said, let’s go ahead and update your custom list item view’s code one more time:
MyApp.CustomListItemView = SC.View.extend(SC.ContentDisplay, {
classNames: ['custom-list-item-view'],
displayProperties: 'isSelected isEnabled'.w(),
contentDisplayProperties: 'fname lname company title'.w(),
render: function(context, firstTime) {
var content = this.get('content');
var fname = content.get('fname');
var lname = content.get('lname');
var company = content.get('company');
var title = content.get('title');
var isSelected = this.get('isSelected');
var isEnabled = this.get('isEnabled');
var standard = isEnabled && !isSelected;
var selected = isEnabled && isSelected;
var disabled = !isEnabled;
var classes = { 'standard': standard, 'selected': selected, 'disabled': disabled };
context = context.begin().addClass('top').setClass(classes);
context = context.begin('p').addClass('name').push('%@, %@'.fmt(lname, fname)).end();
context = context.end(); // div.top
context = context.begin().addClass('bottom').setClass(classes);
context = context.begin('p').addClass('item').addClass('company');
context = context.begin('span').addClass('label').push('Company:').end();
context = context.begin('span').addClass('value').push(company).end();
context = context.end(); // p.item.company
context = context.begin('p').addClass('item').addClass('title');
context = context.begin('span').addClass('label').push('Title:').end();
context = context.begin('span').addClass('value').push(title).end();
context = context.end() // p.item.title
context = context.end() // div.bottom
sc_super();
}
});
Like the isSelected property, we are just doing the same thing with the isEnabled property where we set the “disabled” class to parts of our view. Nothin’ to it. We’re going to have to update our CSS file one more time by adding the following to the file:
div.custom-list-item-view div.top.disabled {
background-color: #f0f0f0;
color: #d0d0d0;
}
div.custom-list-item-view div.bottom.disabled {
background-color: #ffffff;
color: #d0d0d0;
border-bottom-color: #c0c0c0;
border-bottom-width: 3px;
border-bottom-style: solid;
}
Okay. Now you can go ahead and refresh your browser. Click on the disable radio button that is down in the bottom-right of the window. If everything went as planned your view should look like the following:

You did it! The custom list items now look disabled. If you click on the enabled radio button the list items will return to their standard look. Again, there really wasn’t much effort to get this working.
As another one of those beta issues, you can still click on the items in the list view and the list view’s selection property will still be updated. D’oh. As the comments say in the collection.js file for the isEnabled property, nothing should be selectable. There is the same issue with the collection view’s isSelectable property. Again, beta. I’m not sure if anyone else in the SproutCore community has been working on fixing this, but I am in the middle of trying to correct the problem.
Part 1.5: Until We Meet Again
There ya go. You’ve successfully created your own custom list item view making use of the three properties: content, isSelected and isEnabled. Now I should stress that this custom list item view is not nearly as feature complete as the default SC.ListItemView, but that’s okay. If you rather make use of the SC.ListItemView to take advantage of all it has to offer you can by simply extend it and overriding its various methods.
In second part of this tutorial, I will be going into some more advanced things you can do when creating a custom list item view. But until then, have fun programming in SproutCore!
Hey, it would be helpful to do a build and show us a demo (IMO). Great post though!
Very useful. Thanks. Why did you subclass SC.View instead of SC.ListItemView for your custom view?
@Majd: Ya. I need to get around to actually hosting fully built tutorials that anyone can start playing with. The one of many things on my todo list :).
@Cleland: I decided to extend SC.View simply because I wanted to show how to make your own custom list item view from scratch. I felt this would give people a deeper understanding of just how the SC.ListView really works with its list item views. However, as I mention at the end of this post, you can most certainly extend the SC.ListItemView if it already has most of what you want but it just has to be augmented a bit to handle your particular needs.
G’day,
I was starting to go through this tutorial and came into some problems. I got to the end of part 1.2 and it appears that I don’t get the screenshot you have provide with the CSS layout. I achieved the previous screenshot with the SC.objects listed.
I’m using sproutcore (1.0.1003) on Windows with FF 3.5.3
The CSS file is in the resources directory. Would this happen to be a known issue? Just trying to get my head around sproutcore and I am truly appreciating this information.
Kind regards,
Glen
@Glen: Is it that you don’t get the same styling for the custom list item view or is that the custom list item views do not appear in the list? If it’s a styling issue, be sure to place the CSS code in the style.css file and place the file in the english.lproj directory. Also be sure to set the view’s classNames property to be ['custom-list-item-view']. I re-ran my tutorial on both the latest Apple Safari and Firefox and the app displays and behaves correctly in both. Let me know if everything works out. I want to make sure others can get through my SproutCore tutorials without any headaches :).
-FC
Just a reminder that if you’re using the latest SproutCore framework v1.0 RC1, the sc-init tool will no longer create a english.lproj directory. Instead, sc-init now creates a ‘resources’ directory, which is where you now put all your CSS and string files.
its not showing properly in my ff 3.5.7 Win Vista.. the lower part of the screen (the radio buttons, seems to be cut off)
some css issue??
by the way it works fine in chrome in the same system.
Ah the joys of CSS.
I admit that when I create the apps for my tutorials I just run them in Apple’s Safari (read: WebKit). I haven’t actually tried them in FF, though I suppose I should.
WebKit and FF do have some difference in how they handle CSS. I’ve come across incompatible CSS browser issues before when building a SproutCore-based web app, so this is one of those things to keep in mind when you create a web app. I’m sure any web designer will give you their CSS battle stories when trying to get everything to look just right in different browsers.
1. Using Safari 4.0.4 on the mac, this tutorial does not render properly. It’s a CSS issue, I’ve tinkered with it, and it appears to be that the use of float:left causes the container object to be zero pixels high in some cases, thus resulting in no background color displayed. I replaced float:left with display:inline-block and most of the problems went away. Can’t get the bottom border of div.bottom to display now, though… :(
2. What is up with using hard-coded heights and absolutely positioned elements? This is HTML at its core, why wouldn’t we let the browser deal with that kind of stuff? I mean, in my mind, it violates the principle of encapsulation to have to specify rowHeight in main_page.js, which is tied to the pixel-height of the elements created as part of custom_list_item.js. Isn’t there a way to tell it to just use the height of the rendered object?
NOTE: I tried commenting out rowHeight and it used a default value. I tried setting it to 0, and everything disappeared. If there is an ‘auto’ method, it’s non-obvious. But I haven’t yet take the plunge into the docs, because it’s not critical to me (yet).
@John
Hmm. Unfortunately I’m not sure why the tutorial app is not rendering properly for you. I’m also using Safari 4.0.4 on my Mac and tested the tutorial with it. Perhaps I left something out that is causing the issue.
Regarding your second point on hard-coded heights and absolute positioned elements, its done for a few reasons. While, yes, HTML is at the core, you have to look at SproutCore applications differently then traditional web pages.
SproutCore takes control of event handling and propagation for performance reasons as well as to do things like drag and drop and scrolling. By knowing the exact position and dimensions of the views, SC can skip using a browser’s DOM object, which is slow, and quickly access a specific view based on an event.
In the case of list views, by knowing the exact dimensions of the given example view, the list view can optimize what list item views to create and display to the user. This is why you can supply a list view with, say, 10,000 objects, but it will only make a few list item views at a time for those object deemed visible. The list view requires a row height since it controls exactly how its list item views are laid out. If no row height is provided then it will go to a default row height of 18 pixels.
The scroll view (SC.ScrollView) uses exact positioning to great effect to help support its contained viewed. If you were to use a standard DIV with CSS overflow as ‘scroll’ to contain a list view, then it would not be possible, or very difficult, to calculate how far up, down, left, right someone has scrolled in order to only render what is absolutely essential.
There are other examples for SproutCore to have the need for exact dimensions and positioning, but those examples provide the nutshell.
The dimensions and positioning themselves in a page view don’t break encapsulation since that is where you need to start laying out the views. This is no different then if you were to design a standard desktop application. The only difference is that when you typically build a desktop app in, say, Windows or the Mac, you have developer tools that make it easy to just draw the UI components on a screen and let the tools take care of auto-generating the layout code for you. Of course, good dev tools will also provide you the ability to manually code the layout yourself if you so wish.
There is an open source project in the works that will help aid in designing your app’s interfaces by simply drawing views on to the screen. It’s called Greenhouse:
http://wiki.sproutcore.com/Greenhouse-Introduction
Again, its best to look at SproutCore as a framework to build desktop-like applications that load in a web browser and not like a traditional web site.
I admit that when I first started using SC and saw the explicit use of heights, widths and position I was a bit mystified as to why. Eventually after using SC to build web apps it then made sense.
Thanks for the explanation. Having seen how slow the DOM can be for event propagation, now I understand why they did that. And in another post, you mentioned something about the possibility for a single list item to have a custom height. So that’s cool…
But my initial reaction when I saw all that was: What happens when the user adjusts their font size on the fly? How can it adapt? So I tried it. HOLY CRAP! It adapted! How did it do that? Sounds like another blog post topic in the making…
@John
Glad I could help ya out. And yes, you can explicitly control the height of individual list item views by making use of the SC.CollectionRowDelegate mixin. Yet another future blog post ;-).
Hi
Hope you dont mind but I’ve added your code snippets to sproutcore playground at http://sproutcoreplayground.com/examples/custom-list-view/ let me know if you want them removed