Although SproutCore comes with a great set of general views for you to use right out of the box, you are probably going to still come to a point where you realize you need to create your own custom views to suit your needs. Creating a custom view in SproutCore can seem like an intimidating task for a newbie — it was for me. So in the aim of helping out all those who want to start dipping their toes into creating custom views, here’s one place to get started.

We are going to create a simple custom view that will be used to summarize a user. We’ll call the view User Summary and it will display the user’s name, a description of the user and the user’s age (just because we can). This seems okay to start off with. I often find that if you are going to create any custom view, it is good practice to start by working with plain old HTML to build the visible structure of what we want. Here is a preview of what we want the view to look like:

usersummaryview1

Okay, so to get started let’s first create a new SproutCore application called MyApp by running the following command:

sc-init MyApp

Now go into the new directory and run the following command:

sc-gen view MyApp.UserSummaryView

If everything went well you should now have some new files in your project. sc-gen helped us create a template for us to build our view on. You will see a file called user_summary.js in the my_app/apps/my_app/views folder. Open up user_summary.js file. You should see something like the following:


MyApp.UserSummaryView = SC.View.extend(
/** @scope MyApp.UserSummaryView.prototype */ {

  // TODO: Add your own code here.

});

The view looks pretty empty. Not to worry. We’ll be adding code to this baby soon enough! But before we jump into the view, we first need to do a few quick things.

First, we need to create a CSS file that will be used to make our view look nice. create a new CSS file in the english.lproj directory and call it style.css. Copy and paste the following into your CSS file:


.user-summary-view {
  position: relative;
  height: 50px;
  background-color: #c0c0c0;
}

.user-summary-view-name {
  position: absolute;
  top: 0px;
  left: 0px;
  font-family: georgia;
  font-size: 22px;
}

.user-summary-view-desc {
  position: absolute;
  left: 0px;
  bottom: 0px;
  font-family: arial;
  font-size: 12px;
  font-style: italic;
}

.user-summary-view-age {
  position: absolute;
  top: 0px;
  bottom: 0px;
  right: 0px;
  width: 50px;
  background-color: #008000;
}

.user-summary-view-age-value {
  padding: 0px;
  margin: 0px 0px 5px 0px;
  font-family: georgia;
  font-size: 22px;
  color: white;
  text-align: center;
}

.user-summary-view-age-capt {
  padding: 0px;
  margin: 0px;
  font-family: arial;
  font-size: 12px;
  text-align: center;
  color: white;
}

Great. Now we are going to update the main_page.js file located in the english.lproj directory. Replace the code in the file to be the following:


MyApp.mainPage = SC.Page.design({

  mainPane: SC.MainPane.design({
    childViews: 'userSummaryView'.w(),
    
    userSummaryView: MyApp.UserSummaryView.design({
      layout: { top: 0, left: 0, height: 50 },
    })
  })

});

If you try running the application using sc-server you won’t see much, but you will soon.

Now that we got the house cleaning done, let’s get to the good stuff! First and foremost: We always want to keep our views independent of everything else because: 1) it’s just good programming practice; 2) it makes it easier to test our view; and 3) maximizes the view’s reuse. Moving on.

For every view you are going to be dealing with two fundamental concepts. The first is to decide what properties the view will react to to update itself. The second is how to actually render the view using those properties. Let’s focus on the properties first.

We know that our view will display a user’s name, description and age, so those look like good properties for our view to have and others to bind to. Update your user_summary.js file to be the following:


MyApp.UserSummaryView = SC.View.extend({

  name: '',
  description: '',	
  age: 0,

  displayProperties: ['name', 'description', 'age']

});

In the code you see that we have added three basic properties. Pretty easy. Also, we added another interesting property to our view called displayProperties. This is used to inform the view to watch for changes to some or all of its properties in order to re-render the view. SproutCore hides a lot of the complexity so that we can focus on building our view and not get tied up in the plumbing.

Now for the second part — getting the view to render. We are going to add a render() function to our view, as so:


MyApp.UserSummaryView = SC.View.extend({

  name: '',
  description: '',	
  age: 0,

  displayProperties: ['name', 'description', 'age'],

  render: function(context, firstTime) {
    sc_super();
  }

});

By adding this function to our view, we have just overridden the parent view’s render function. This is the function where the real meat will be added to give us our end result. To start, we will get the values of the properties we will use to help render our view. Update the render method to be the following:


render: function(context, firstTime) {
  var name = this.get('name');
  var description = this.get('description');
  var age = this.get('age');

  sc_super();
}

Remember that in SproutCore we always want to get the value of a object’s property through the get() method instead of directly, like as in myObject.foo.

