Basic View API (part 2)

by Evin

In part 2, we will discuss some more advanced concepts in the View API like composite views. We will talk a little bit about the mechanics of how to create childViews as a part of the View API. Creating child views is a very important aspect of creating Sproutcore applications. You will probably do this more often in the code than you will be creating totally custom views. Also you will use them when you architect your application to create different views of the application.

Basics of Childviews

ChildViews can be designed in two main places: In the instance of the view (.lproj directory), and the definition of the custom view (view directory). Depending on what you want to do, you would define the child views in the right layer. For one-off view layout constuction, as in building your user-focused application and laying out the views, you will define the child views in the .lproj directories. If you want to build a composite view that you will be using repeatedly then you would build the definition of the childviews in the view directory.

Childviews in the Instance Layer

We have already seen this in our current application in the main_page.js in english.lproj. We are going to add another child view to show what we can do:

  MyApp.mainPage = SC.Page.design({
 
    // The main pane is made visible on screen as soon as your app is loaded.
    // Add childViews to this pane for views to display immediately on page 
    // load.
    mainPane: SC.MainPane.design({
      childViews: 'titleView blogBodyView'.w(), // <== The List of Child View names that you want to render...
 
      titleView: MyApp.TitleView.design({
        layout: { centerX: 0, top: 10, width: 400, height: 50 },
        classNames: ['another-custom-class'],
        name: 'Basic View API',
        icon: 'tutorial'
      }),
 
      blogBodyView: SC.LabelView.design({
        escapeHTML: NO, // This means that if there is HTML in the value string do not escape it, render the HTML
        layout: { centerX: 0, top: 70, width: 400, height: 500 },
        classNames: ['blog-body'],
        value: "This is the intro to an awesome blog post. <h2>New Header for cool stuff</h2> Keep posting some cool stuff"
      })
    })
  });

OK, we have added two childviews to the main page: titleView, which is the custom view that we have built, and blogBodyView, which is using an basic SC.View. The important thing to note is that we create these as named views in the SC.MainPane design definition. This gives us the ability to reference the child views through named paths:

  MyApp.getPath('mainPane.titleView'); // will return the instance of the MyApp.TitleView in the page
  MyApp.getPath('mainPane.blogBodyView'); // will return the instance of the SC.LabelView in the page

Then, if you want to create them in the page and make them visible to add them to the childViews parameter. This is the most common way that you are going to be creating childviews.

Childviews in the View Definition Layer

Sometimes you will want to create composite views made up of view that you want to use over an over. For this example, we will make a rating bar at the bottom of the blog post. So let’s run:

  sc-gen view MyApp.RatingBarView

Which will create rating_bar.js in the view directory. We are going to add a function called createChildViews() to the file and this is where we are going to put three different views in there: An SC.LabelView; a SC.ButtonView for ‘Helpful Post’; and one SC.ButtonView for ‘Unhelpful Post’. So we will make the file look like this:

  MyApp.RatingBarView = SC.View.extend(
  /** @scope MyApp.RatingBarView.prototype */ {
 
    createChildViews: function(){
      var view, childViews = [];
 
      // The first view.  Using the Class and Full Parameter Method
      view = this.createChildView( SC.LabelView, {
        layout: {left: 10, top: 5, width: 200, height: 25},
        value: "Was this Post Helpful?"
      });
      childViews.push(view);
 
      // The Second View...using the Design Pattern method...only need to fill out the first parameter of the createChildView()
      view = this.createChildView( 
        SC.ButtonView.design({
          layout: {right: 90, top: 5, width: 80, height: 22},
          title: "Yes"
        })
      );
      childViews.push(view);
 
      // The Third View...
      view = this.createChildView( 
        SC.ButtonView.design({
          layout: {right: 5, top: 5, width: 80, height: 22},
          title: "No"
        })
      );
      childViews.push(view);
 
      this.set('childViews', childViews);
    }
 
  });

