public
Description: JavaScript Application Framework - JS library only
Home | Edit | New

Creating Custom Views

This walkthrough we will demonstrate how easy it is to create your own custom views. I assume that you have already walked through the Hello World Tutorial (http://www.sproutcore.com/documentation/hello-world-tutorial).

For our example, we want to create a listing of products and have it output in HTML table tags. We’ve already looked through the available view helpers but didn’t find a control that would create a grid this way so we decided to create our own custom view.

Background

Sproutcore views are dynamic and, as such, can be added or removed at any time. A view is simply a javascript wrapper around your HTML elements. This allows you to add more functionality to standard tags and ultimately give the end-user a better experience. At least, that’s what we hope to accomplish.

Model

One of the first things we will need to do is create a model for our products. Let’s construct a simple SproutCore application to show how this could work in practice. First, generate the app:

$ sc-init app

Now create a new model class:

$ cd app $ sc-gen model app/products

Running this command will create a fixtures directory which will contain a file we can put our test data in. Since we will not be connecting to an external data source lets open up the fixtures/products.js file and fill it up with some test data.

Replace the pre-populated fixture data with our own products data and the end result should look like this:

<pre>// ========================================================================== // App.Products Fixtures // ==========================================================================

require(‘core’) ;

App.FIXTURES = App.FIXTURES.concat([

// TODO: Add your data fixtures here. // All fixture records must have a unique guid and a type matching the // name of your model. See the example below.

{
guid: 1,
type: ‘Products’,
name: ‘Sample Product’,
manufacturer: ‘Manufacturer1’,
price: ‘$9.99’,
},

{
guid: 2,
type: ‘Products’,
name: ‘Sample Product’,
manufacturer: ‘Manufacturer One’,
price: ‘$9.99’,
},

{
guid: 3,
type: ‘Products’,
name: ‘Sample Product’,
manufacturer: ‘Manufacturer One’,
price: ‘$9.99’,
},

{
guid: 4,
type: ‘Products’,
name: ‘Sample Product’,
manufacturer: ‘Manufacturer Two’,
price: ‘$19.99’,
},

{
guid: 5,
type: ‘Products’,
name: ‘Sample Product’,
manufacturer: ‘Manufacturer Two’,
price: ‘$19.99’,
},

]);

We will also need to modify our model and set the properties that are going to be available:

<pre> // ========================================================================== // App.Products // ==========================================================================

require(‘core’);

/** @class

(Document your class here) @extends SC.Record @author AuthorName @version 0.1

/
App.Products = SC.Record.extend(
/
* @scope App.Products.prototype */ {

dataSource: App.server, properties: [‘name’, ‘manufacturer’, ‘price’]

}) ;

Controller

Now that we have data we can use lets create a collections controller to bind our data to the views.

Generate the controller:

$ sc-gen controller app/products

We need to make sure our products controller is a collections controller so let’s open up controller/products.js and make it look like this:

<pre>// ========================================================================== // App.ProductsController // ==========================================================================

require(‘core’);

/** @class

(Document Your View Here) @extends SC.Object @author AuthorName @version 0.1 @static

/
App.productsController = SC.CollectionController.create(
/
* @scope App.productsController */ {

// TODO: Add your own code here.

}) ;

Basically all we did was change SC.Object.create to SC.CollectionController.create. With this change we inherit a lot from the collections controller class including arrangedObjects which will return our products as a set of objects that we can later iterate through.

View

With our Model and Controller in place we can now turn our attention to the view:

$ sc-gen view app/my_table_view

Let’s open up my_table.js in our editor and begin adding our code. Once open, you should notice that our custom view is just an extension of the base SproutCore view class. Extending is a lot like creating a new class. In our case, it will be creating a new object type called myTableView that has all the methods and properties of the base class View, plus any additional properties and methods that we will add below.

One of the first things we are going to do in our new view is add the following line:

<pre>emptyElement: '<table><tr><td></td></tr></table>',</pre>

emptyElement is used to specify the HTML we want to use when creating this element.

Next we will create some of the properties that will be used by our view. The most important of them being the content property.

<pre>// Properties content: [], contentBindingDefault: SC.Binding.MultipleNotEmpty, </pre>

This property will allow us to bind data to our new view.

Once we’ve got our properties coded up we can begin to add methods to our view sub-class.