We’re almost there. Now we are going to make use of the argument context to create our HTML that SproutCore will eventually spit out to the browser. context is a SC.RenderContext object that is used to build the HTML. Think of the render context as a fancy string builder that queues up changes to an element. With the context in hand, let’s once more update the render() method to be the following:


render: function(context, firstTime) {
  var name = this.get('name');
  var description = this.get('description');
  var age = this.get('age');
	
  context = context.begin('div').addClass('user-summary-view');
  context = context.begin('div').addClass('user-summary-view-name').push(name).end();
  context = context.begin('div').addClass('user-summary-view-desc').push(description).end();
  context = context.begin('div').addClass('user-summary-view-age');
  context = context.begin('div').addClass('user-summary-view-age-value').push(age).end();
  context = context.begin('div').addClass('user-summary-view-age-capt').push('age').end();
  context = context.end();
  context = context.end();

  sc_super();
}

Because I know you’re eager to see something on the screen, let’s go ahead and update the browser (remember to have the sc-server running). You should see the following:

usersummaryview2

Yay! We’re finally getting something. It looks close to what we want. In fact, the view is ready to be used. But before we go any further, let’s go back and study what we added to the render() method.

With the context object, we make calls to its methods, such as calling begin(), end(), addClass(), push(). All these methods make it convenient to construct our view. begin() and end() create the start and end of a tag, such as div. addClass() adds a value to the tag’s class attribute. And push() is used to push any old string to build up your HTML. To see the reset of the context’s methods, go to the render_context.js file located in the SproutCore framework’s frameworks/foundation/system.

Now that we got the render context object out of the way, let’s get back to making our custom view do cool stuff. We are going to modify our view’s properties programmatically but we’ll do it through the use of a interactive JavaScript console. If you are using the Firefox browser then go ahead and use the JavaScript console that comes with the Firebug add-on. If you are using Apple Safari or Google Chrome (read: Webkit) then you can just use the built-in JavaScript debugger. I’ll be using the latest Safari.

To access our custom view in our application we are going to have to traverse the application’s object graph. Remember that when we initially created the application, SproutCore set up a root object called MyApp. So in our JavaScript console, we’ll acquire our view as follows:

var view = MyApp.mainPage.mainPane.userSummaryView

You should now have a reference to the view object. With view in hand let’s change its properties and see what happens. First, we’ll modify the view’s name property by entering the following into the console:

view.set('name', 'Skippy McGaven')

Now, if like me, you happen to hit enter and didn’t see anything happen, don’t fret. Just move your mouse over the browser’s window and the name will appear. I’m not sure why this is, but it appears you have to trigger an event to invoke SproutCore to update the view. No matter. You should now see our view with “Skippy McGaven” brightly displayed for all to behold, such as the following:

usersummaryview3

Again, remember that in order to set a SproutCore object’s properties you use the set() method (at least for the object’s public properties anyway).

Things are looking up! So let go the next mile and modify the view’s description property. Enter the following into the console:

view.set('description', 'Skippy always said that the answer to everything in life is 42')

Hit the enter button and remember to move your mouse over the browser’s window to see the changes if nothing happened. You should see the following:

usersummaryview4

We are on a role now! So let’s modify the view’s final property, age. Again, in the JavaScript console, enter the following:

view.set('age', 17)

If all goes well you should see the final result:

usersummaryview5

Great work! Now its time to do the funky chicken dance in the end-zone.

Hopefully this tutorial showed you the beginnings of how to make a custom view. There is still more to making a full-blown custom view, but this is a good first step. In part 2, I’ll take this custom view and do a bit more advanced stuff by hooking up the view to a model object.

You can find the complete source for the custom view below:


MyApp.UserSummaryView = SC.View.extend({

  name: '',

  description: '',
	
  age: 0,

  displayProperties: ['name', 'description', 'age'],
	
  render: function(context, firstTime) {
    var name = this.get('name');
    var description = this.get('description');
    var age = this.get('age');
	
    context = context.begin('div').addClass('user-summary-view');
    context = context.begin('div').addClass('user-summary-view-name').push(name).end();
    context = context.begin('div').addClass('user-summary-view-desc').push(description).end();
    context = context.begin('div').addClass('user-summary-view-age');
    context = context.begin('div').addClass('user-summary-view-age-value').push(age).end();
    context = context.begin('div').addClass('user-summary-view-age-capt').push('age').end();
    context = context.end();
    context = context.end();

    sc_super();
  }

});

[Update (Aug 16, 2009): Be sure to check out my post discussing some updates to this tutorial here based on some of the feedback below.]

[Update (Aug 16, 2009): Part 2 of how to create a simple custom view has been added.]

About these ads