In part 1 of how to make a simple custom view, we focused on the very fundamentals. The three main things we learned were: 1) how to add basic properties to a view that will cause the view to be re-rendered; 2) The need to override the render() method from the base class SC.View; and 3) how to build the HTML in the render method using the passed in context argument that is a SC.RenderContext object. For the second part of how to make a simple custom view, we are going to look at how to instead assign an object to our view and use it to render. If you have completed part 1 of this tutorial you should have all the code ready that we’ll make modifications to.

So let’s pretend that we rather have our user summary view make use of an object in our domain model to display a user’s name, description and age. Therefore, we need to make sure that the model object has three properties, which, as you may have guessed, is a name, description and age property. We’ll create a simple User object that will supply all three properties. Below is a visual of what will be happening:

view_model_assignment

For the purposes of this tutorial, we’ll just be focusing on creating a very simple SproutCore object even though there are more preferred methods of making a model object for a SproutCore application. So with that being said, let’s first create our new user object. Just to get us going, we’ll place the code for our user object in our application’s apps/my_app directory where you should see a core.js file. In the directory create a new file called user.js. Now open up the file and enter the following:


MyApp.User = SC.Object.extend({
	
  name: '',
	
  description: '',
	
  age: ''
	
});

That’s it? That’s it. Notice how how we are extending the SC.Object to create our user object. That way we’ll gain the advantages of SproutCore’s key-value observing (KVO) mechanism.

Okay, great. Now we can go back and focus our attention on our user summary view. From the end of the part 1 tutorial we ended up with our user summary view being the following (see my updates for the changes I made to the view):


MyApp.UserSummaryView = SC.View.extend({

  classNames: ['user-summary-view'],

  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-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();

    sc_super();
  }

});

We’re going to play a bit of doctor here and pull some the view’s guts out and and replace it with new parts… Hmm, perhaps I should lay off watching medical surgery shows on TV. Anyway. First things first, let’s strip our view down to the following:


MyApp.UserSummaryView = SC.View.extend({

  classNames: ['user-summary-view'],

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

    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();

    sc_super();
  }

});

We got rid of the view’s basic properties, the displayProperties property, and render() method is no longer making use of those properties. Awesome. Now what? Now let’s update the view to first be the following:


MyApp.UserSummaryView = SC.View.extend(SC.ContentDisplay, {

  classNames: ['user-summary-view'],

  contentDisplayProperties: 'name description age'.w(),

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

    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();

    sc_super();
  }

});

Whoa. What’s this content thing’a’ma’bob all about? What we want to do is get our view to observe a given content object. The “content” object can be any type of object. All the view will care about is that it, again, has a name, description and age property. Our view now extends not just our custom logic but also a SC.ContentDisplay mixin. (A mixin is just a set of properties and methods that can be added to any object). The SC.ContentDisplay adds logic to our view that will monitor a content property and detect changes to the content’s properties. The contentDisplayProperties is what we now use to say what content properties we want to trigger a render, which is the content’s name, description and age properties.

(Side note: the w() method is just a convenience to convert a string of words into an array object. The SproutCore framework extended the JavaScript String object to include it among other methods.)

Now that we’ve made the first set of changes to our view, let’s go ahead and modify the render method to make use of the content property. Update the render method to be the following:


render: function(context, firstTime) {
  var name = '';
  var description = '';
  var age = '';
  var content = this.get('content');
  if (content != null)
  {
    name = content.get('name');
    description = content.get('description');
    age = content.get('age');
  }

  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();

  sc_super();
}

So what did we just do? We simply acquired the view’s content property (added by the SC.ContentDisplay mixin) and got its value for name, description and age using the content’s get() method. Nothin’ to it!

Now that we got all the view’s code it place let’s see what we can do. And like in part 1, we’ll just make use of a browser’s interactive JavaScript console so we can quickly see what is happening. Also remember to start up your application using sc-server. When you start up your browser and direct it to your application you should get the following:

simplecustomview-part2-1

Our view is looking pretty empty but we’ll fix that in a moment. First, let’s create our user object in the console by typing the following:

var user = MyApp.User.create()

Now we have a user object ready to pass to our view. If you look inside the object you’ll see the default value for the object’s properties are empty, that will change but just wait. Okay, now let’s get a reference to our view by typing the following:

var view = MyApp.mainPage.mainPane.userSummaryView

Perfect. All we have to do now is pass our user object to our view by entering the following into the console:

view.set('content', user)

Very simple. Remember how in part 1 we set the properties on the view directly to update the view in the window as in view.set('name', 'Jon Doe')? Well we don’t do that now. Instead we are going to modify the user object to get the view to re-render. Enter the following:

SC.run(function() { user.set('name', 'Luke Skywalker') })

If everything went smoothly you should now see the view updated that displays the name “Luke Skywalker” such as below:

simplecustomview-part2-2

Kick-ass! But why did this just happen? This is where SproutCore’s key-value observing (KVO) comes into play. The view is listening for changes to the content object and when we used the set() method on the user object that triggered the view to react. (We used the SC.run to make sure that the run loop was kicked off to see the changes in the browser immediately). This is pretty cool. Let’s go ahead and now modify the user object’s description and age like so:

SC.run(function() { user.set('description', 'He had daddy issues') })
SC.run(function() { user.set('age', '21?') })

You should see the following:

simplecustomview-part2-3

Excellent! We have successfully updated our view to make use of a content object.

Before we wrap up part 2, I want to just go back to discuss the object being assigned to the view’s content property. Remember that I said the view doesn’t care about the object’s type. The view just cares that the object has the needed properties. So let’s see what that means by assigning a generic SproutCore object to the view’s content property and see what happens. Type the following into your console:

var obj = SC.Object.create({name: 'Darth Vader', description: 'He just needed a hug', age: '60?'})

Good. We just created a generic SproutCore object with properties name, description and age. Now type in the following:

SC.run(function() { view.set('content', obj) })

Again, if everything went fine you should see the following:

simplecustomview-part2-4

This confirms that the view just sees any object given to it as generic. The view just cares about the properties assigned to the object. Now normally in a SproutCore application you would be passing an content object to a view through a controller and make use of bindings, but we used the JavaScript console for quick demonstration purposes.

You have now become a little bit more advanced in creating a custom view in SproutCore. I’ll create future posts about doing more interesting stuff with custom views, but hopefully this and part 1 will get you on the right path. Be sure to check out other excellent blogs here and here to learn interesting SproutCore stuff. The complete code for the custom view is below:


MyApp.UserSummaryView = SC.View.extend(SC.ContentDisplay, {

  classNames: ['user-summary-view'],

  contentDisplayProperties: 'name description age'.w(),
	
  render: function(context, firstTime) {
    var name = '';
    var description = '';
    var age = '';
    var content = this.get('content');
    if (content != null)
    {
      name = content.get('name');
      description = content.get('description');
      age = content.get('age');
    }
	
    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();
		
    sc_super();
  }

});

[Update (Aug 18, 2009): Be sure to check out my update post that provides some clarification to this tutorial.]

Advertisements