<pre>render: function() { var html = []; var content = this.get('content');

// Iterate through the collection and add rows html.push(this._renderRowHtml(content));

// Finally set the innerHTML of the view
html = html.join(‘’);
this.set(’innerHTML’, html);
}.observes(‘content’),

The method render will obtain a copy of the data being bound to our view and pass it along to a private method that will iterate through the collection and create the rows and columns in our table. Once we have gone through the data we convert the array of html into one big string and passing it into the innerHTML property of the base class.

Notice that at the end of the method we add observes('content'). Since render is the main method in our view we want it to be called whenever the data is refreshed or changed in any way.

To iterate through our content collection we create a private method:

<pre>_renderRowHtml: function(content) { var html = [];

content.each( function(record){ html.push(‘’);

var noColumns = record.get(‘properties’).length;
for (i=0; i < noColumns; i++){
html.push(‘’);
html.push(record.get(record.get(‘properties’)[i]));
html.push(‘’);
};
html.push(‘’);
});
return html.join(’’);
}

In _renderRowHTML we take each record in our collection and wrap it in <tr> tags. For each field in our record we generate a <td> tag. Once we’ve looped through the entire array we return the new html string to our main function render.

There we have it. We’ve created a view that will generate our own html and use binding to bring data in and output to the client.

Wire It Up

With our view created we can now call it from our english.lproj/body.rhtml page. Remove all the pre-generated code from body.rhtml and insert a line to call our new custom view:

<pre><% content_for('body') do %> <%= view :myTableView, :tag => :table, :view => 'App.MyTableView', :bind => { :content => 'App.productsController.arrangedObjects' } %> <% end %> </pre>

Finally, let’s get our data and wire it up to the content of our controller so that we can use it.. In main.js, add the following code under the third section (Step 3):

<pre>var products = App.Products.collection(); App.productsController.set('content', products); products.refresh(); </pre>

That’s it. You now have created your very own custom view.

Run $ sc-server from your app directory to start the sproutCore server. Then point your browser at http://localhost:4020/app and you should see the products defined in the fixtures file displayed on the page in table format.

Comments

I’ve tried the tutorial, but the table has no content (not even a single or in my source, the table tag itself is there). I think you miss something, can’t tell you what though otherwise i would’nt be trying this tutorial ;) – Marc


I’ve managed to see something after I changed a little the code:

First I changed all app instances with App. Ex: app.MyTableView to App.MyTableView.

Also I needed to change App.ProductsController.set('content', products); to App.productsController.set('content', products);, in file main.js.

This line var products = app.Products.collection();was not working for me: I had to change it to look like this: var products = App.Products.findAll().clone();.

And the final change I needed to do was in body.rhtml, from :content => 'app.productsController.arrangedObjects' to :content => 'App.productsController.content' . – Dumi


After changing the names, (I had already seen some myself but not all, I fixed them in the tutorial now too), it works for me.
var products = App.Products.collection(); and :content => 'App.productsController.arrangedObjects' work fine for me, are you sure your ProductsController is a CollectionController instead of an Object? – Marc


You forgot one: :content => 'app.productsController.arrangedObjects' :). It’s fixed now.

It was a CollectionController… I guess my browser cached some files because I started over (sc-init app) in a new location on disk and everything went fine. – Dumi


Thanks for the post! :) I finally got it working… However, I tried this with four properties in firefox 3.1 on Mac OS X Leopard. I used start_date and end_date as two of the properties for my model. And I did (for example) start_date: 'monday', in my FIXTURES. These properties, however, did not show up in their respective tabel cells (between the td tags). Instead, there was nothing between the td tags. When I took out the underscores “_” from start_date and end_date, making them startDate and endDate, it worked! they showed up! Weird… really weird… this is definitely a bug… either with sproutcore or the code in this tutorial. please fix. thank you :)
-Matt Di Pasquale AKA MattCollegeWikis


This is awesome. Now, how do you use another view as an outlet in your custom view. For example, how would we make each a labelView that is binded to each property so that we can edit the properties on double click? I tried doing a simple test version of this here but I couldn’t get it working… I couldn’t even get render() to execute I don’t think… -MattCollegeWikis AKA Matt Di Pasquale

Last edited by mattdipasquale, Thu Aug 28 20:08:58 -0700 2008
Home | Edit | New
Versions: