brisket/brisket

View on GitHub
docs/brisket.view.md

Summary

Maintainability
Test Coverage
Brisket.View
==========

An extension of Backbone.View that provides a Rendering Workflow, Child View Managment, and helpful rendering callbacks.

## Documentation Index

* [Rendering A View](#rendering-a-view)
* [Events](#events)
* [Setting A Templating Engine](#setting-a-templating-engine)
* [Exposing Data to A Template](#exposing-data-to-a-template)
* [Creating Child Views](#creating-child-views)
* [Rerendering A View](#rerendering-a-view)

## Rendering A View

In a typical Backbone app, you have to implement your Views' `render` methods. Generally that looks something like this:

```js
var View = Backbone.View.extend({
    render: function() {
        this.el.innerHTML = 'some content';

        return this;
    }
});
```

In Brisket, we hijack the `render` method to keep your Views environment agnostic. When `render` is called on a Brisket.View, you can expect that the `template` that you have specified in your View definition will be rendered into your View's internal `el`. The Brisket implementation of `render` also returns the view so that you can chain. For example:

```js
var View = Brisket.View.extend({
    template: '<div class="myView">some content</div>'
});

var view = new View();
console.log(view.render().el.innerHTML); // '<div class="myView">some content</div>'
```

But what if you want to do more complex work than simply rendering the template that is specified (e.g. pick a different template, some post processing, etc)?

Instead of having you override the `render` method, Brisket provides several callbacks that you can use to set up content in your views:

### beforeRender
Use `beforeRender` to set up data, queue up child views, and/or pick a template to use before Brisket renders the View's template into the `el`. `beforeRender` is called on both the client AND the server.

### afterRender
Use `afterRender` to modify your view after Brisket has rendered the View's template into the `el`. This is useful if you have to do some work (e.g. add a special class) that can only be done once the template has been rendered. `afterRender` is called on both the client AND the server.

### onDOM
Use the `onDOM` callback to make changes to your view when it has entered the DOM in the browser. This callback is useful as a place where you can safely expect a `window` object to be available. So if you need to use a jquery plugin that only works in a browser, for example, this is the place to do it. `onDOM` is called on the client but NOT the server.


During a View's [Rendering Workflow](rendering.workflow.md), on both the server AND client, you can expect that `beforeRender` will be called before `afterRender`. On the client, `onDOM` will be called only when the View enters the DOM - that will always occur `afterRender`.

## Events

### 'on-dom'
Triggers when the View enters the DOM.

```js
var view = new Brisket.View();

view.on('on-dom', function() {
  // do something now that view has entered dom;
});
```

### 'close'
Triggers when the View is closed.

```js
var view = new Brisket.View();

view.on('close', function() {
  // do something when view is closed
});
```

## Setting A Templating Engine
By default, all Brisket.Views use [Brisket.Templating.StringTemplateAdapter](brisket.templating.stringtemplateadapter.md) to render templates. To specify a TemplateAdapter other than the default, set the templateAdapter field on a View:

```js
var AddsSmilesTemplateAdapter = TemplateAdapter.extend({

    templateToHTML: function(template, data) {
        return template + ' :) :)';
    }

});

var SmileView = Brisket.View.extend({

    templateAdapter: AddsSmilesTemplateAdapter,

    template: '3 smiles look like :)'

});

var view = new SmileView();
console.log(view.render().el.innerHTML); // '3 smiles look like :) :) :)'
```

## Exposing Data to A Template

With just about every templating engine, you will end up passing data from your View to your template. Since Brisket hijacks the `render` method, it takes care of passing data to your Views' templates.

By default, Brisket will pass a json representation (model.toJSON()) of the View's `model` to the template as data. Using Mustache as an example:

```js
var MustacheTemplateAdapter = TemplateAdapter.extend({

    templateToHTML: function(template, data, partials) {
        return Mustache.render(template, data, partials);
    }

});

var Model = Backbone.Model.extend();

var View = Brisket.View.extend({

    templateAdapter: MustacheTemplateAdapter,

    template: '{{field}}'

});

var model = new Model();
model.set('field', 'data');

var view = new View({ model: model });
console.log(view.render().el.innerHTML); // 'data'
```

While the data in your model may be sufficient to construct the template, sometimes you want to send additional data to the template from the View. Use the `logic` callback to send additional data to the View.

```template
// my/template

{{field}} {{fromLogic}}
```

```js
var MustacheTemplateAdapter = TemplateAdapter.extend({

    templateToHTML: function(template, data, partials) {
        return Mustache.render(template, data, partials);
    }

});

var Model = Backbone.Model.extend();

var View = Brisket.View.extend({

    templateAdapter: MustacheTemplateAdapter,

    template: '{{field}} {{fromLogic}}',

    logic: function() {
        return {
            fromLogic: 'otherData'
        };
    }
});

var model = new Model();
model.set('field', 'data');

var view = new View({ model: model });
console.log(view.render().el.innerHTML); // 'data otherData'
```

Brisket combines the data in the View's `model` AND the data provided by the `logic` function.

In some cases however you may not have a Backbone.Model as the `model` but you have another object that you want to be the data that is sent to the template. Brisket provides a callback called `modelForView` that you can use as the View's template data. With `modelForView` you can use a ViewModel that you've dedicated to your View or a plain object that you construct in-place:

```template
// my/template

{{fromCustomModel}}
```

```js
var MustacheTemplateAdapter = TemplateAdapter.extend({

    templateToHTML: function(template, data, partials) {
        return Mustache.render(template, data, partials);
    }

});

var Model = Backbone.Model.extend();

var View = Brisket.View.extend({

    templateAdapter: MustacheTemplateAdapter,

    template: '{{fromCustomModel}}',

    modelForView: function() {
        return {
            fromCustomModel: 'customData'
        };
    }
});

var model = new Model();
var view = new View();
console.log(view.render().el.innerHTML); // 'customData'
```

Just as before with a real `model`, Brisket combines the data from the View's `modelForView` AND the data provided by the `logic` function.

**Note:** If your View has a `model` AND you define a `modelForView`, the `modelForView` will take precendence and the `model` will be ignored.

## Creating Child Views
Please go [**here**](brisket.childviews.md) to read about child views.

## Rerendering A View
To rerender a View after it's been rendered, call render again and it will re-render in place:

```
var View = Brisket.View.extend({
    template({ count }) {
      return count;
    }
});

var model = new Backbone.Model({ count: 0 });
var view = new View({ model });

view.render().el.innerHTML; // <div>0</div>

model.set("count", 1);

view.render().el.innerHTML; // <div>1</div>
```