View

Jump to Table of Contents

A View represents a renderable piece of an application's user interface, and provides hooks for easily subscribing to and handling delegated DOM events on a view's container element.

Views provide a generic structure for template- or DOM-based rendering. Views are template-agnostic, meaning that there's no actual template language built in, so you're welcome to use any template language you want (or none at all).

A common practice is to associate a View instance with a Model instance so that the view is automatically re-rendered whenever the model changes, but this relationship is not required. A view may also be used standalone, associated with a Model List, or may even contain nested views.

The Y.View class is meant to be extended by a custom class that defines a custom render() method and any necessary DOM event handlers.

Getting Started

To include the source files for View and its dependencies, first load the YUI seed file if you haven't already loaded it.

<script src="http://yui.yahooapis.com/3.4.0/build/yui/yui-min.js"></script>

Next, create a new YUI instance for your application and populate it with the modules you need by specifying them as arguments to the YUI().use() method. YUI will automatically load any dependencies required by the modules you specify.

// Create a new YUI instance and populate it with the required modules.
YUI().use('view', function (Y) {
    // View is available and ready for use. Add implementation
    // code here.
});

For more information on creating YUI instances and on the use() method, see the documentation for the YUI Global object.

Using View

Extending Y.View

The first step in creating a custom View class is to extend Y.View. This allows you to override the render() method and default View properties to implement the desired behavior for your view.

If you want, you can also establish a relationship between your view and a Model or Model List instance by attaching event handlers to them in a custom initializer() method. The initializer is also typically where you would subscribe to model change events to be notified when you need to re-render your view.

The View Properties section below describes the properties available on the View class in more detail, but there are a couple of important properties worth mentioning up front:

container

A DOM element, Y.Node instance, or HTML string representing an element that will contain the view's rendered content. This element may already exist on the page, or it may be added to the page later; that's up to you.

When rendering or re-rendering a view, the contents of this element will change, but the element itself won't be removed until you call the view's remove() method.

events

A map of CSS selectors to DOM events that should be handled by your view.

Views provide several other properties as well, but these two are the most important, since they form the core of the view's functionality.

The following example demonstrates how to create a Y.PieView class that displays the current state of a Y.PieModel instance like the one defined in the Model user guide.

// Create a new Y.PieView class that extends Y.View and renders the current
// state of a Y.PieModel instance.
Y.PieView = Y.Base.create('pieView', Y.View, [], {
  // Add prototype methods and properties for your View here if desired. These
  // will be available to all instances of your View. You may also override
  // existing default methods and properties of Y.View.

  // Override the default container element.
  container: '<div class="pie"/>',

  // Provide a template that will be used to render the view. The template can
  // be anything we want, but in this case we'll use a string that will be
  // processed with Y.Lang.sub().
  template: '{slices} slice(s) of {type} pie remaining. ' +
            '<a href="#" class="eat">Eat a Slice!</a>',

  // Specify delegated DOM events to attach to the container.
  events: {
    '.eat': {click: 'eatSlice'}
  },

  // The initializer function will run when a view is instantiated. This is a
  // good time to subscribe to change events on a model instance.
  initializer: function () {
    var model = this.model;

    // Re-render this view when the model changes, and destroy this view when
    // the model is destroyed.
    model.after('change', this.render, this);
    model.after('destroy', this.destroy, this);
  },

  // The render function is responsible for rendering the view to the page. It
  // will be called whenever the model changes.
  render: function () {
    // Render this view's HTML into the container element.
    this.container.setContent(Y.Lang.sub(this.template,
        this.model.getAttrs(['slices', 'type'])));

    // Append the container element to the DOM if it's not on the page already.
    if (!this.container.inDoc()) {
      Y.one('body').append(this.container);
    }
  },

  // The eatSlice function will handle click events on this view's "Eat a Slice"
  // link.
  eatSlice: function (e) {
    e.preventDefault();

    // Call the pie model's eatSlice() function. This will consume a slice of
    // pie (if there are any left) and update the model, thus causing the view
    // to re-render.
    this.model.eatSlice();
  }
});

After defining the Y.PieView class and the Y.PieModel class (see the Model user guide), we can instantiate a new PieView and associate it with a PieModel instance.

var pie     = new Y.PieModel({type: 'apple'}),
    pieView = new Y.PieView({model: pie});

pieView.render();

This renders the following HTML to the page:

<div class="pie">
  6 slice(s) of apple pie remaining. <a href="#" class="eat">Eat a Slice!</a>
</div>

If the user clicks the "Eat a Slice!" link, the model will be updated and the view will re-render itself:

<div class="pie">
  5 slice(s) of apple pie remaining. <a href="#" class="eat">Eat a Slice!</a>
</div>

View Properties

View classes and subclasses contain the following properties.

Property Default Value Description
container '<div/>'

A DOM element, Y.Node instance, or HTML string representing an element that will contain the view's rendered content. The view's DOM events will be attached to this container so that they don't have to be re-attached if the view is re-rendered.

If you specify a container element that isn't already on the page, then you'll need to append it to the page yourself. You can do this in the render() method the first time the view is rendered.

events {}

A map of CSS selectors to DOM events that should be handled by your view. Under the hood, event delegation is used so that the actual events are attached to the view's container element. This means you can freely re-render the contents of the container without having to worry about detaching and re-attaching events.

See Handling DOM Events for more details.

model undefined

A Model instance that you want to associate with the view instance. This is meant to be set as a config property when a view is instantiated.

This property is entirely optional. There's no requirement that views be associated with models, but if you do intend to associate your view with a model, then specifying that model instance at instantiation time will cause a reference to be stored here for convenience.