And then we make a simple change to the main_page.js in the english.lproj directory:

  MyApp.mainPage = SC.Page.design({
 
    // The main pane is made visible on screen as soon as your app is loaded.
    // Add childViews to this pane for views to display immediately on page 
    // load.
    mainPane: SC.MainPane.design({
      childViews: 'titleView blogBodyView ratingBar'.w(),
 
      titleView: MyApp.TitleView.design({
        layout: { centerX: 0, top: 10, width: 400, height: 50 },
        classNames: ['another-custom-class'],
        name: 'Basic View API',
        icon: 'tutorial'
      }),
 
      blogBodyView: SC.LabelView.design({
        escapeHTML: NO, // This means that if there is HTML in the value string do not escape it, render the HTML
        layout: { centerX: 0, top: 70, width: 400, height: 500 },
        classNames: ['blog-body'],
        value: "This is the intro to an awesome blog post. <h2>New Header for cool stuff</h2> Keep posting some cool stuff"
      }),
 
      ratingBar: MyApp.RatingBarView.design({
        layout: { centerX: 0, top: 580, width: 400, height: 32 },
        classNames: ['rating-bar']
      })
    })
  });

And now if we add a little more css, we get the app to look like this:

basic view api part 2 pic 1

using the createChildView()

The createChildView() is a very important function to use. You should only use this function when you are creating child views that will live in your parent view. This function will set up the ownership chain and all the little things that you need the view to do to function correctly in Sproutcore. There are two methods for using the createChildView() that you can choose to do: Class and Full Parameter method and the Design Pattern method. Let’s take a look at the first method, this is lifted from the view.js code in Sproutcore:

  /**
    Instantiates a view to be added to the childViews array during view 
    initialization. You generally will not call this method directly unless 
    you are overriding createChildViews(). Note that this method will 
    automatically configure the correct settings on the new view instance to 
    act as a child of the parent.
 
    @param {Class} viewClass
    @param {Hash} attrs optional attributes to add
    @returns {SC.View} new instance
    @test in createChildViews
  */
  createChildView: function(view, attrs) {
    // Code to do the stuff...
  }

The first parameter is the view class and the second parameters is all the attributes that you want to send to the function when it is created. The second method (Design Pattern) takes advantage of the fact that .design() acts just like a extension of the view class object. It is really up to you which you prefer to do, they work exactly the same.

Micellaneous

There are a couple of functions that are important to note when you are making basic views. You might not need to do all or any of these but they have some little quirks that need to be known:

init()

You might find the need to overwrite the init() function to do some special initialization. This is common practice but you must follow this pattern:

  init: function(){
    sc_super()
 
    // <== YOUR CODE HERE
  }

The reason for this is that the init() in the view is what sets up the bindings and that should be done first and then your stuff second.

didCreateLayer()

This function is called only once at the end of the cycle of creating the view layer. This is where you would set up some extra bindings or some special event handling that needs all the parts created and instantiated first. You do not need to call sc_super() if you extend() SC.View because there is no implementation of didCreateLayer() in SC.View. If you extend one of your own classes or one that does implement didCreateLayer() remember to call sc_super().

willDestroyLayer()

willDestroyLayer() is the paired function to didCreateLayer() and you would need to remove any work that would cause memory leaks that you did in the didCreateLayer()). The same sc_super() principles apply.

Conclusions

This is the basics for building custom views and composite views and 90% of the views that you will create will just need to use what is in this post and part 1. In a later post, I want to show you some more advanced stuff that you can do with the child views.

This entry was posted on Sunday, August 23rd, 2009 at 6:08 am and is filed under Best Practices, Coding Tutorials, Sproutcore. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

2 Responses to “Basic View API (part 2)”

  1. Gary Greyling Says:

    Awesome articles! I have been anxious to learn about views, looking forward to the future article about manipulating child views!! Thanks!!!!

  2. Ido Ran Says:

    Hi,
    Thanks for the great posts.
    I have one thing I really want to know and I think can complete this two posts: how to use the new custom view in a collection view – that is ListView and GridView.

    I am trying to recreate the photos application using Sproutcore 1.0.
    In that application I would like to show the photos grid using a custom view that I will create and to use it as exampleView of the GridView. The problem is that I can’t figure out how to bind each photo model item to the custom view created by the grid.

    Thank you,
    Ido.

Leave a Reply