index.mustache revision 76bf9d78d0779795bbf4cf41abec4ff0300706b2
<div class="intro">
<p>
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.
</p>
<p>
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).
</p>
<p>
A common practice is to associate a View instance with a <a href="../model/index.html">Model</a> instance so that the view is automatically re-rendered whenever the data in the model changes, but this relationship is not required. A view may also be used standalone, associated with a <a href="../model-list/index.html">Model List</a>, or may even contain nested views.
</p>
<p>
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.
</p>
</div>
{{>getting-started}}
<h2>Upgrading from YUI 3.4.x</h2>
<p>
A beta version of View was first introduced in YUI 3.4.0. If you're using View in YUI 3.4.0 or 3.4.1, you'll need to make the following changes to your code when upgrading:
</p>
<ul>
<li>
<p>
The `container`, `model`, and `modelList` properties are now attributes. If you were accessing them as properties, update your code to access them as attributes instead. For example, `var model = myView.model` becomes `var model = myView.get('model')`, and `myView.model = thing` becomes `myView.set('model', thing);`.
</p>
</li>
<li>
<p>
The `container` attribute now treats string values as CSS selectors. Previously, it assumed string values represented raw HTML. To get the same functionality as the old behavior, pass your HTML string through `Y.Node.create()` before passing it to `container`. For example, `new Y.View({container: '<div/>'})` becomes `new Y.View({container: Y.Node.create('<div/>')})`.
</p>
</li>
</ul>
<h2>Using View</h2>
<h3>Instantiating View</h3>
<p>
The most basic way to use View is to create an instance of the `Y.View` class, pass in some configuration attributes, and override the `render()` method at the instance level to define how your view will be rendered.
</p>
<p class="tip">
To demonstrate how to associate a Model with a View, this example uses an instance of the `Y.PieModel` class that's used in the examples in the <a href="../model/">Model User Guide</a>.
</p>
<p class="tip">
Jump to the [[#View Attributes]] section below to learn more about the `container`, `model`, and `template` attributes used in this example.
</p>
```
var pie = new Y.PieModel({type: 'apple'}),
pieView = new Y.View({
// Override the default container attribute.
container: Y.Node.create('<div class="pie"/>'),
// Specify an optional model to associate with the view.
model: pie,
// Provide an optional 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.'
});
// Override the render() method to define how the view will be rendered.
pieView.render = function () {
var container = this.get('container'),
html = Y.Lang.sub(this.template, this.get('model').toJSON());
// Render this view's HTML into the container element.
container.setContent(html);
// Append the container element to the DOM if it's not on the page already.
if (!container.inDoc()) {
Y.one('body').append(container);
}
return this;
};
// Render the view.
pieView.render();
```
<p>
This renders the following HTML to the page:
</p>
```
<div class="pie">
6 slice(s) of apple pie remaining.
</div>
```
<p>
Creating an instance of the base `Y.View` class like this makes it easy to get up and running with a simple view, but probably isn't the best way to create more complex views that need to handle DOM events, re-render automatically on model changes, etc.
</p>
<p>
For more complex use cases, it's usually better to create a custom View subclass by [[#Extending `Y.View`]].
</p>
<h3>Extending `Y.View`</h3>
<p>
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 attributes to implement the desired behavior for your view, while also adding your own methods to handle DOM events and provide other custom view functionality.
</p>
<p>
If you want, you can establish a relationship between your view and a <a href="../model/index.html">Model</a> or <a href="../model-list/index.html">Model List</a> instance by attaching event handlers to them in a custom `initializer()` method. The initializer is typically where you would subscribe to model change events to be notified when you need to re-render your view.
</p>
<p>
This 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 <a href="../model/index.html">Model</a> user guide. It's functionally similar to the example shown in [[#Instantiating View]], but adds the ability to handle a DOM event with a custom event handler, and automatically re-renders the view whenever the associated model changes.
</p>
```
// 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.
// Specify delegated DOM events to attach to the container.
events: {
'.eat': {click: 'eatSlice'}
},
// 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. ' +
'<button class="eat">Eat a Slice!</button>',
// 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.get('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 () {
var container = this.get('container'),
html = Y.Lang.sub(this.template, this.get('model').toJSON());
// Render this view's HTML into the container element.
container.setContent(html);
// Append the container element to the DOM if it's not on the page already.
if (!container.inDoc()) {
Y.one('body').append(container);
}
return this;
},
// The eatSlice function will handle click events on this view's "Eat a Slice"
// button.
eatSlice: function (e) {
// 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 to reflect the new model data.
this.get('model').eatSlice();
}
}, {
// Specify attributes and static properties for your View here.
ATTRS: {
// Override the default container attribute.
container: {
valueFn: function () {
return Y.Node.create('<div class="pie"/>');
}
}
}
});
```
<p>
After defining the `Y.PieView` class and the `Y.PieModel` class (see the <a href="../model/index.html">Model</a> user guide), we can instantiate a new PieView and associate it with a PieModel instance.
</p>
```
var pie = new Y.PieModel({type: 'apple'}),
pieView = new Y.PieView({model: pie});
pieView.render();
```
<p>
This renders the following HTML to the page:
</p>
```
<div class="pie">
6 slice(s) of apple pie remaining. <button class="eat">Eat a Slice!</button>
</div>
```
<p>
If the user clicks the "Eat a Slice!" button, the model will be updated and the view will re-render itself:
</p>
```
<div class="pie">
5 slice(s) of apple pie remaining. <button class="eat">Eat a Slice!</button>
</div>
```
<h3>View Properties</h3>
<p>
The following properties are meaningful to View classes and subclasses.
</p>
<table>
<thead>
<tr>
<th>Property</th>
<th>Default Value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>`events`</td>
<td>`{}`</td>
<td>
<p>
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.
</p>
<p>
See [[#Handling DOM Events]] for more details.
</p>
</td>
</tr>
<tr>
<td>`template`</td>
<td>`''`</td>
<td>
<p>
Reference to a template for this view.
</p>
<p>
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 wish to use as a template, whether that's an HTML string, a `Y.Node` instance, a Mustache template, or anything else your heart desires.
</p>
<p>
How this template gets used is entirely up to you and your custom `render()` method. See [[#Rendering a View]] for more details.
</p>
</td>
</tr>
</tbody>
</table>
<p class="tip">
The View class uses both properties and attributes. What's the difference? In short, properties are best for storing data that might be useful to multiple instances of a View, whereas attributes are best for storing data that pertains only to a specific instance.
</p>
<h3>View Attributes</h3>
<p>
View classes and subclasses provide the following attributes.
</p>
<table>
<thead>
<tr>
<th>Attribute</th>
<th>Default Value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>`container`</td>
<td>`<div>` Node</td>
<td>
<p>
A DOM element, `Y.Node` instance, or selector 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.
</p>
<p>
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.
</p>
</td>
</tr>
<tr>
<td>`model`</td>
<td>`null`</td>
<td>
<p>
A <a href="../model/index.html">Model</a> instance that you want to associate with the view instance. This is most useful when set at or after instantiation time.
</p>
<p>
This attribute 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.
</p>
<p>
For more information, see [[#Associating a View with a Model or Model List]].
</p>
</td>
</tr>
<tr>
<td>`modelList`</td>
<td>`null`</td>
<td>
<p>
A <a href="../model-list/index.html">Model List</a> instance that you want to associate with the view instance. This is most useful when set at or after instantiation time.
</p>
<p>
Like the `model` attribute, the `modelList` attribute is entirely optional, and is provided for convenience only. For more information, see [[#Associating a View with a Model or Model List]].
</p>
</td>
</tr>
</tbody>
</table>
<h3>Handling DOM Events</h3>
<p>
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.
</p>
<p>
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.
</p>
<p>
Event handlers may be specified as functions or as strings that map to function names on the view instance or its prototype.
</p>
```
var Y.MyView = Y.Base.create('myView', Y.View, [], {
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 ...
}
});
```
<p>
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.
</p>
<p>
At instantiation time, you can provide an `events` config property to add or override event handlers for individual view instances.
</p>
```
// 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 ...
}
}
}
});
```
<h3>Rendering a View</h3>
<p>
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.
</p>
<p>
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.
</p>
<p>
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 <a href="http://www.handlebarsjs.com/">Handlebars</a> or <a href="http://mustache.github.com/">Mustache</a>). The View component intentionally avoids dictating how you render things, so you're free to do whatever you like best.
</p>
<p>
In general, it makes sense to [[#Associating a View with a Model or Model List|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).
</p>
<p>
Again, which route you choose to take is entirely up to you.
</p>
<h3>Associating a View with a Model or Model List</h3>
<p>
When instantiating a view, you may pass a `model` attribute in the config object that references a <a href="../model/index.html">Model</a> instance you wish to associate with the view.
</p>
```
// Associate a PieModel instance with a PieView instance.
var pie = new Y.PieModel({type: 'apple'}),
pieView = new Y.PieView({model: pie});
```
<p>
This attribute 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.
</p>
<p>
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`]]).
</p>
<p>
Instead of specifying a model association, you could also choose to associate your view with a <a href="../model-list/index.html">Model List</a>, or even with nothing at all. It's entirely up to you.
</p>
<p>
To associate a view with a <a href="../model-list/index.html">Model List</a> instead of a Model, use the `modelList` config attribute. In your view's initializer, attach event listeners to list events to re-render the view when the list's contents change or when the data of one of the models in the list changes.
</p>
```
// Create a custom View subclass that's associated with a Model List.
var Y.PieListView = Y.Base.create('pieListView', Y.View, [], {
// ... other prototype properties and methods ...
initializer: function () {
var list = this.get('modelList');
// Re-render this view when a model is added to or removed from the model
// list.
list.after(['add', 'remove', 'reset'], this.render, this);
// We'll also re-render the view whenever the data of one of the models in
// the list changes.
list.after('*:change', this.render, this);
}
// ... other prototype properties and methods ...
});
```
<p>
Then pass in a Model List instance when instantiating your view.
</p>
```
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'});
```
<h2>View Extensions</h2>
<h3>Y.View.NodeMap</h3>
<p>
The <a href="{{apiDocs}}/classes/View.NodeMap.html">NodeMap</a> extension adds a static `getByNode()` method to a View subclass that returns the View instance associated with a given Node instance (much like `Y.Widget.getByNode()` does for widgets). The Node may be a View container or a child of a View container.
</p>
<p>
This functionality is provided by an optional extension because it requires you to manually call `destroy()` on your views when you're done using them in order to avoid memory leaks due to long-lived internal references.
</p>
<p>
To use this extension, load the `view-node-map` module and pass `Y.View.NodeMap` in the extensions array when creating a View subclass.
</p>
```
<div id="container">
<div id="child"></div>
</div>
<script>
YUI().use('view', 'view-node-map', function (Y) {
// Create a custom View subclass that mixes in the Y.View.NodeMap extension.
Y.PieView = Y.Base.create('pieView', Y.View, [Y.View.NodeMap]);
// Create a new instance of the custom View.
var pieView = new Y.PieView({container: '#container'});
// Look up the View instance by its container.
Y.PieView.getByNode('#container'); // returns the pieView instance
// ...or by a child of its container.
Y.PieView.getByNode('#child'); // also returns the pieView instance
});
</script>
```
<h2>Views vs. Widgets</h2>
<p>
While `Y.View` and <a href="../widget/index.html">`Y.Widget`</a> may seem similar on the surface, they're intended for different purposes, even though they have some overlap.
</p>
<p>
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.
</p>
<p>
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.
</p>
<p>
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 &mdash; but only as internal plumbing. The widget itself is responsible for providing a reusable public API.
</p>