For more information, see Associating a View with a Model or Model List.

modelList undefined

A Model List instance that you want to associate with the view instance. This is meant to be set as a config property when a view is instantiated.

Like the model property, the modelList property is entirely optional, and is provided for convenience only. For more information, see Associating a View with a Model or Model List.

template ''

Reference to a template for this view.

Much like the model property, this is a convenience property that has no default behavior of its own. It's only provided as a convention to allow you to store whatever you consider to be a template, whether that's an HTML string, a Y.Node instance, a Mustache template, or anything else your heart desires.

How this template gets used is entirely up to you and your custom render() method. See Rendering a View for more details.

Handling DOM Events

The events property of a view class allows you to specify a mapping of CSS selectors to DOM events that should be handled by your view.

Under the hood, event delegation is used so that the actual events are attached to the view's container element. This means you can freely re-render the contents of the container without having to worry about detaching and re-attaching events.

Event handlers may be specified as functions or as strings that map to function names on the view instance or its prototype.

var Y.MyView = Y.Base.create('myView', Y.View, [], {
  // ... other prototype properties and methods ...

  events: {
    // Call `this.toggle()` whenever the element with the id "toggle-button" is
    // clicked.
    '#toggle-button': {click: 'toggle'},

    // Call `this.hoverOn()` when the mouse moves over any element with the
    // "hoverable" class, and `this.hoverOff()` when the mouse moves out of any
    // element with the "hoverable" class.
    '.hoverable': {
        mouseover: 'hoverOn',
        mouseout : 'hoverOff'
    }
  },

  hoverOff: function (e) {
    // ... handle the mouseout event ...
  },

  hoverOn: function (e) {
    // ... handle the mouseover event ...
  },

  toggle: function (e) {
    // ... handle the click event ...
  }
});

The this object in view event handlers will always refer to the current view instance. If you'd prefer this to refer to something else, use Y.bind() to bind a custom this object.

At instantiation time, you can provide an events config property to add or override event handlers for individual view instances.

// Overriding or adding event handlers on a per-instance basis.
var myView = new Y.MyView({
  events: {
    // Replace the #toggle-button click handler with a custom one.
    '#toggle-button': {
      click: function (e) {
        // ... custom click handler ...
      }
    },

    // Add a handler for focus events on elements with the "focusable" class.
    '.focusable': {
      focus: function (e) {
        // ... custom focus handler ...
      }
    }
  }
});

Rendering a View

A view's default render() method is completely empty. It's up to you to override this method and fill it with your own rendering logic.

The ultimate goal of your render() function is to put some HTML into the view's container element and ensure that the container is on the page. How you choose to do this is entirely up to you.

You might choose to use plain old DOM manipulation to create the elements for your view, or you might store an HTML string in your view's template property and then render that, or you might even store a full-blown template (perhaps using Handlebars or Mustache). The View component intentionally avoids dictating how you render things, so you're free to do whatever you like best.

In general, it makes sense to associate a view with a Model or Model List instance so that you can update the view when related data changes. You can do this either by re-rendering the entire view (this is the easiest way) or by modifying only the parts of the view that have changed (harder, but possibly more performant).

Again, which route you choose to take is entirely up to you.

Associating a View with a Model or Model List

When instantiating a view, you may pass a model property in the config object that references a Model instance you wish to associate with the view.

// Associate a PieModel instance with a PieView instance.
var pie     = new Y.PieModel({type: 'apple'}),
    pieView = new Y.PieView({model: pie});

This property is entirely optional. There's no requirement that views be associated with models, but if you do intend to associate your view with a model, then specifying that model instance at instantiation time will cause a reference to be stored for convenience.

If you do set the model property, there's no special magic under the hood that will link the model to your view; you'll still need to manually subscribe to any model events you want to handle in your view's initializer() function (see the example in Extending Y.View).

Instead of specifying a model association, you could also choose to associate your view with a Model List, or even with nothing at all. It's entirely up to you.

To associate a view with a Model List instead of a Model, you'll need to add a bit of extra logic to your view's initializer() method when defining your custom view class.

// Create a custom View subclass that can be associated with a Model List.
var Y.PieListView = Y.Base.create('pieListView', Y.View, [], {
  // ... other prototype properties and methods ...

  initializer: function (config) {
    if (config && config.modelList) {
      // Store the modelList config value as a property on this view instance.
      this.modelList = config.modelList;

      // Re-render the view whenever a model is added to or removed from the
      // model list, or when the entire list is refreshed.
      this.modelList.after(['add', 'remove', 'refresh'], this.render, this);
    }
  },

  // ... other prototype properties and methods ...
});

You could then pass in a Model List instance when instantiating your view.

var pies        = new Y.PieList(),
    pieListView = new Y.PieListView({modelList: pies});

// When we add a pie to the list, the view will be re-rendered.
pies.add({type: 'banana cream'});

Views vs. Widgets

While Y.View and Y.Widget may seem similar on the surface, they're intended for different purposes, even though they have some overlap.

In a nutshell: a view is meant to be used as an internal piece of a component or application, and is not intended to be exposed to external developers as an API or a reusable component itself. A widget, on the other hand, is meant to be a reusable component with a public API.

Views are well suited for rendering portions of web pages, whether large or small, since they're lightweight and can be easily associated with Models and Model Lists. A view may even be responsible for creating and rendering widgets on a page, but the view is an internal piece of an application or component and is not meant to be used externally.

Widgets are well suited for representing self-contained interactive controls or objects that behave like first-class HTML elements. A widget might actually use views to provide a visual representation and even handle some DOM events — but only as internal plumbing. The widget itself is responsible for providing a reusable public API.