From d4330ec6f69c5dec811ec648b0af26f7dbf2456a Mon Sep 17 00:00:00 2001 From: Paul Falgout Date: Wed, 6 Jun 2018 22:53:28 +0900 Subject: [PATCH 1/2] Pass the container to `attachHtml` This is a non breaking change that prevents the method needing `this` or documenting `this.$container` --- src/collection-view.js | 6 +++--- test/unit/collection-view/collection-view-data.spec.js | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/collection-view.js b/src/collection-view.js index 201a53f4b5..8e7a0d7f38 100644 --- a/src/collection-view.js +++ b/src/collection-view.js @@ -547,7 +547,7 @@ const CollectionView = Backbone.View.extend({ view.triggerMethod('before:attach', view); }); - this.attachHtml(els); + this.attachHtml(els, this.$container); _.each(views, view => { if (view._isAttached) { return; } @@ -558,8 +558,8 @@ const CollectionView = Backbone.View.extend({ // Override this method to do something other than `.append`. // You can attach any HTML at this point including the els. - attachHtml(els) { - this.Dom.appendContents(this.$container[0], els, {_$el: this.$container}); + attachHtml(els, $container) { + this.Dom.appendContents($container[0], els, {_$el: $container}); }, isEmpty() { diff --git a/test/unit/collection-view/collection-view-data.spec.js b/test/unit/collection-view/collection-view-data.spec.js index 016955e633..a9b7e264c3 100644 --- a/test/unit/collection-view/collection-view-data.spec.js +++ b/test/unit/collection-view/collection-view-data.spec.js @@ -240,6 +240,15 @@ describe('CollectionView Data', function() { expect($(attachHtmlEls).children()).to.have.lengthOf(5); }); + it('should append to the el', function() { + this.sinon.stub(myCollectionView, 'attachHtml'); + collection.add([{ id: 4 }, { id: 5 }]); + + const callArgs = myCollectionView.attachHtml.args[0]; + const $el = callArgs[1]; + expect($el).to.equal(myCollectionView.$el); + }); + it('should still have all children attached', function() { collection.add([{ id: 4 }, { id: 5 }]); From d7772bf225c1ac58af98ba997c995445598859b0 Mon Sep 17 00:00:00 2001 From: Paul Falgout Date: Wed, 6 Jun 2018 22:53:41 +0900 Subject: [PATCH 2/2] Revamp documentation for v4 changes --- docs/backbone.radio.md | 3 + docs/basics.md | 9 +- docs/classes.md | 93 ++ docs/common.md | 83 +- docs/dom.api.md | 4 +- docs/dom.interactions.md | 288 ++++++ docs/dom.prerendered.md | 141 +++ docs/events.class.md | 550 +++++++++++ docs/events.entity.md | 124 +++ docs/events.md | 249 ++--- docs/features.md | 4 +- docs/installation.md | 3 + docs/marionette.application.md | 80 +- docs/marionette.behavior.md | 275 +++--- docs/marionette.behaviors.md | 74 -- docs/marionette.collectionview.md | 1041 +++++++++++++++++---- docs/marionette.collectionviewadvanced.md | 625 ------------- docs/marionette.mnobject.md | 37 +- docs/marionette.region.md | 312 +++--- docs/marionette.view.md | 791 ++++------------ docs/routing.md | 45 + docs/template.md | 401 -------- docs/upgrade.md | 6 +- docs/view.lifecycle.md | 211 +++++ docs/view.rendering.md | 490 ++++++++++ docs/viewlifecycle.md | 689 -------------- readme.md | 5 +- src/behavior.js | 13 +- src/collection-view.js | 7 +- src/common/bind-events.js | 2 +- src/common/bind-requests.js | 2 +- src/mixins/radio.js | 2 +- src/mixins/regions.js | 2 +- src/mixins/view.js | 2 + src/region.js | 14 +- src/view.js | 11 +- 36 files changed, 3545 insertions(+), 3143 deletions(-) create mode 100644 docs/classes.md create mode 100644 docs/dom.interactions.md create mode 100644 docs/dom.prerendered.md create mode 100644 docs/events.class.md create mode 100644 docs/events.entity.md delete mode 100644 docs/marionette.behaviors.md delete mode 100644 docs/marionette.collectionviewadvanced.md create mode 100644 docs/routing.md delete mode 100644 docs/template.md create mode 100644 docs/view.lifecycle.md create mode 100644 docs/view.rendering.md delete mode 100644 docs/viewlifecycle.md diff --git a/docs/backbone.radio.md b/docs/backbone.radio.md index 2956f1c81f..05673af37d 100644 --- a/docs/backbone.radio.md +++ b/docs/backbone.radio.md @@ -259,6 +259,9 @@ provide bindings to provide automatic event listeners and / or request handlers instances. This works with a bound `channelName` to let us provide listeners using the `radioEvents` and `radioRequests` properties. +**Errors** An error will be thrown if using the radio integration unless `backbone.radio` is setup +as a dependency. + ### API * `channelName` - defines the Radio channel that will be used for the requests and/or events diff --git a/docs/basics.md b/docs/basics.md index a07d469ed5..efaf83a78e 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -16,7 +16,7 @@ patterns etc. Like [Backbone](http://backbonejs.org/#Model-extend), Marionette utilizes the [`_.extend`](http://underscorejs.org/#extend) function to simulate class-based -inheritance. All built-in classes, such as `Marionette.View`, `Marionette.MnObject` +inheritance. [All built-in classes](./classes.md), such as `Marionette.View`, `Marionette.MnObject` and everything that extend these provide an `extend` method for just this purpose. In the example below, we create a new pseudo-class called `MyView`: @@ -117,10 +117,6 @@ documentation. When using functions to set attributes, Marionette will assign the instance of your new class as `this`. You can use this feature to ensure you're able to access your object in cases where `this` isn't what you might expect it to be. -For example, the value or result of -[`templateContext`](./template.md#template-context) is -[bound to its data object](./template.md#binding-of-this) so using a -function is the only way to access the view's context directly. ### Binding Attributes on Instantiation @@ -195,5 +191,6 @@ view.checkOption(); // prints 'some text' ## Common Marionette Functionality -Marionette has a few methods and core functionality that are common to all classes. +Marionette has a few methods and core functionality that are common to [all classes](./classes.md). + [Continue Reading...](./common.md). diff --git a/docs/classes.md b/docs/classes.md new file mode 100644 index 0000000000..e84f94b60f --- /dev/null +++ b/docs/classes.md @@ -0,0 +1,93 @@ +# Marionette Classes + +Marionette follows Backbone's [pseudo-class architecture](./basics.md#class-based-inheritance). +This documentation is meant to provide a comprehensive listing of those classes so that +the reader can have a high-level view and understand functional similarities between the classes. +All of these classes share a [common set of functionality](./common.md). + +### [Marionette.View](./marionette.view.md) + +A `View` is used for managing portions of the DOM via a single parent DOM element or `el`. +It provides a consistent interface for managing the content of the `el` which is typically +administered by serializing a `Backbone.Model` or `Backbone.Collection` and rendering +a template with the serialized data into the `View`s `el`. + +The `View` provides event delegation for capturing and handling DOM interactions as well as +the ability to separate concerns into smaller, managed child views. + +`View` includes: +- [The DOM API](./dom.api.md) +- [Class Events](./events.class.md#view-events) +- [DOM Interactions](./dom.interactions.md) +- [Child Event Bubbling](./events.md#event-bubbling) +- [Entity Events](./events.entity.md) +- [View Rendering](./view.rendering.md) +- [Prerendered Content](./dom.prerendered.md) +- [View Lifecycle](./view.lifecycle.md) + +A `View` can have [`Region`s](#marionetteregion) and [`Behavior`s](#marionettebehavior) + +### [Marionette.CollectionView](./marionette.collectionview.md) + +A `CollectionView` like `View` manages a portion of the DOM via a single parent DOM element +or `el`. This view manages an ordered set of child views that are shown within the view's `el`. +These children are most often created to match the models of a `Backbone.Collection` though a +`CollectionView` does not require a `collection` and can manage any set of views. + +`CollectionView` includes: +- [The DOM API](./dom.api.md) +- [Class Events](./events.class.md#collectionview-events) +- [DOM Interactions](./dom.interactions.md) +- [Child Event Bubbling](./events.md#event-bubbling) +- [Entity Events](./events.entity.md) +- [View Rendering](./view.rendering.md) +- [Prerendered Content](./dom.prerendered.md) +- [View Lifecycle](./view.lifecycle.md) + +A `CollectionView` can have [`Behavior`s](#marionettebehavior). + +### [Marionette.Region](./marionette.region.md) + +Regions provide consistent methods to manage, show and destroy views in your +applications and views. + +`Region` includes: +- [Class Events](./events.class.md#region-events) +- [The DOM API](./dom.api.md) + +### [Marionette.Behavior](marionette.behavior.md) + +A `Behavior` provides a clean separation of concerns to your view logic, allowing you to +share common user-facing operations between your views. + +`Behavior` includes: +- [Class Events](./events.class.md#behavior-events) +- [DOM Interactions](./dom.interactions.md) +- [Entity Events](./events.entity.md) + +### [Marionette.Application](marionette.application.md) + +An `Application` provides hooks for organizing and initiating other elements and a view tree. + +`Application` includes: +- [Class Events](./events.class.md#application-events) +- [Radio API](./backbone.radio.md#marionette-integration) +- [MnObject's API](./marionette.mnobject.md) + +An `Application` can have a single [region](./marionette.application.md#application-region). + +### [Marionette.MnObject](marionette.mnobject.md) + +`MnObject` incorporates backbone conventions `initialize`, `cid` and `extend`. + +`MnObject` includes: +- [Class Events](./events.class.md#mnobject-events) +- [Radio API](./backbone.radio.md#marionette-integration). + +## Routing in Marionette + +Users of versions of Marionette prior to v4 will notice that a router is no longer bundled. +The [Marionette.AppRouter](https://github.com/marionettejs/marionette.approuter) was extracted +and the core library will no longer hold an opinion on routing. + +[Continue Reading](./routing.md) about routing in Marionette. diff --git a/docs/common.md b/docs/common.md index 24d57aa703..6241e8bcce 100644 --- a/docs/common.md +++ b/docs/common.md @@ -1,9 +1,10 @@ # Common Marionette Functionality -Marionette has a few methods that are common to all classes. +Marionette has a few methods that are common to [all classes](./classes.md). ## Documentation Index +* [initialize](#initialize) * [extend](#extend) * [Events API](#events-api) * [triggerMethod](#triggermethod) @@ -16,6 +17,27 @@ Marionette has a few methods that are common to all classes. * [mergeOptions](#mergeoptions) * [The `options` Property](#the-options-property) +### `initialize` + +Like the backbone classes, `initialize` is a method you can define on any Marionette class +that will be called when the class is instantiated and will be passed any arguments passed +at instantiation. The first argument may contain [options](#getoption) the class attaches +to the instance. + +```js +import { MnObject } from 'backbone.marionette'; + +const MyObject = MnObject.extend({ + initialize(options, arg2) { + console.log(options.foo, this.getOption('foo'), arg2); + } +}); + +const myObject = new MyObject({ foo: 'bar' }, 'baz'); // logs "bar" "bar" "baz" +``` + +[Live example](https://jsfiddle.net/marionettejs/1ytrwyog/) + ### `extend` Borrowed from backbone, `extend` is available on all class definitions for @@ -27,12 +49,12 @@ The [Backbone.Events API](http://backbonejs.org/#Events) is available to all cla Each Marionette class can both `listenTo` any object with this API and have events triggered on the instance. -**Note** The events API should not be confused with [`View` `events`](/.marionette.view.md#events) +**Note** The events API should not be confused with [view `events`](/.dom.interactions.md#view-events) which capture DOM events. ### `triggerMethod` -Trigger an event and a corresponding method on the object. +Trigger an event and [a corresponding method](./events.md#onevent-binding) on the object. It is the same as `Backbone`'s [`trigger`](http://backbonejs.org/#Events-trigger) but with the additional method handler. @@ -67,6 +89,8 @@ const myObj = new MyObject(); // console.log "baz" myObj.triggerMethod('foo', 'qux'); // console.log "qux" ``` +More information on `triggerMethod` can be found in the [Marionette events documentation](./events.md#triggermethod). + ### `bindEvents` This method is used to bind any object that works with the [`Backbone.Events` API](#events-api). @@ -99,6 +123,8 @@ any object that has Backbone.Events mixed in) to bind the events from. The second parameter is a hash of `{ 'event:name': 'eventHandler' }` configuration. A function can be supplied instead of a string handler name. +**Errors** An error will be thrown if the second parameter is not an object. + ### `unbindEvents` This method is used to unbind any object that works with the [`Backbone.Events` API](#events-api). @@ -181,6 +207,8 @@ The first parameter, `channel`, is an instance from `Radio`. The second parameter is a hash of `{ 'request:name': 'replyHandler' }` configuration. A function can be supplied instead of a string handler name. +**Errors** An error will be thrown if the second parameter is not an object. + ### `unbindRequests` This method is used to unbind any object that works with the [`Backbone.Radio` Request API](https://github.com/marionettejs/backbone.radio#backboneradiorequests). @@ -228,7 +256,7 @@ same hash with the function names replaced with the function references themselv import { View } from 'backbone.marionette'; const MyView = View.extend({ - initialize: function() { + initialize() { const hash = { 'action:one': 'handleActionOne', // This will become a reference to `this.handleActionOne` 'action:two': this.handleActionTwo @@ -237,15 +265,15 @@ const MyView = View.extend({ this.normalizedHash = this.normalizeMethods(hash); }, - do: function(action) { + do(action) { this.normalizedHash[action](); }, - handleActionOne: function() { + handleActionOne() { console.log('action:one was fired'); }, - handleActionTwo: function() { + handleActionTwo() { console.log('action:two was fired'); } @@ -261,24 +289,29 @@ myView.do('action:two'); ### `getOption` To access an option, we use the `getOption` method. `getOption` will fall back -to the value defined on the instance of the same name if not defined in the options. +to the value of the same name defined on the instance if not defined in the options. ```javascript import { View } from 'backbone.marionette'; -const MyView = View.extend({ - className() { - const defaultClass = this.getOption('defaultClass'); - const extraClasses = this.getOption('extraClasses'); - return [defaultClass, extraClasses].join(' '); - }, - defaultClass: 'table' +const View = View.extend({ + classVal: 'class value', + initialize(){ + this.instanceVal = 'instance value' + } }); -const myView = new MyView({ - model: new MyModel(), - extraClasses: 'table-striped' -}); +const view = new View({ optVal: 'option value' }); + +view.getOption('instanceVal'); // instance value +view.getOption('classVal'); // class value +view.getOption('optVal'); // option value + +const view2 = new View({ instanceVal: 'foo', classVal: 'bar', optVal: 'baz' }); + +view.getOption('instanceVal'); // foo +view.getOption('classVal'); // bar +view.getOption('optVal'); // baz ``` [Live example](https://jsfiddle.net/marionettejs/ekvb8wwa/) @@ -304,8 +337,9 @@ const MyObject = MnObject.extend({ const model1 = new MyObject(); // => "bar" -const foo; -const model2 = new MyObject({ foo: foo }); // => "bar" +const myObj = {}; +console.log(myObj.foo); // undefined +const model2 = new MyObject({ foo: myObj.foo }); // => "bar" ``` [Live example](https://jsfiddle.net/marionettejs/2ddk28ap/) @@ -377,3 +411,10 @@ const myObject = new MyObject({ another: 'value' }); ``` + +## Marionette Classes + +Marionette provides a few classes for building your view tree and +application structure. + +[Continue Reading...](./classes.md). diff --git a/docs/dom.api.md b/docs/dom.api.md index 208d7bea5c..0509239402 100644 --- a/docs/dom.api.md +++ b/docs/dom.api.md @@ -5,7 +5,7 @@ jQuery and integrate with the DOM using a custom api. ## API Methods -The DOM API manages the DOM on behalf of each view type and `Region`. +The DOM API manages the DOM on behalf of [each view class and `Region`](./classes.md). It defines the methods that actually attach and remove views and children. [The default API](#the-default-api) depends on Backbone's jQuery `$` object however it does not @@ -109,7 +109,7 @@ import { View } from 'backbone.marionette'; const MyView = View.extend(); MyView.setDomApi({ - setContents: function(el, html) { + setContents(el, html) { el.innerHTML = html; } }); diff --git a/docs/dom.interactions.md b/docs/dom.interactions.md new file mode 100644 index 0000000000..d266047d12 --- /dev/null +++ b/docs/dom.interactions.md @@ -0,0 +1,288 @@ +# DOM Interactions + +In addition to what Backbone provides the views, Marionette has additional API +for DOM interactions available to all Marionette [view classes](./classes.md). + +### DOM Interactions in a Backbone.View + +Marionette's Views extend [`Backbone.View`](http://backbonejs.org/#View) and +so have references to the view's `el`, `$el`, and `this.$()` as well as +defining an `events` hash. + +These methods provide ways for interacting with the view scoped to it's `el` +_and_ all of the view's children. To restate `events` and `this.$()` will query +the view's template and all of the children. Marionette's added interfaces +attempt to scope interactions with only the view's template, leaving the +children to handle themselves. + +### Binding To User Input + +Views can bind custom events whenever users perform some interaction with the +DOM. Using the view [`events`](#view-events) and [`triggers`](#view-triggers) +handlers lets us either bind user input directly to an action or fire a generic +trigger that may or may not be handled. + +#### Event and Trigger Mapping + +The `events` and `triggers` attributes bind DOM events to actions to perform on +the view. They each take a DOM event key and a mapping to the handler. + +We'll cover a simple example: + +```javascript +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + events: { + 'drop': 'onDrop', + 'click .btn-show-modal': 'onShowModal', + 'click @ui.save': 'onSave' + }, + + triggers: { + 'click @ui.close': 'close' + }, + + ui: { + save: '.btn-save', + close: '.btn-cancel' + }, + + onShowModal() { + console.log('Show the modal'); + }, + + onSave() { + console.log('Save the form'); + }, + + onDrop() { + console.log('Handle a drop event anywhere in the element'); + } +}); +``` + +Event listeners are constructed by: + +```javascript +' [dom node]': 'listener' +``` + +The `dom event` can be a jQuery DOM event - such as `click` - or another custom +event, such as Bootstrap's `show.bs.modal`. + +The `dom node` represents a jQuery selector or a `ui` key prefixed by `@.`. +The `dom node` is optional, and if omitted, the view's `$el` will be used as the +selector. For more information about the `ui` object, and how it works, see +[the documentation on ui](#organizing-your-view). + +#### View `events` + +The view `events` attribute binds DOM events to functions or methods on the +view. The simplest form is to reference a method on the view: + +```javascript +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + events: { + 'click a': 'onShowModal' + }, + + onShowModal(event) { + console.log('Show the modal'); + } +}); +``` + +[Live example](https://jsfiddle.net/marionettejs/jfxwtmxj/) + +The DOM event gets passed in as the first argument, allowing you to see any +information passed as part of the event. + +**When passing a method reference, the method must exist on the View.** + +The `events` attribute can also directly bind functions: + +```javascript +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + events: { + 'click a'(event) { + console.log('Show the modal'); + } + } +}); +``` + +[Live example](https://jsfiddle.net/marionettejs/obt5vt09/) + +As when passing a string reference to a view method, the `events` attribute +passes in the `event` as the argument to the function called. + +**Note** Backbone `events` are delegated to the view's `el`. This means that +events with a dom node selector will be handled for the view and any decendants. +So if you attach a child with the same selector as the parent event handler, the +parent will handle the event for both views. + +#### View `triggers` + +The view `triggers` attribute binds DOM events to Marionette events that +can be responded to at the view or parent level. For more information on events, +see the [events documentation](./events.md). This section will just +cover how to bind these events to views. + +```javascript +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + triggers: { + 'click a': 'click:link' + }, + + onClickLink(view, event) { + console.log('Show the modal'); + } +}); +``` + +[Live example](https://jsfiddle.net/marionettejs/exu2s3tL/) + +When the `a` tag is clicked here, the `link:click` event is fired. This event +can be listened to using the [`onEvent` Binding](./events.md#onevent-binding) +technique discussed in the [events documentation](./events.md). + +The major benefit of the `triggers` attribute over `events` is that triggered +events can bubble up to any parent views. For a full explanation of bubbling +events and listening to child events, see the +[event bubbling documentation](./events.md#event-bubbling).. + +#### View `triggers` Event Object + +Event handlers will receive the triggering view as the first argument and the +DOM Event object as the second followed by any extra parameters triggered by the event. + +**NOTE** It is _strongly recommended_ that View's handle their own DOM event objects. It should +be considered a best practice to not utilize the DOM event in external listeners. + +By default all trigger events are stopped with [`preventDefault`](./features.md#triggerspreventdefault) +and [`stopPropagation`](./features.md#triggersstoppropagating) methods. This by nature artificially +scopes event handling to the view's template preventing event handling of the same selectors in +child views. However you can manually configurethe triggers using a hash instead of an event name. +The example below triggers an event and prevents default browser behaviour using `preventDefault`. + +```js +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + triggers: { + 'click a': { + event: 'link:clicked', + preventDefault: true, // this param is optional and will default to true + stopPropagation: false + } + } +}); +``` + +The default behavior for calling `preventDefault` can be changed with the feature flag +[`triggersPreventDefault`](./features.md#triggerspreventdefault), and `stopPropagation` +can be changed with the feature flag [`triggersStopPropagation`](./features.md#triggersstoppropagating). + +## Organizing Your View + +The `View` provides a mechanism to name parts of your template to be used +throughout the view with the `ui` attribute. This provides a number of benefits: + +1. Provide a single defined reference to commonly used UI elements +2. Cache the jQuery selector +3. Query from only the view's template and not the children + +### Defining `ui` + +To define your `ui` hash, just set an object of named jQuery selectors to the +`ui` attribute of your View: + +```javascript +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + template: MyTemplate, + ui: { + save: '#save-button', + close: '.close-button' + } +}); +``` + +Inside your view, the `save` and `close` references will point to the jQuery +selectors `#save-button` and `.close-button`respectively found only in the +rendered `MyTemplate`. + +### Accessing UI Elements + +To get the handles to your UI elements, use the `getUI(ui)` method: + +```javascript +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + template: MyTemplate, + ui: { + save: '#save-button', + close: '.close-button' + }, + + onFooEvent() { + const $saveButton = this.getUI('save'); + $saveButton.addClass('disabled'); + $saveButton.attr('disabled', 'disabled'); + } +}); +``` + +[Live example](https://jsfiddle.net/marionettejs/rpa58v0g/) + +As `$saveButton` here is a jQuery selector, you can call any jQuery methods on +it, according to the jQuery documentation. + +#### Referencing UI in `events` and `triggers` + +The UI attribute is especially useful when setting handlers in the +[`events`](#view-events) and [`triggers`](#view-triggers) objects - simply use +the `@ui.` prefix: + +```javascript +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + template: MyTemplate, + ui: { + save: '#save-button', + close: '.close-button' + }, + + events: { + 'click @ui.save': 'onSave' + }, + + triggers: { + 'click @ui.close': 'close' + }, + + onSave() { + this.model.save(); + } +}); +``` + +[Live example](https://jsfiddle.net/marionettejs/f2k0wu05/) + +In this example, when the user clicks on `#save-button`, `onSave` will be +called. If the user clicks on `.close-button`, then the event `close:view` will +be fired on `MyView`. + +By prefixing with `@ui`, we can change the underlying template without having to +hunt through our view for every place where that selector is referenced - just +update the `ui` object. diff --git a/docs/dom.prerendered.md b/docs/dom.prerendered.md new file mode 100644 index 0000000000..a5af7467c0 --- /dev/null +++ b/docs/dom.prerendered.md @@ -0,0 +1,141 @@ +# Prerendered Content + +[View classes](./classes.md) can be initialized with pre-rendered DOM. + +This can be HTML that's currently in the DOM: + +```javascript +import { View } from 'backbone.marionette'; + +const myView = new View({ el: $('#foo-selector') }); + +const myView = new MyView(); +myView.isRendered(); // true if '#foo-selector` exists and has content +myView.isAttached(); // true if '#foo-selector` is in the DOM +``` + +Or it can be DOM created in memory: + +```javascript +import { View } from 'backbone.marionette'; + +const $inMemoryHtml = $('
Hello World!
'); + +const myView = new View({ el: $inMemoryHtml }); +``` + +[Live example](https://jsfiddle.net/marionettejs/b2yz38gj/) + +In both of the cases at instantiation the view will determine +[its state](./view.lifecycle.md) as to whether the el is rendered +or attached. + +**Note** `render` and `attach` events will not fire for the initial +state as the state is set already at instantiation and is not changing. + +## Managing `View` children + +With [`View`](./marionette.view.md) in most cases the [`render` event](./events.class.md#render-and-beforerender-events) +is the best place to show child views [for best performance](./marionette.view.md#efficient-nested-view-structures). + +However with pre-rendered DOM you may need to show child views in `initialize` +as the view will already be rendered. + +```javascript +import { View } from 'backbone.marionette'; +import HeaderView from './header-view'; + +const MyBaseLayout = View.extend({ + regions: { + header: '#header-region', + content: '#content-region' + }, + el: $('#base-layout'), + initialize() { + this.showChildView('header', new HeaderView()); + } +}); +``` + +### Managing a Pre-existing View Tree. + +It may be the case that you need child views of already existing DOM as well. +To set this up you'll need to query for `el`s down the tree: + +```javascript +import { View } from 'backbone.marionette'; +import HeaderView from './header-view'; + +const MyBaseLayout = View.extend({ + regions: { + header: '#header-region', + content: '#content-region' + }, + el: $('#base-layout'), + initialize() { + this.showChildView('header', new HeaderView({ + el: this.getRegion('header').$el.contents() + })); + } +}); +``` + +The same can be done with [`CollectionView`](./marionette.collectionview.md): + +```javascript +import { CollectionView } from 'backbone.marionette'; +import ItemView from './item-view'; + +const MyList = CollectionView.extend({ + el: $('#base-table'), + childView: ItemView, + childViewContainer: 'tbody', + buildChildView(model, ChildView) { + const index = this.collection.indexOf(model); + const childEl = this.$('tbody').contents()[index]; + + return new ChildView({ + model, + el: childEl + }); + } +}); + +const myList = new MyList({ collection: someCollection }); + +// Unlike `View`, `CollectionView` should be rendered to build the `children` +myList.render(); +``` + +https://github.com/marionettejs/backbone.marionette/issues/3128 + +## Re-rendering children of a view with preexisting DOM. + +You may be instantiating a `View` with existing HTML, but if you re-render the view, +like any other view, your view will render the `template` into the view's `el` and +any children will need to be re-shown. + +So your view will need to be prepared to handle both scenarios. + +```javascript +import _ from 'underscore'; +import { View } from 'backbone.marionette'; +import HeaderView from './header-view'; + +const MyBaseLayout = View.extend({ + regions: { + header: '#header-region', + content: '#content-region' + }, + el: $('#base-layout'), + initialize() { + this.showChildView('header', new HeaderView({ + el: this.getRegion('header').$el.contents() + })); + }, + template: _.template('
'), + onRender() { + this.showChildView('header', new HeaderView()); + } +}); +``` diff --git a/docs/events.class.md b/docs/events.class.md new file mode 100644 index 0000000000..8ffb6f1a3f --- /dev/null +++ b/docs/events.class.md @@ -0,0 +1,550 @@ +# Class Events + +Marionette uses [`triggerMethod`](./events.md#triggermethod) internally to trigger various +events used within the [classes](./classes.md). This provides ['onEvent' binding](./events.md#onevent-binding) +providing convenient hooks for handling class events. Notably all internally triggered events +will pass the triggering class instance as the first argument of the event. + +## Documentation Index + +* [Application Events](#application-events) + * [`before:start` event](#before-start-event) + * [`start` event](#start-event) +* [Behavior Events](#behavior-events) + * [`initialize` event](#initialize-event) + * [Proxied Events](#proxied-events) +* [Region Events](#region-events) + * [`show` and `before:show` events](#show-and-beforeshow-events) + * [`empty` and `before:empty` events](#empty-and-beforeempty-events) +* [MnObject Events](#mnobject-events) +* [View Events](#view-events) + * [`add:region` and `before:add:region` events](#addregion-and-beforeaddregion-events) + * [`remove:region` and `before:remove:region` events](#removeregion-and-beforeremoveregion-events) +* [CollectionView Events](#collectionview-events) + * [`add:child` and `before:add:child` events](#addchild-and-beforeaddchild-events) + * [`remove:child` and `before:remove:child` events](#removechild-and-beforeremovechild-events) + * [`sort` and `before:sort` events](#sort-and-beforesort-events) + * [`filter` and `before:filter` events](#filter-and-beforefilter-events) + * [`render:children` and `before:render:children` events](#renderchildren-and-beforerenderchildren-events) + * [`destroy:children` and `before:destroy:children` events](#destroychildren-and-beforedestroychildren-events) + * [CollectionView EmptyView Region Events](#collectionview-emptyview-region-events) +* [DOM Change Events](#dom-change-events) + * [`render` and `before:render` events](#render-and-beforerender-events) + * [`attach` and `before:attach` events](#attach-and-beforeattach-events) + * [`detach` and `before:detach` events](#detach-and-beforedetach-events) + * [`dom:refresh` event](#domrefresh-event) + * [`dom:remove` event](#domremove-event) + * [Advanced Event Settings](#advanced-event-settings) +* [Destroy Events](#destroy-events) + * [`destroy` and `before:destroy` events](#destroy-and-beforedestroy-events) +* [Supporting Backbone Views](#supporting-backbone-views) + * [`Marionette.Events` and `triggerMethod`](#marionetteevents-and-triggermethod) + * [Lifecycle Events](#lifecycle-events) + +## Application Events + +The `Application` object will fire two events: + +### `before:start` event + +Fired just before the application is started. Use this to prepare the +application with anything it will need to start, for example instantiating +routers, models, and collections. + +### `start` event + +Fired as part of the application startup. This is where you should be showing +your views and starting `Backbone.history`. + +```javascript +import Bb from 'backbone'; +import { Application } from 'backbone.marionette'; + +import MyModel from './mymodel'; +import MyView from './myview'; + +const MyApp = Application.extend({ + region: '#root-element', + + initialize(options) { + console.log('Initialize' + options.foo); + }, + + onBeforeStart(app, options) { + this.model = new MyModel(options.data); + }, + + onStart(app, options) { + this.showView(new MyView({model: this.model})); + Bb.history.start(); + } +}); + +const myApp = new MyApp({ foo: 'My App' }); +myApp.start({ data: { bar: true } }); +``` + +[Live example](https://jsfiddle.net/marionettejs/ny59rs7b/) + +As shown the `options` object is passed into the `Application` as the +second argument to `start`. + +#### Application `destroy` events + +The `Application` class also triggers [Destroy Events](#destroy-and-beforedestroy-events). + +## Behavior Events + +### `initialize` event + +After the view and behavior are [constructed and initialized](./marionette.behavior.md#events--initialize-order), +the last event to occur is an `initialize` event on the behavior which is passed +the view instance and any options passed to the view at instantiation. + +```javascript +import { Behavior, View } from 'backbone.marionette'; + +const MyBehavior = Behavior.extend({ + onInitialize(view, options) { + console.log(options.msg); + } +}); + +const MyView = View.extend({ + behaviors: [MyBehavior] +}); + +const myView = new MyView({ msg: 'view initialized' }); +``` + +**Note** This event is unique in that the triggering class instance (the view) is not the same instance +as the handler (the behavior). In most cases internally triggered events are triggered and handled by +the same instance, but this is an exception. + +### Proxied Events + +A `Behavior`'s view events [are proxied directly on the behavior](./marionette.behavior.md#proxy-handlers). + +**Note** In order to prevent conflict `Behavior` does not trigger [destroy events](#destroy-and-beforedestroy-events) +with its own destruction. A `destroy` event occurring on the `Behavior` will have originated from the related view. + +## Region Events + +When you show a view inside a region - either using [`region.show(view)`](./marionette.region.md#showing-a-view) or +[`showChildView('region', view)`](./marionette.view.md#showing-a-view) - the `Region` will emit events around the view +events that you can hook into. + +The `Region` class also triggers [Destroy Events](#destroy-and-beforedestroy-events). + +### `show` and `before:show` events + +These events fire before (`before:show`) and after (`show`) showing anything in a region. +A view may or may not be rendered during `before:show`, but a view will be rendered by `show`. + +The `show` events will receive the region instance, the view being shown, and any options passed to `region.show`. + +```javascript +import { Region, View } from 'backbone.marionette'; + +const MyRegion = Region.extend({ + onBeforeShow(myRegion, view, options) { + console.log(myRegion.hasView()); //false + console.log(view.isRendered()); // false + console.log(options.foo === 'bar'); // true + }, + onShow(myRegion, view, options) { + console.log(myRegion.hasView()); //true + console.log(view.isRendered()); // true + console.log(options.foo === 'bar'); // true + } +}); + +const MyView = View.extend({ + template: _.template('hello') +}); + +const myRegion = new MyRegion({ el: '#dom-hook' }); + +myRegion.show(new MyView(), { foo: 'bar' }); +``` + +### `empty` and `before:empty` events + +These events fire before (`before:empty`) and after (`empty`) emptying a region's view. +These events will not fire if there is no view in the region, even if the region detaches +DOM from within the region's `el`. +The view will not be detached or destroyed during `before:empty`, +but will be detached or destroyed during the `empty`. + +The empty events will receive the region instance, the view leaving the region. + +```javascript +import { Region, View } from 'backbone.marionette'; + +const MyRegion = Region.extend({ + onBeforeEmpty(myRegion, view) { + console.log(myRegion.hasView()); //true + console.log(view.isDestroyed()); // false + }, + onEmpty(myRegion, view) { + console.log(myRegion.hasView()); //false + console.log(view.isDestroyed()); // true + } +}); + +const MyView = View.extend({ + template: _.template('hello') +}); + +const myRegion = new MyRegion({ el: '#dom-hook' }); + +myRegion.empty(); // no events, no view emptied + +myRegion.show(new MyView()); + +myRegion.empty(); +``` +## MnObject Events + +The `MnObject` class triggers [Destroy Events](#destroy-and-beforedestroy-events). + +## View Events + +### `add:region` and `before:add:region` events + +These events fire before (`before:add:region`) and after (`add:region`) a region is added to a view. +This event handler will receive the view instance, the region name string, and the region instance as +event arguments. The region is fully instantated for both events. + +### `remove:region` and `before:remove:region` events + +These events fire before (`before:remove:region`) and after (`remove:region`) a region is removed from a view. +This event handler will receive the view instance, the region name string, and the region instance as +event arguments. The region will be not be destroyed in the before event, but is destroyed by `remove:region`. + +**Note** Currently these events are only triggered using the `view.removeRegion` API and not when the region +is destroyed directly. https://github.com/marionettejs/backbone.marionette/issues/3602 + +## CollectionView Events + +The `CollectionView` triggers unique events specifically related to child management. + +### `add:child` and `before:add:child` events + +These events fire before (`before:add:child`) and after (`add:child`) each child view +is instantiated and added to the [`children`](./collectionview.md#collectionviews-children). +These will fire once for each item in the attached collection or for any view added using +[`addChildView`](./collectionview.md#adding-a-child-view). + +### `remove:child` and `before:remove:child` events + +These events fire before (`before:remove:child`) and after (`remove:child`) each child view +is removed to the [`children`](./collectionview.md#collectionviews-children). +A view may be removed from the `children` if it is destroyed, if it is removed +from the `collection` or if it is removed with [`removeChildView`](./collectionview.md#removing-a-child-view). + +**NOTE** A childview may or may not be destroyed by this point. + +**NOTE** When a `CollectionView` is destroyed it will not individually remove its `children`. +Each childview will be destroyed, but any needed clean up during the `CollectionView`'s destruction +should happen in [`before:destroy:children`](#destroychildren-and-beforedestroychildren-events). + +### `sort` and `before:sort` events + +These events fire before (`before:sort`) and after (`sort`) sorting the children in the `CollectionView`. +These events will only fire if there are [`children`](./collectionview.md#collectionviews-children) +and a [`viewComparator`](./collectionview.md#defining-the-viewcomparator) + +### `filter` and `before:filter` events + +These events fire before (`before:filter`) and after (`filter`) filtering the children in the `CollectionView`. +This event will only fire if there are [`children`](./collectionview.md#collectionviews-children) +and a [`viewFilter`](./collectionview.md#defining-the-viewfilter). + +When the `filter` event is fired the children filtered out will have already been +detached from the view's `el`, but new children will not yet have been rendered. +The `filter` event not only receives the view instance, but also arrays of attached views, +and detached views. + +```javascript +const MyCollectionView = CollectionView.extend({ + onBeforeFilter(myCollectionView) { + console.log('Nothing has changed yet!'); + }, + onFilter(myCollectionView, attachViews, detachedView) { + console.log('Array of attached views', attachedView); + console.log('Array of detached views', attachedView); + } +}); +``` + +### `render:children` and `before:render:children` events + +Similar to [`Region` `show` and `before:show` events](#show-and-beforeshow-events) these events fire +before (`before:render:children`) and after (`render:children`) the `children` of the `CollectionView` +are attached to the `CollectionView`'s `el` or `childViewContainer`. + +These events will be passed the `CollectionView` instance and the array of views being attached. +The views in the array may or may not be rendered or attached for `before:render:children`, +but will be rendered and attached by `render:children`. + +If the `CollectionView` can determine that added views will only be appended to the end, only the appended views +will be passed to the event. Otherwise all of the `children` views will be passed. + +**Note** if you consistently need all of the views within this event use [`children`](./marionette.collectionview.md#collectionviews-children) + +### `destroy:children` and `before:destroy:children` events + +These events fire before (`before:destroy:children`) and after (`destroy:children`) destroying the children +in the `CollectionView`. These events will only fire if there are [`children`](./collectionview.md#collectionviews-children). + +### CollectionView EmptyView Region Events + +The `CollectionView` uses a region internally that can be used to know when the empty view is show or destroyed. +See [Region Events](#region-events). + +```javascript +import { CollectionView } from 'backbone.marionette'; + +const MyView = CollectionView.extend({ + emptyView: MyEmptyView +}); + +const myView = new MyView(); + +myView.getEmptyRegion().on({ + 'show'() { + console.log('CollectionView is empty!'); + }, + 'before:empty'() { + if (this.hasView()) { + console.log('CollectionView is removing the emptyView'); + } + } +}); + +myView.render(); +``` + +## DOM Change Events + +### `render` and `before:render` events + +Reflects when a view's template is being rendered into its `el`. + +`before:render` will occur prior to removing any current child views. +`render` is an ideal event for attaching child views to the view's template as the first +render _generally_ occurs prior to the view attaching to the DOM. + +```javascript +import { View, CollectionView } from 'backbone.marionette'; +import MyChildView from './MyChildView'; + +const MyView = View.extend({ + template: _.template('
'), + regions: { + 'foo': '.foo-region' + }, + onRender() { + this.showChildView('foo', new MyChildView()); + } +}); + +const MyCollectionView = CollectionView.extend({ + childView: MyChildView, + onRender() { + // Add a child not from the `collection` + this.addChildView(new MyChildView()); + } +}) +``` + +**Note** This event is only triggered when rendering a template into a view. A view that +is pre-rendered will not have this event triggered unless re-rendered. [Pre-rendered views](./dom.prerendered.md) +should use `initialize` for attaching child views and the `render` event if the view is re-rendered. + +**Note** If a view's `template` is set to `false` this event will not trigger. + +### `attach` and `before:attach` events + +Relects when the `el` of a view is attached to the DOM. These events will not trigger when +a view is re-rendered as the `el` itself does not change. + +`attach` is the ideal event to setup any external DOM listeners such as `jQuery` plugins +that use the view's `el`, but _not_ its contents. + +### `detach` and `before:detach` events + +Relects when the `el` of a view is detached from the DOM. These events will not trigger when +a view is re-rendered as the `el` itself does not change. + +`before:detach` is the ideal event to clean up any external DOM listeners such as `jQuery` plugins +that use the view's `el`, but _not_ its contents. + +### `dom:refresh` event + +Relects when the _contents_ of a view's `el` change in the DOM. +This event will fire when the view is first [`attach`ed](#attach-and-beforeattach-events). +It will also fire if an attached view is re-rendered. + +This is the ideal event to setup any external DOM listeners such as `jQuery` plugins +that use DOM _within_ the `el` of the view and not the view's `el` itself. + +**NOTE** This event will not fire if the view has no template to render unless it contains +prerendered html. + +### `dom:remove` event + +Relects when the _contents_ of a view's `el` are about to change in the DOM. +This event will fire when the view is about to be [`detach`ed](#detach-and-beforedetach-events). +It will also fire before an attached view is re-rendered. + +This is the ideal event to clean up any external DOM listeners such as `jQuery` plugins +that use DOM _within_ the `el` of the view and not the view's `el` itself. + +**NOTE** This event will not fire if the view has no template to render unless it contains +prerendered html. + +### Advanced Event Settings + +Marionette is able to trigger `attach`/`detach` events down the view tree along with +triggering the `dom:refresh`/`dom:remove` events because of the view event monitor. +This monitor starts when a view is created or shown in a region (to handle non-Marionette views). + +In some cases it may be a useful performance improvement to disable this functionality. +Doing so is as easy as setting `monitorViewEvents: false` on the view class. + +```javascript +import { View } from 'backbone.marionette'; + +const NonMonitoredView = View.extend({ + monitorViewEvents: false +}); +``` + +**Note**: Disabling the view monitor will break the monitor generated events for this view +_and all child views_ of this view. Disabling should be done carefully. + +## Destroy Events + +### `destroy` and `before:destroy` events + +Every class has a `destroy` method which can be used to clean up the instance. +With the exception of `Behavior`'s each of these methods triggers a `before:destroy` +and a `destroy` event. + +As a general rule, `onBeforeDestroy` is the best handler for cleanup as the instance +and any internally created children are already destroyed by the time `onDestroy` is called. + +**Note** For views this is not the ideal location for clean up of anything touching the DOM. +See [`dom:remove`](#domremove-event) or [`before:detach`] for DOM related clean up. + +```javascript +import { Application, View } from 'backbone.marionette'; + +const MyView = View.extend({ + onBeforeDestroy(options) { + console.log(options.foo); + } +}); + +const myView = new MyView(); + +mvView.destroy({ foo: 'destroy view' }); + +const MyApp = Application.extend({ + onBeforeDestroy(options) { + console.log(options.foo); + } +}); + +const myApp = new MyApp(); + +myApp.destroy({ foo: 'destroy app' }); +``` + +#### `CollectionView` `destroy:children` and `before:destroy:children` events + +Similar to `destroy`, `CollectionView` has events for when all of its children +are destroyed. See [the CollectionView's events](#destroychildren-and-beforedestroychildren-events) +for more information. + +## Supporting Backbone Views + +### `Marionette.Events` and `triggerMethod` + +Internally Marionette uses [`triggerMethod`](./common.md#triggermethod) for event triggering. +This API is not available to `Backbone.View`s so in order to support `Backbone.View`s in Marionette v4+, +`Marionette.Events` must be mixed into the non-Marionette view. + +This can be done for an individual view definition: +```javascript +import { Events } from 'backbone.marionette'; + +const MyBbView = Backbone.View.extend(Events); +``` +or for all `Backbone.View`s +```javascript +_.extend(Backbone.View.prototype, Events); +``` + +### Lifecycle Events + +#### `render` and `destroy` + +To support non-Marionette Views, Marionette uses two flags to determine if it should trigger +`render` and `destroy` events on the view. If a custom view throws it's own `render` or `destroy` +events, the related flag should be set to `true` to avoid Marionette duplicating these events. + +```javascript +// Add support for triggerMethod +import { Events } from 'backbone.marionette'; + +_.extend(Backbone.View.prototype, Events); + +const MyCustomView = Backbone.View.extend({ + supportsRenderLifecycle: true, + supportsDestroyLifecycle: true, + render() { + this.triggerMethod('before:render'); + + this.$el.html('render html'); + + // Since render is being triggered here set the + // supportsRenderLifecycle flag to true to avoid duplication + this.triggerMethod('render'); + }, + destroy() { + this.triggerMethod('before:destroy'); + + this.remove(); + + // Since destroy is being triggered here set the + // supportsDestroyLifecycle flag to true to avoid duplication + this.triggerMethod('destroy'); + } +}); +``` + +#### DOM Change Lifecycle Events + +As mentioned in [Advanced Event Settings](#advanced-event-settings) some DOM events +are triggers from the view event monitor that will handle DOM attachment related events +down the view tree. Backbone View's won't have the functionality unless the monitor is +added. This will include all [DOM Change Events](#dom-change-events) other than render. + +You can add the view events monitor to any non-Marionette view: +```javascript +import { monitorViewEvents, Events } from 'backbone.marionette'; + +// Add support for triggerMethod +_.extend(Backbone.View.prototype, Events); + +const MyCustomView = Backbone.View.extend({ + initialize() { + monitorViewEvents(this); + // Ideally this happens first prior to any rendering + // or attaching that might occur in the initialize + } +}); +``` diff --git a/docs/events.entity.md b/docs/events.entity.md new file mode 100644 index 0000000000..9bf3a260b2 --- /dev/null +++ b/docs/events.entity.md @@ -0,0 +1,124 @@ +# Entity events + +The [`View`, `CollectionView` and `Behavior`](./classes.md) can bind to events that occur on attached models and +collections - this includes both [standard backbone-events](http://backbonejs.org/#Events-catalog) and custom events. + +Event handlers are called with the same arguments as if listening to the entity directly +and called with the context of the view instance. + +### Model Events + +For example, to listen to a model's events: + +```javascript +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + modelEvents: { + 'change:attribute': 'onChangeAttribute' + }, + + onChangeAttribute(model, value) { + console.log('New value: ' + value); + } +}); +``` + +[Live example](https://jsfiddle.net/marionettejs/auvk4hps/) + +The `modelEvents` attribute passes through all the arguments that are passed +to `model.trigger('event', arguments)`. + +The `modelEvents` attribute can also take a +[function returning an object](basics.md#functions-returning-values). + +#### Function Callback + +You can also bind a function callback directly in the `modelEvents` attribute: + +```javascript +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + modelEvents: { + 'change:attribute': () { + console.log('attribute was changed'); + } + } +}); +``` + +[Live example](https://jsfiddle.net/marionettejs/zaxLe6au/) + +### Collection Events + +Collection events work exactly the same way as [`modelEvents`](#model-events) +with their own `collectionEvents` key: + +```javascript +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + collectionEvents: { + sync: 'onSync' + }, + + onSync(collection) { + console.log('Collection was synchronised with the server'); + } +}); +``` + +[Live example](https://jsfiddle.net/marionettejs/7qyfeh9r/) + +The `collectionEvents` attribute can also take a +[function returning an object](basics.md#functions-returning-values). + +Just as in `modelEvents`, you can bind function callbacks directly inside the +`collectionEvents` object: + +```javascript +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + collectionEvents: { + 'update'() { + console.log('the collection was updated'); + } + } +}); +``` + +[Live example](https://jsfiddle.net/marionettejs/ze8po0x5/) + +### Listening to Both + +If your view has a `model` and `collection` attached, it will listen for events +on both: + +```javascript +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + + modelEvents: { + 'change:someattribute': 'onChangeSomeattribute' + }, + + collectionEvents: { + 'update': 'onCollectionUpdate' + }, + + onChangeSomeattribute() { + console.log('someattribute was changed'); + }, + + onCollectionUpdate() { + console.log('models were added or removed in the collection'); + } +}); +``` + +[Live example](https://jsfiddle.net/marionettejs/h9ub5hp3/) + +In this case, Marionette will bind event handlers to both. diff --git a/docs/events.md b/docs/events.md index f7a3f9e2fd..3bea9c3be8 100644 --- a/docs/events.md +++ b/docs/events.md @@ -1,23 +1,20 @@ # Marionette Events The Marionette Event system provides a system for objects to communicate with -each other in a uniform way. In Marionette, this typically involves objects -(models, collections, and views) triggering events that other objects -(typically views) listen to and act on. - -This section will mostly deal with View events and the semantics and methods of -responding to events. - -**This section will not cover events from models and collections. See the -[documentation for View](./marionette.view.md#model-and-collection-events).** +each other in a uniform way. In Marionette, this involves one object triggering +an event that another listens to. This is an extended from of the +[event handling system in Backbone](http://backbonejs.org/#Events), and is +different than [DOM related events](./dom.interactions.md#binding-to-user-input). +It is mixed in to every [Marionette class](./classes.md). ## Documentation Index * [Triggering and Listening to Events](#triggering-and-listening-to-events) - * [View `triggerMethod`](#view-triggermethod) + * [`triggerMethod`](#triggermethod) * [Listening to Events](#listening-to-events) * [`onEvent` Binding](#onevent-binding) * [View events and triggers](#view-events-and-triggers) + * [View entity events](#view-entity-events) * [Child View Events](#child-view-events) * [Event Bubbling](#event-bubbling) * [Using CollectionView](#using-collectionview) @@ -33,31 +30,29 @@ responding to events. ## Triggering and Listening to Events The traditional [event handling system in Backbone](http://backbonejs.org/#Events) -is also supported in Marionette. Marionette, however, provides an alternative -event system using the `triggerMethod` method on `Marionette.Object` - the key -difference between the two is that `triggerMethod` triggers magically named -event handlers on views. This section covers how `triggerMethod` works and how -listeners are set up to handle it. +is fully supported in Marionette. Marionette, however, provides an additional +event API using the `triggerMethod` method - the key difference between the two +is that `triggerMethod` automatically calls specially named event handlers. -### View `triggerMethod` +### `triggerMethod` -The `triggerMethod` method fires the named event on the view - any listeners -will then be triggered on the event. If there are no listeners, this call will -still succeed. All arguments after the first argument will be passed to all -event handlers. +Just like `Backbone`'s [`trigger`](http://backbonejs.org/#Events-trigger) the +`triggerMethod` method fires the named event on the instance - any listeners will then +be triggered on the event. If there are no listeners, this call will still succeed. +All arguments after the first event name string will be passed to all event handlers. ```javascript -var Mn = require('backbone.marionette'); +import { View } from 'backbone.marionette'; -var MyView = Mn.View.extend({ - callMethod: function(myString) { +const MyView = View.extend({ + callMethod(myString) { console.log(myString + ' was passed'); } }); -var myView = new MyView(); +const myView = new MyView(); /* See Backbone.listenTo */ -myView.on('something:happened', myView.callMethod, myView); +myView.on('something:happened', myView.callMethod); /* Calls callMethod('foo'); */ myView.triggerMethod('something:happened', 'foo'); @@ -65,24 +60,22 @@ myView.triggerMethod('something:happened', 'foo'); [Live example](https://jsfiddle.net/marionettejs/whvgao7o/) -**The `triggerMethod` call comes from the `trigger-method` mixin that is also -part of `Marionette.Object` and its subclasses like `Marionette.Application`. -This documentation also applies.** +**The `triggerMethod` method is available to [all Marionette classes](./common.md#triggermethod).** ### Listening to Events Marionette's event triggers work just like regular Backbone events - you can -use `view.on` and `view.listenTo` to act on events: +use `myView.on` and `myObject.listenTo` to act on events: ```javascript -var Mn = require('backbone.marionette'); +import { View } from 'backbone.marionette'; -var MyView = Mn.View.extend({ - initialize: function() { +const MyView = View.extend({ + initialize() { this.on('event:happened', this.logCall); }, - logCall: function(myVal) { + logCall(myVal) { console.log(myVal); } }); @@ -93,23 +86,25 @@ var MyView = Mn.View.extend({ You can also use `listenTo` as in Backbone: ```javascript -var Mn = require('backbone.marionette'); +import { View } from 'backbone.marionette'; -var OtherView = Mn.View.extend({ - initialize: function(someView) { +const OtherView = View.extend({ + initialize(someView) { this.listenTo(someView, 'event:happened', this.logCall); }, - logCall: function(myVal) { + logCall(myVal) { console.log(myVal); } }); -var MyView = Mn.View.extend(); +const MyView = View.extend(); + +const myView = new MyView(); -var myView = new MyView(); +const otherView = new OtherView(myView); -var otherView = new OtherView(myView); +myView.triggerMethod('event:happened', 'someValue'); // Logs 'someValue' ``` [Live examples](https://jsfiddle.net/marionettejs/cm2rczqz/) @@ -123,9 +118,9 @@ core of `onEvent` Binding. #### `onEvent` Binding -The major difference between `Backbone.trigger` and `View.triggerMethod` is -that `triggerMethod` can fire specially named events on the attached view. For -instance, a view that has been rendered will fire `view.triggerMethod('render')` +The major difference between `Backbone.trigger` and `triggerMethod` is +that `triggerMethod` can fire specially named events on the instance. For +example, a view that has been rendered will iternally fire `view.triggerMethod('render')` and call `onRender` - providing a handy way to add behavior to your views. Determining what method an event will call is easy, we will outline this with an @@ -141,34 +136,34 @@ Using this process, `before:dom:refresh` will call the `onBeforeDomRefresh` method. Let's see it in action with a custom event: ```javascript -var Mn = require('backbone.marionette'); +import { View } from 'backbone.marionette'; -var MyView = Mn.View.extend({ - onMyEvent: function(myVal) { +const MyView = View.extend({ + onMyEvent(myVal) { console.log(myVal); } }); -var myView = new MyView(); +const myView = new MyView(); myView.triggerMethod('my:event', 'someValue'); // Logs 'someValue' ``` [Live example](https://jsfiddle.net/marionettejs/oc8wwcnx/) -As before, all arguments passed into `triggerMethod` will make their way into -the event handler. Using this method ensures there will be no unexpected +As before, all arguments passed into `triggerMethod` after the event name will make +their way into the event handler. Using this method ensures there will be no unexpected memory leaks. ### View `events` and `triggers` -Views can automatically bind DOM events to methods and View events with [`events`](./marionette.view.md#view-events) -and [`triggers`](./marionette.view.md#view-triggers) respectively: +Views can automatically bind DOM events to methods and View events with [`events`](./dom.interactions.md#view-events) +and [`triggers`](./dom.interactions.md#view-triggers) respectively: ```javascript -var Mn = require('backbone.marionette'); +import { View } from 'backbone.marionette'; -var MyView = Mn.View.extend({ +const MyView = View.extend({ events: { 'click a': 'showModal' }, @@ -177,11 +172,11 @@ var MyView = Mn.View.extend({ 'keyup input': 'data:entered' }, - showModal: function(event) { + showModal(event) { console.log('Show the modal'); }, - onDataEntered: function(view, event) { + onDataEntered(view, event) { console.log('Data was entered'); } }); @@ -189,19 +184,50 @@ var MyView = Mn.View.extend({ [Live example](https://jsfiddle.net/marionettejs/pq4xfchk/) -For more information, see the [view documentation](./marionette.view.md#binding-to-user-input). +For more information, see the [DOM interactions documentation](./dom.interactions.md#binding-to-user-input). + +### View entity events + +Views can automatically bind to its model or collection with [`modelEvents`](./events.entity.md#model-events) +and [`collectionEvents`](./events.entity.md#collection-events) respectively. + +```javascript +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + modelEvents: { + 'change:someattribute': 'onChangeSomeattribute' + }, + + collectionEvents: { + 'update': 'onCollectionUpdate' + }, + + onChangeSomeattribute() { + console.log('someattribute was changed'); + }, + + onCollectionUpdate() { + console.log('models were added or removed in the collection'); + } +}); +``` + +[Live example](https://jsfiddle.net/marionettejs/h9ub5hp3/) + +For more information, see the [Entity events documentation](./events.entity.md). ## Child View Events The [`View`](marionette.view.md) and [`CollectionView`](marionette.collectionview.md) -are able to monitor and act on events on any children they own. Any events fired +are able to monitor and act on events on any of their direct children. Any events fired on a view are automatically propagated to their direct parents as well. Let's see a quick example: ```javascript -var Mn = require('backbone.marionette'); +import { View, CollectionView } from 'backbone.marionette'; -var Item = Mn.View.extend({ +const Item = View.extend({ tagName: 'li', triggers: { @@ -209,14 +235,14 @@ var Item = Mn.View.extend({ } }); -var Collection = Mn.CollectionView.extend({ +const Collection = CollectionView.extend({ tagName: 'ul', childViewEvents: { 'select:item': 'itemSelected' }, - itemSelected: function(childView) { + itemSelected(childView) { console.log('item selected: ' + childView.model.id); } }); @@ -232,45 +258,45 @@ methods bound to the `childViewEvents` attribute. This works for built-in events, custom events fired with `triggerMethod` and bound events using `triggers`. -**Note**: Automatic event bubbling can be disabled by setting +**NOTE** Automatic event bubbling can be disabled by setting [`childViewEventPrefix`](#a-child-views-event-prefix) to `false`. When using implicit listeners, the [`childview:*` event prefix](#a-child-views-event-prefix) is used which needs to be included as part of the handler: ```javascript -var Mn = require('backbone.marionette'); +import { View, } from 'backbone.marionette'; -var MyView = Mn.View.extend({ +const MyView = View.extend({ triggers: { click: 'click:view' }, - doSomething: function() { + doSomething() { this.triggerMethod('did:something', this); } }); -var ParentView = Mn.View.extend({ +const ParentView = View.extend({ regions: { foo: '.foo-hook' }, - onRender: function() { + onRender() { this.showChildView('foo', new MyView()); }, - onChildviewClickView: function(childView) { + onChildviewClickView(childView) { console.log('View clicked ' + childView); }, - onChildviewDidSomething: function(childView) { + onChildviewDidSomething(childView) { console.log('Something was done to ' + childView); } }) ``` -**Note**: `triggers` will automatically pass the child view as an argument to the parent view, however `triggerMethod` will not, and so notice that in the above example, the `triggerMethod` explicitly passes the child view. +**NOTE** `triggers` will automatically pass the child view as an argument to the parent view, however `triggerMethod` will not, and so notice that in the above example, the `triggerMethod` explicitly passes the child view. [Live example](https://jsfiddle.net/marionettejs/oquea4uy/) @@ -279,16 +305,16 @@ var ParentView = Mn.View.extend({ This works exactly the same way for the `CollectionView` and its `childView`: ```javascript -var Mn = require('backbone.marionette'); +import { View, CollectionView } from 'backbone.marionette'; -var MyChild = Mn.View.extend({ +const MyChild = View.extend({ triggers: { click: 'click:child' } }); -var MyList = Mn.CollectionView.extend({ - onChildviewClickChild: function(childView) { +const MyList = CollectionView.extend({ + onChildviewClickChild(childView) { console.log('Childview ' + childView + ' was clicked'); } }); @@ -307,16 +333,18 @@ The default value for `childViewEventPrefix` is `childview`. Setting this proper `false` will disable [automatic event bubbling](#event-bubbling). ```javascript -var Bb = require('backbone'); -var Mn = require('backbone.marionette'); +import Backbone from 'backbone'; +import { CollectionView } from 'backbone.marionette'; +import MyChildView from './my-child-view'; -var myCollection = new Bb.Collection([{}]); +const myCollection = new Backbone.Collection([{}]); -var CollectionView = Mn.CollectionView.extend({ - childViewEventPrefix: 'some:prefix' +const CollectionView = CollectionView.extend({ + childViewEventPrefix: 'some:prefix', + childView: MyChildView }); -var collectionView = new CollectionView({ +const collectionView = new CollectionView({ collection: myCollection }); @@ -340,15 +368,15 @@ fired on child views - _without the `childview:` prefix_ - and calls the method referenced or attached function. ```javascript -var Mn = require('backbone.marionette'); +import { View } from 'backbone.marionette'; -var MyView = Mn.View.extend({ +const MyView = View.extend({ triggers: { click: 'view:clicked' } }); -var ParentView = Mn.View.extend({ +const ParentView = View.extend({ regions: { foo: '.foo-hook' }, @@ -357,11 +385,11 @@ var ParentView = Mn.View.extend({ 'view:clicked': 'displayMessage' }, - onRender: function() { + onRender() { this.showChildView('foo', new MyView()); }, - displayMessage: function(childView) { + displayMessage(childView) { console.log('Displaying message for ' + childView); } }); @@ -375,26 +403,26 @@ The `childViewEvents` attribute can also attach functions directly to be event handlers: ```javascript -var Mn = require('backbone.marionette'); +import { View } from 'backbone.marionette'; -var MyView = Mn.View.extend({ +const MyView = View.extend({ triggers: { click: 'view:clicked' } }); -var ParentView = Mn.View.extend({ +const ParentView = View.extend({ regions: { foo: '.foo-hook' }, childViewEvents: { - 'view:clicked': function(childView) { + 'view:clicked'(childView) { console.log('Function called for ' + childView); } }, - onRender: function() { + onRender() { this.showChildView('foo', new MyView()); } }); @@ -405,14 +433,13 @@ var ParentView = Mn.View.extend({ #### Using `CollectionView`'s `childViewEvents` ```javascript -var Mn = require('backbone.marionette'); +import { CollectionView } from 'backbone.marionette'; // childViewEvents can be specified as a hash... -var MyCollectionView = Mn.CollectionView.extend({ - +const MyCollectionView = CollectionView.extend({ childViewEvents: { // This callback will be called whenever a child is rendered or emits a `render` event - render: function() { + render() { console.log('A child view has been rendered.'); } } @@ -427,11 +454,13 @@ A `childViewTriggers` hash or method permits proxying of child view events witho setting bindings. The values of the hash should be a string of the event to trigger on the parent. `childViewTriggers` is sugar on top of [`childViewEvents`](#explicit-event-listeners) much -in the same way that [View `triggers`](./marionette.view.md#view-triggers) are sugar for [View `events`](./marionette.view.md#view-events). +in the same way that [view `triggers`](./dom.interaction.md#view-triggers) are sugar for [view `events`](./dom.interactions.md#view-events). ```javascript +import { View, CollectionView } from 'backbone.marionette'; + // The child view fires a custom event, `show:message` -var ChildView = Marionette.View.extend({ +const ChildView = View.extend({ // Events hash defines local event handlers that in turn may call `triggerMethod`. events: { @@ -442,7 +471,7 @@ var ChildView = Marionette.View.extend({ 'submit form': 'submit:form' }, - onClickButton: function () { + onClickButton () { // Both `trigger` and `triggerMethod` events will be caught by parent. this.trigger('show:message', 'foo'); this.triggerMethod('show:message', 'bar'); @@ -450,7 +479,7 @@ var ChildView = Marionette.View.extend({ }); // The parent uses childViewEvents to catch the child view's custom event -var ParentView = Marionette.CollectionView.extend({ +const ParentView = CollectionView.extend({ childView: ChildView, childViewTriggers: { @@ -458,21 +487,21 @@ var ParentView = Marionette.CollectionView.extend({ 'submit:form': 'child:submit:form' }, - onChildShowMessage: function (message) { + onChildShowMessage (message) { console.log('A child view fired show:message with ' + message); }, - onChildSubmitForm: function (childView) { + onChildSubmitForm (childView) { console.log('A child view fired submit:form'); } }); -var GrandParentView = Marionette.View.extend({ +const GrandParentView = View.extend({ regions: { list: '.list' }, - onRender: function() { + onRender() { this.showChildView('list', new ParentView({ collection: this.collection })); @@ -482,7 +511,7 @@ var GrandParentView = Marionette.View.extend({ 'child:show:message': 'showMessage' }, - showMessage: function(childView) { + showMessage(childView) { console.log('A child (' + childView + ') fired an event'); } }); @@ -493,10 +522,10 @@ var GrandParentView = Marionette.View.extend({ #### Using `CollectionView`'s `childViewTriggers` ```javascript -var Mn = require('backbone.marionette'); +import { View, CollectionView } from 'backbone.marionette'; // The child view fires a custom event, `show:message` -var ChildView = Mn.View.extend({ +const ChildView = View.extend({ // Events hash defines local event handlers that in turn may call `triggerMethod`. events: { @@ -509,7 +538,7 @@ var ChildView = Mn.View.extend({ 'submit form': 'submit:form' }, - onClickButton: function () { + onClickButton () { // Both `trigger` and `triggerMethod` events will be caught by parent. this.trigger('show:message', 'foo'); this.triggerMethod('show:message', 'bar'); @@ -517,7 +546,7 @@ var ChildView = Mn.View.extend({ }); // The parent uses childViewEvents to catch the child view's custom event -var ParentView = Mn.CollectionView.extend({ +const ParentView = CollectionView.extend({ childView: ChildView, @@ -526,11 +555,11 @@ var ParentView = Mn.CollectionView.extend({ 'submit:form': 'child:submit:form' }, - onChildShowMessage: function (message) { + onChildShowMessage (message) { console.log('A child view fired show:message with ' + message); }, - onChildSubmitForm: function (childView) { + onChildSubmitForm (childView) { console.log('A child view fired submit:form'); } }); @@ -542,4 +571,4 @@ var ParentView = Mn.CollectionView.extend({ Marionette Views fire events during their creation and destruction lifecycle. For more information see the documentation covering the -[`View` Lifecycle](./viewlifecycle.md). +[`View` Lifecycle](./view.lifecycle.md). diff --git a/docs/features.md b/docs/features.md index 01a7269831..7ccb9453dd 100644 --- a/docs/features.md +++ b/docs/features.md @@ -56,14 +56,14 @@ disabling [automatic event bubbling](./events.md#event-bubbling). *Default:* `true` -It indicates the whether or not [`View.triggers` will call `event.preventDefault()`](./marionette.view.md#view-triggers-event-object) if not explicitly defined by the trigger. +It indicates the whether or not [`View.triggers` will call `event.preventDefault()`](./dom.interactions.md#view-triggers-event-object) if not explicitly defined by the trigger. The default has been true, but for a future version [`false` is being considered](https://github.com/marionettejs/backbone.marionette/issues/2926). ### `triggersStopPropagating` *Default:* `true` -It indicates the whether or not [`View.triggers` will call `event.stopPropagating()`](./marionette.view.md#view-triggers-event-object) if not explicitly defined by the trigger. +It indicates the whether or not [`View.triggers` will call `event.stopPropagating()`](./dom.interactions.md#view-triggers-event-object) if not explicitly defined by the trigger. The default has been true, but for a future version [`false` is being considered](https://github.com/marionettejs/backbone.marionette/issues/2926). ### DEV_MODE diff --git a/docs/installation.md b/docs/installation.md index 50f3f27558..0356930b9d 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -67,4 +67,7 @@ we prepared simple marionettejs skeleton with Browserify. ## Getting Started After installing Marionette you might want to check out the basics. + [Continue Reading...](./basics.md). + +Additionally check out [features](./features.md) for some configurable options. diff --git a/docs/marionette.application.md b/docs/marionette.application.md index 77a5befbb6..e10e2226bd 100644 --- a/docs/marionette.application.md +++ b/docs/marionette.application.md @@ -5,10 +5,11 @@ and a view tree. `Application` includes: - [Common Marionette Functionality](./common.md) +- [Class Events](./events.class.md#application-events) - [Radio API](./backbone.radio.md#marionette-integration) - [MnObject's API](./marionette.mnobject.md) -In addition to `MnObject`'s API Application provides two significant additions. +In addition to `MnObject`'s API, Application provides two significant additions. A simple lifecycle hook with [`start`](#starting-an-application) and a [single region](#application-region) for attaching a view tree. @@ -16,78 +17,29 @@ One additional difference is the `Application` [`cidPrefix`](./marionette.mnobje ## Documentation Index -* [Application Events](#application-events) +* [Instantiating An Application](#instantiating-an-application) * [Starting An Application](#starting-an-application) * [Application Region](#application-region) * [Application Region Methods](#application-region-methods) -## Application Events +## Instantiating an Application -The `Application` object will fire two events: - -### `before:start` - -Fired just before the application is started. Use this to prepare the -application with anything it will need to start, for example instantiating -routers, models, and collections. - -### `start` - -Fired as part of the application startup. This is where you should be showing -your views and starting `Backbone.history`. - -### Application Lifecycle - -When `Application` was initialized and `start` method was called -a set of events will be called in a specific order. - -| Order | Event | -| :---: |-----------------| -| 1 | `before:start` | -| 2 | `start` | +When instantiating a `Application` there are several properties, if passed, +that will be attached directly to the instance: +`channelName`, `radioEvents`, `radioRequests`, `region`, `regionClass` ```javascript -import Bb from 'backbone'; import { Application } from 'backbone.marionette'; -import MyModel from './mymodel'; -import MyView from './myview'; - -const MyApp = Application.extend({ - region: '#root-element', - - initialize: function(options) { - console.log('Initialize'); - }, - - onBeforeStart: function() { - this.model = new MyModel(this.getOption('data')); - }, - - onStart: function() { - this.showView(new MyView({model: this.model})); - Bb.history.start(); - } -}); - -const myApp = new MyApp(options); -myApp.start(); +const myApplication = new Application({ ... }); ``` -[Live example](https://jsfiddle.net/marionettejs/ny59rs7b/) - -As we'll see below, the `options` object is passed into the Application as an -argument to `start`. - ## Starting An Application Once you have your application configured, you can kick everything off by calling: `myApp.start(options)`. -This function takes a single optional parameter. This parameter will be passed -to each of your initializer functions, as well as the initialize events. This -allows you to provide extra configuration for various parts of your app throughout the -initialization sequence. +This function takes a single optional argument to pass along to the events. ```javascript import Bb from 'backbone'; @@ -96,15 +48,15 @@ import { Application } from 'backbone.marionette'; const MyApp = Application.extend({ region: '#root-element', - initialize: function(options) { + initialize(options) { console.log('Initialize'); }, - onBeforeStart: function(app, options) { + onBeforeStart(app, options) { this.model = new MyModel(options.data); }, - onStart: function(app, options) { + onStart(app, options) { this.showView(new MyView({model: this.model})); Bb.history.start(); } @@ -124,7 +76,7 @@ myApp.start({ ## Application Region -An `Application` provides a single region for attaching a view tree. +An `Application` provides a single [region](./marionette.region.md) for attaching a view tree. The `region` property can be [defined in multiple ways](./marionette.region.md#defining-regions) ```javascript @@ -134,7 +86,7 @@ import RootView from './views/root'; const MyApp = Application.extend({ region: '#root-element', - onStart: function() { + onStart() { this.showView(new RootView()); } }); @@ -182,9 +134,9 @@ The Marionette Application provides helper methods for managing its attached reg Return the attached [region object](./marionette.region.md) for the Application. -### `showView(View)` +### `showView(view)` -Display `View` in the region attached to the Application. This runs the [`View lifecycle`](./viewlifecycle.md). +Display a `View` instance in the region attached to the Application. This runs the [`View lifecycle`](./view.lifecycle.md). ### `getView()` diff --git a/docs/marionette.behavior.md b/docs/marionette.behavior.md index a897c759ac..b1226d70f0 100644 --- a/docs/marionette.behavior.md +++ b/docs/marionette.behavior.md @@ -3,32 +3,49 @@ A `Behavior` provides a clean separation of concerns to your view logic, allowing you to share common user-facing operations between your views. -Behaviors are particularly good at factoring out the common user, model and -collection interactions to be utilized across your application. +`Behavior` includes: +- [Common Marionette Functionality](./common.md) +- [Class Events](./events.class.md#behavior-events) +- [DOM Interactions](./dom.interactions.md) +- [Entity Events](./events.entity.md) + +`Behavior`s are particularly good at factoring out the common user, model and +collection interactions to be utilized across your application. Unlike the other +Marionette classes, `Behavior`s are not meant to be instantiated directly. +Instead a `Behavior` should be instantiated by the view it is related to by +[attaching the a behavior class definition to the view](#using-behaviors). ## Documentation Index +* [Instantiating a Behavior](#instantiating-a-behavior) * [Using Behaviors](#using-behaviors) * [Defining and Attaching Behaviors](#defining-and-attaching-behaviors) * [Behavior Options](#behavior-options) * [Nesting Behaviors](#nesting-behaviors) +* [The Behavior's `view`](#the-behaviors-view) * [View Proxy](#view-proxy) * [Listening to View Events](#listening-to-view-events) * [Proxy Handlers](#proxy-handlers) * [Events / Initialize Order](#events--initialize-order) * [Using `ui`](#using-ui) - * [View and el](#view-and-el) + * [View DOM proxies](#view-dom-proxies) +* [Destroying a Behavior](#destroying-a-behavior) +## Instantiating a Behavior + +Unlike other [Marionette classes](./classes.md), `Behavior`s are not meant to +be instantiated except by a view. + ## Using Behaviors The easiest way to see how to use the `Behavior` class is to take an example view and factor out common behavior to be shared across other views. ```javascript -var Mn = require('backbone.marionette'); +import { View } from 'backbone.marionette'; -var MyView = Mn.View.extend({ +const MyView = View.extend({ ui: { destroy: '.destroy-btn' }, @@ -37,12 +54,12 @@ var MyView = Mn.View.extend({ 'click @ui.destroy': 'warnBeforeDestroy' }, - warnBeforeDestroy: function() { + warnBeforeDestroy() { alert('You are about to destroy all your data!'); this.destroy(); }, - onRender: function() { + onRender() { this.ui.destroy.tooltip({ text: 'What a nice mouse you have.' }); @@ -59,15 +76,15 @@ to be extracted into `Behavior` classes. ### Defining and Attaching Behaviors ```javascript -var Mn = require('backbone.marionette'); +import { Behavior, View } from 'backbone.marionette'; -var DestroyWarn = Mn.Behavior.extend({ +const DestroyWarn = Behavior.extend({ // You can set default options - // just like you can in your Backbone Models. // They will be overridden if you pass in an option with the same key. options: { message: 'You are destroying!' }, + ui: { destroy: '.destroy-btn' }, @@ -77,8 +94,8 @@ var DestroyWarn = Mn.Behavior.extend({ 'click @ui.destroy': 'warnBeforeDestroy' }, - warnBeforeDestroy: function() { - var message = this.getOption('message'); + warnBeforeDestroy() { + const message = this.getOption('message'); window.alert(message); // Every Behavior has a hook into the // view that it is attached to. @@ -86,28 +103,23 @@ var DestroyWarn = Mn.Behavior.extend({ } }); -var ToolTip = Mn.Behavior.extend({ +const ToolTip = Behavior.extend({ options: { - text: '' + text: 'Tooltip text' }, + ui: { tooltip: '.tooltip' }, - onRender: function() { + onRender() { this.ui.tooltip.tooltip({ text: this.getOption('text') }); } }); -``` - -We've passed in a `options` attribute that sets default options. -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ +const MyView = View.extend({ behaviors: [DestroyWarn, ToolTip] }); ``` @@ -119,9 +131,7 @@ event handlers were attached to the view directly. In addition to using array notation, Behaviors can be attached using an object: ```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ +const MyView = View.extend({ behaviors: { destroy: DestroyWarn, tooltip: ToolTip @@ -137,9 +147,7 @@ should do. In our above example, we want to override the message to our `DestroyWarn` and `Tooltip` behaviors to match the original message on the View: ```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ +const MyView = View.extend({ behaviors: [ { behaviorClass: DestroyWarn, @@ -155,85 +163,46 @@ var MyView = Mn.View.extend({ [Live example](https://jsfiddle.net/marionettejs/vq9k3c69/) +There are several properties, if passed, that will be attached directly to the instance: +`collectionEvents`, `events`, `modelEvents`, `triggers`, `ui` + Using an object, we must define the `behaviorClass` attribute to refer to our behaviors and then add any extra options with keys matching the option we want to override. Any passed options will override the values from `options` property. -Here is the syntax for declaring which behaviors get used within a View. -* You can pass behaviors either as a set of key-value pairs where the keys are used to lookup the behavior class, or as an array. -* The options for each `Behavior` are also passed through to the `Behavior` during initialization. -* The options are then stored within each `Behavior` under `options`. - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - ui: { - destroy: '.destroy-btn' - }, +**Errors** An error will be thrown if the `Behavior` class is not passed. - behaviors: { - DestroyWarn: { - message: 'you are destroying all your data is now gone!' - }, - ToolTip: { - text: 'what a nice mouse you have' - } - } -}); -``` +## Nesting Behaviors -Now let's create the `DestroyWarn` `Behavior`. +In addition to extending a `View` with `Behavior`, a `Behavior` can itself use +other Behaviors. The syntax is identical to that used for a `View`: ```javascript -var Mn = require('backbone.marionette'); +import { Behavior } from 'backbone.marionette'; -var DestroyWarn = Mn.Behavior.extend({ - // You can set default options - // just like you can in your Backbone Models. - // They will be overridden if you pass in an option with the same key. - options: { - message: 'You are destroying!' - }, - - // Behaviors have events that are bound to the views DOM. - events: { - 'click @ui.destroy': 'warnBeforeDestroy' - }, - - warnBeforeDestroy: function() { - alert(this.options.message); - // Every Behavior has a hook into the - // view that it is attached to. - this.view.destroy(); - } +const Modal = Behavior.extend({ + behaviors: [ + { + behaviorClass: DestroyWarn, + message: 'Whoa! You sure about this?' + } + ] }); ``` -And onto the `Tooltip` behavior. - -```javascript -var Mn = require('backbone.marionette'); - -var ToolTip = Mn.Behavior.extend({ - ui: { - tooltip: '.tooltip' - }, +[Live example](https://jsfiddle.net/marionettejs/7ffnqff3/) - onRender: function() { - this.ui.tooltip.tooltip({ - text: this.options.text - }); - } -}); -``` +Nested Behaviors act as if they were direct Behaviors of the parent `Behavior`'s +view instance. -### view +## The Behavior's `view` The `view` is a reference to the `View` instance that the `Behavior` is attached to. ```javascript -Marionette.Behavior.extend({ - handleDestroyClick: function() { +import { Behavior } from 'backbone.marionette'; + +Behavior.extend({ + handleDestroyClick() { this.view.destroy(); } }); @@ -241,34 +210,12 @@ Marionette.Behavior.extend({ [Live example](https://jsfiddle.net/marionettejs/p8vymo4j/) -## Nesting Behaviors - -In addition to extending a `View` with `Behavior`, a `Behavior` can itself use -other Behaviors. The syntax is identical to that used for a `View`: - -```javascript -var Mn = require('backbone.marionette'); - -var Modal = Mn.Behavior.extend({ - behaviors: { - DestroyWarn: { - message: 'Whoa! You sure about this?' - } - } -}); -``` - -[Live example](https://jsfiddle.net/marionettejs/7ffnqff3/) - -Nested Behaviors act as if they were direct Behaviors of the parent `Behavior`'s -view instance. - ## View Proxy The `Behavior` class provides proxies for a selection of `View` functionality. -This includes listening to events on the view, being able to handle events on -models and collections, and being able to directly interact with the attached -template. +This includes [listening to events on the view](), being able to [handle events on +models and collections](), and being able to directly [interact with the attached +template](). ### Listening to View Events @@ -283,6 +230,7 @@ triggered on a `View` are passed to all attached `behaviors`. This includes: These handlers work exactly as they do on `View` - [see the `View` documentation](./marionette.view.md#events) +> Be default all events triggered on the behavior come from the view or the view's entities. > Events triggered in the behavior instance are not executed in the view. To notify > the view, the behavior must trigger an event in its view property, e.g, `this.view.trigger('my:event')` @@ -291,10 +239,41 @@ These handlers work exactly as they do on `View` - Behaviors provide proxies to a number of the view event handling attributes including: -* [`events`](./marionette.view.md#view-events) -* [`triggers`](./marionette.view.md#view-triggers) -* [`modelEvents`](./marionette.view.md#model-events) -* [`collectionEvents`](./marionette.view.md#collection-events) +* [`events`](./dom.interactions.md#view-events) +* [`triggers`](./dom.interactions.md#view-triggers) +* [`modelEvents`](./events.entity.md#model-events) +* [`collectionEvents`](./events.entity.md#collection-events) + +```javascript +import { Behavior } from 'backbone.marionette'; + +Behavior.extend({ + events: { + 'click .foo-button': 'onClickFooButton' + }, + triggers: { + 'click .bar-button': 'click:barButton' + }, + modelEvents: { + 'change': 'onChangeModel' + }, + collectionEvents: { + 'change': 'onChangeCollection' + }, + onClickFooButton(evt) { + // .. + }, + onClickBarButton(view, evt) { + // .. + }, + onChangeModel(model, opts) { + // .. + }, + onChangeCollection(model, opts) { + // .. + } +}); +``` ### Events / Initialize Order @@ -318,13 +297,13 @@ The `initialize` event is triggered on the behavior indicating that the view is #### Using `ui` As in views, `events` and `triggers` can use the `ui` references in their -listeners. For more details, see the [`ui` documentation for views](./marionette.view.md#organizing-your-view). +listeners. For more details, see the [`ui` documentation](./dom.interactions.md#organizing-your-view). These can be defined on either the Behavior or the View: ```javascript -var Mn = require('backbone.marionette'); +import { Behavior } from 'backbone.marionette'; -var MyBehavior = Mn.Behavior.extend({ +const MyBehavior = Behavior.extend({ ui: { saveForm: '.btn-save' }, @@ -337,11 +316,11 @@ var MyBehavior = Mn.Behavior.extend({ invalid: 'showError' }, - saveForm: function() { + saveForm() { this.view.model.save(); }, - showError: function() { + showError() { alert('You have errors'); } }); @@ -349,29 +328,29 @@ var MyBehavior = Mn.Behavior.extend({ [Live example](https://jsfiddle.net/marionettejs/6b8o3pmz/) -If your `ui` keys clash with keys on the attached view, references within the -behavior will always use the definition on the behavior itself. As views are -only peripherally aware of their behaviors, their `ui` keys will not be changed -when accessed within the `View`. For example: +If your `ui` keys clash with keys on the attached view, the view's `ui` +declarations will take precidence over the behavior's `ui`. +This allows for behaviors to be more easily reused without dictating +necessary structures within the view itself. ```javascript -var Mn = require('backbone.marionette'); +import { Behavior, View } from 'backbone.marionette'; -var MyBehavior = Mn.Behavior.extend({ +const MyBehavior = Behavior.extend({ ui: { saveForm: '.btn-save' }, events: { - 'click @ui.saveForm': 'saveForm' // .btn-save + 'click @ui.saveForm': 'saveForm' // .btn-primary when used with `FirstView` }, - saveForm: function() { + saveForm() { this.view.model.save(); } }); -var FirstView = Mn.View.extend({ +const FirstView = View.extend({ behaviors: [MyBehavior], ui: { @@ -382,15 +361,13 @@ var FirstView = Mn.View.extend({ 'click @ui.saveForm': 'checkForm' // .btn-primary }, - checkForm: function() { + checkForm() { // ... } }); ``` -[Live example](https://jsfiddle.net/marionettejs/xoy56gpv/) - -### View and el +### View DOM proxies The `Behavior` has a number of proxies attributes that directly refer to the related attribute on a view: @@ -403,18 +380,24 @@ In addition, each behavior is able to reference the view they are attached to through the `view` attribute: ```javascript -var Mn = require('backbone.marionette'); +import { Behavior } from 'backbone.marionette'; -var ViewBehavior = Mn.Behavior.extend({ - onRender: function() { - if (this.view.model.get('selected')) { - this.$el.addClass('highlight'); - } - else { - this.$el.removeClass('highlight'); - } +const ViewBehavior = Behavior.extend({ + onRender() { + const shouldHighlight = this.view.model.get('selected'); + this.$el.toggleClass('highlight', shouldHighlight); + this.$('.view-class').addClass('highlighted-icon'); } }); ``` [Live example](https://jsfiddle.net/marionettejs/8dmk30Lq/) + +**Note** in rare cases when a view's `el` is modified via `setElement` if utilizing +these proxies they will need to be manually updated by calling +`myBehavior.proxyViewProperties();` + +## Destroying a Behavior + +`myBehavior.destroy()` will call `stopListening` on the behavior instance, and it will +remove the behavior from the view. diff --git a/docs/marionette.behaviors.md b/docs/marionette.behaviors.md deleted file mode 100644 index 905646cee8..0000000000 --- a/docs/marionette.behaviors.md +++ /dev/null @@ -1,74 +0,0 @@ -# Marionette.Behaviors - -**_DEPRECATED: The `behaviorsLookup` is deprecated pending removal. See the -[documentation for Behavior](./marionette.behavior.md) to learn how to map -behaviors to views in Marionette 3.** - -'Marionette.Behaviors' is a utility class that takes care of gluing your `Behavior` instances to their given `View`. -The most important thing to understand when using this class is that you **MUST** override the class level `behaviorsLookup` method or set the option `behaviorClass` for things to work properly. - -## Documentation Index -* [API](#api) - * [Behaviors Lookup](#behaviorslookup) - * [getBehaviorClass](#getbehaviorclass) - * [behaviorClass](#behaviorclass) - -## API - -There are two class level methods that you can override on the `Behaviors` class. The rest of the class is tied to under the hood implementation details of Views. - -### behaviorsLookup - -This method defines where your Behavior classes are stored. A simple implementation might look something like this. - -```javascript -Marionette.Behaviors.behaviorsLookup = function() { - return window.Behaviors; -} -``` - -There are 2 different syntaxes for attaching Behaviors to a View. The first is an object syntax where the Behaviors are looked up by their key value in a given View's behavior hash. The second is an array syntax where you can pass the Behavior class directly. - -In this sample, which uses the object syntax, your code will expect the following Behaviors to be present in `window.Behaviors.DestroyWarn` and `window.Behaviors.ToolTip` - -```javascript -var MyView = Marionette.View.extend({ - behaviors: { - ToolTip: {}, - DestroyWarn: { - message: "you are destroying all your data is now gone!" - } - } -}); -``` - -If you use a module loader like [requirejs](http://requirejs.org/) or [browserify](http://browserify.org/) you can use the array based syntax, where you pass in a Behavior Class directly or include it as a behaviorClass property on your options object. - -```javascript -var Tooltip = require('behaviors/tooltip'); -var DestroyWarn = require('behaviors/destroy-warn'); - -var MyView = Marionette.View.extend({ - behaviors: [Tooltip, { - behaviorClass: DestroyWarn, - message: "you are destroying all your data is now gone!" - }] -}); -``` - -### getBehaviorClass - -This method has a default implementation that is simple to override. It is responsible for the lookup of single Behavior when given an options object and a key, and is used for both the array and object based notations. - -```javascript -getBehaviorClass: function(options, key) { - if (options.behaviorClass) { - return options.behaviorClass; - //treat functions as a Behavior constructor - } else if(_.isFunction(options)) { - return options; - } - // behaviorsLookup can be either a flat object or a method - return Marionette._getValue(Behaviors.behaviorsLookup, this, [options, key])[key]; -} -``` diff --git a/docs/marionette.collectionview.md b/docs/marionette.collectionview.md index f86b9461c9..1348bd1b9b 100644 --- a/docs/marionette.collectionview.md +++ b/docs/marionette.collectionview.md @@ -1,69 +1,307 @@ # Marionette.CollectionView -The `CollectionView` will loop through all of the models in the specified collection, -instantiating a view for each of them using a specified `childView`, and adding them to the `children`. -It will then sort the `children` by the `viewComparator` and filter them by the `viewFilter`. -The `el` of the child views that pass the filter will be rendered and appended to -the collection view's `el`. By default the `CollectionView` will maintain a -sorted collection's order in the DOM. This behavior can be disabled by specifying `{sortWithCollection: false}` on initialize. - -`CollectionView` has the base functionality provided by the View Mixin. +A `CollectionView` like `View` manages a portion of the DOM via a single parent DOM element +or `el`. This view manages an ordered set of child views that are shown within the view's `el`. +These children are most often created to match the models of a `Backbone.Collection` though a +`CollectionView` does not require a `collection` and can manage any set of views. + +`CollectionView` includes: +- [The DOM API](./dom.api.md) +- [Class Events](./events.class.md#collectionview-events) +- [DOM Interactions](./dom.interactions.md) +- [Child Event Bubbling](./events.md#event-bubbling) +- [Entity Events](./events.entity.md) +- [View Rendering](./view.rendering.md) +- [Prerendered Content](./dom.prerendered.md) +- [View Lifecycle](./view.lifecycle.md) + +A `CollectionView` can have [`Behavior`s](./marionette.behavior.md). ## Documentation Index +* [Instantiating a CollectionView](#instantiating-a-collectionview) +* [Rendering a CollectionView](#rendering-a-collectionview) + * [Rendering a Template](#rendering-a-template) + * [Defining the `childViewContainer`](#defining-the-childviewcontainer) + * [Re-rendering the CollectionView](#re-rendering-the-collectionview) +* [View Lifecycle and Events](#view-lifecycle-and-events) +* [Entity Events](#entity-events) +* [DOM Interactions](#dom-interactions) +* [Behaviors](#behaviors) +* [Managing Children](#managing-children) + * [Attaching `children` within the `el`](#attaching-children-within-the-el) + * [Destroying All `children`](#destroying-all-children) * [CollectionView's `childView`](#collectionviews-childview) - * [CollectionView's `childViewOptions`](#collectionviews-childviewoptions) + * [Building the `children`](#building-the-children) + * [Passing Data to the `childView`](#passing-data-to-the-childview) * [CollectionView's `emptyView`](#collectionviews-emptyview) * [CollectionView's `getEmptyRegion`](#collectionviews-getemptyregion) - * [CollectionView's `emptyViewOptions`](#collectionviews-emptyviewoptions) - * [CollectionView's `isEmpty`](#collectionviews-isempty) -* [CollectionView's `render`](#collectionviews-render) - * [Automatic Rendering](#automatic-rendering) - * [Re-render the CollectionView](#re-render-the-collectionview) - * [CollectionView's `attachHtml`](#collectionviews-attachhtml) -* [CollectionView's `destroy`](#collectionviews-destroy) -* [Events](#events) - * [Child Event Bubbling](#child-event-bubbling) - * [Lifecycle Events](#lifecycle-events) -* [Advanced CollectionView Usage](#advanced-collectionview-usage) - * [Managing Children](#managing-children) - * [Filtering](#filtering) - * [Sorting](#sorting) + * [Passing Data to the `emptyView`](#passing-data-to-the-emptyview) + * [Defining When an `emptyView` shows](#defining-when-an-emptyview-shows) +* [Accessing a Child View](#accessing-a-child-view) + * [CollectionView `children` Iterators And Collection Functions](collectionview-children-iterators-and-collection-functions) +* [Listening to Events on the `children`](#listening-to-events-on-the-children) +* [Self Managed `children`](#self-managed-children) + * [Adding a Child View](#adding-a-child-view) + * [Removing a Child View](#removing-a-child-view) + * [Detaching a Child View](#detaching-a-child-view) + * [Swapping Child Views](#swapping-child-views) +* [Sorting the `children`](#sorting-the-children) + * [Defining the `viewComparator`](#defining-the-viewcomparator) + * [Maintaining the `collection`'s sort](#maintaining-the-collections-sort) +* [Filtering the `children`](#filtering-the-children) + * [Defining the `viewFilter`](#defining-the-viewfilter) + +## Instantiating a CollectionView + +When instantiating a `CollectionView` there are several properties, if passed, +that will be attached directly to the instance: +`attributes`, `behaviors`, `childView`, `childViewContainer`, `childViewEventPrefix`, +`childViewEvents`, `childViewOptions`, `childViewTriggers`, `className`, `collection`, +`collectionEvents`, `el`, `emptyView`, `emptyViewOptions`, `events`, `id`, `model`, +`modelEvents`, `sortWithCollection`, `tagName`, `template`, `templateContext`, +`triggers`, `ui`, `viewComparator`, `viewFilter` +```javascript +import { CollectionView } from 'backbone.marionette'; -## CollectionView's `childView` +const myCollectionView = new CollectionView({ ... }); +``` -Specify a `childView` in your collection view definition. This must be -a Backbone view class definition, not an instance. It can be any -`Backbone.View` or be derived from `Marionette.View`. +Some of these properties come from Marionette, but many are inherited from +[`Backbone.View`](http://backbonejs.org/#View-constructor). + +## Rendering a CollectionView + +The `render` method of the `CollectionView` is primarily responsible +for rendering the entire collection. It loops through each of the +children in the collection and renders them individually as a +`childView`. ```javascript -var Mn = require('backbone.marionette'); +import { CollectionView } from 'backbone.marionette'; -var MyChildView = Mn.View.extend({}); +const MyCollectionView = CollectionView.extend({...}); -Mn.CollectionView.extend({ - childView: MyChildView +// all of the children views will now be rendered. +new MyCollectionView().render(); +``` + +### Rendering a Template + +In addition to rendering children, the `CollectionView` may have a +`template`. The child views can be rendered within a DOM element of +this template. The `CollectionView` will serialize either the `model` +or `collection` along with context for the `template` to render. + +For more detail on how to render templates, see +[View Template Rendering](./view.rendering.md). + +### Defining the `childViewContainer` + +By default the `CollectionView` will render the children into the `el` +of the `CollectionView`. If you are rendering a template you will want +to set the `childViewContainer` to be a selector for an element within +the template for child view attachment. + +```javascript +import { CollectionView } from 'backbone.marionette'; + +const MyCollectionView = CollectionView.extend({ + childViewContainer: '.js-widgets', + template: _.template('

Widgets

    ') +}); +``` + +**Errors** An error will throw if the childViewContainer can not be found. + +### Re-rendering the CollectionView + +If you need to re-render the entire collection or the template, you can call the +`collectionView.render` method. This method will destroying all of +the child views that may have previously been added. + +## View Lifecycle and Events + +An instantiated `CollectionView` is aware of its lifecycle state and will throw events related +to when that state changes. The view states indicate whether the view is rendered, attached to +the DOM, or destroyed. + +Read More: +- [View Lifecycle](./view.lifecycle.md) +- [View DOM Change Events](./events.class.md#dom-change-events) +- [View Destroy Events](./events.class.md#destroy-events) + +## Entity Events + +The `CollectionView` can bind to events that occur on the attached `model` and `collection` - this +includes both [standard backbone-events](http://backbonejs.org/#Events-catalog) and custom events. + +Read More: +- [Entity Events](./events.entity.md) + +## DOM Interactions + +In addition to what Backbone provides the views, Marionette has additional API +for DOM interactions: `events`, `triggers`, and `ui`. + +By default `ui` is only bound to the elements within the [template](#rendering-a-template). +However as `events` and `triggers` are delegated to the view's `el` they will apply to any children. +There may be instances where binding `ui` is helpful when you want to access elements inside +`CollectionView`s children with [`getUI()`](./dom.interactions.md#accessing-ui-elements). For these +cases you will need to bind `ui` yourself. To do so run `bindUIElements` on the `CollectionView`: + +```javascript +import { CollectionView } from 'backbone.marionette'; + +const MyCollectionView = CollectionView.extend({ + // ... + + ui: { + checkbox: 'input[type="checkbox"]' + } }); + +const collectionView = new MyCollectionView(); + +collectionView.bindUIElements(); + +console.log(collectionView.getUI('checkbox')); // Output all checkboxes. ``` -Child views must be defined before they are referenced by the -`childView` attribute in a collection view definition. +Read More: +- [DOM Interactions](./dom.interactions.md) + +## Behaviors + +A `Behavior` provides a clean separation of concerns to your view logic, +allowing you to share common user-facing operations between your views. + +Read More: +- [Using `Behavior`s](./marionette.behavior.md#using-behaviors) + +## Managing Children + +Children are automatically managed once the `CollectionView` is +[rendered](#rendering-a-collectionview). For each model within the +`collection` the `CollectionView` will build and store a `childView` +within its `children` object. This allows you to easily access +the views within the collection view, iterate them, find them by +a given indexer such as the view's model or id and more. + +After the initial `render` the `CollectionView` binds to the `update` +and `reset` events of the `collection`. + +When the `collection` for the view is `reset`, the view will destroy all +children and re-render the entire collection. -Alternatively, you can specify a `childView` in the options for -the constructor: +When a model is added to the `collection`, the `CollectionView` will render that +one model into the `children`. + +When a model is removed from the `collection` (or destroyed / deleted), the `CollectionView` +will destroy and remove that model's child view. + +When the `collection` for the view is sorted, the view by default automatically re-sort +its child views unless the `sortWithCollection` attribute on the `CollectionView` is set +to `false`, or the `viewComparator` is `false`. ```javascript -var Mn = require('backbone.marionette'); +import Backbone from 'backbone'; +import { View, CollectionView } from 'backbone.marionette'; + +const collection = new Backbone.Collection(); + +const MyChildView = View.extend({: + template: false +}); + +const MyCollectionView = CollectionView.extend({ + childView: MyChildView, + collection, +}); + +const myCollectionView = new MyCollectionView(); + +// Collection view will not re-render as it has not been rendered +collection.reset([{foo: 'foo'}]); + +myCollectionView.render(); + +// Collection view will effectively re-render displaying the new model +collection.reset([{foo: 'bar'}]); +``` -var MyCollectionView = Mn.CollectionView.extend({...}); +When the children are rendered the +[`render:children` and `before:render:children` events](./events.class.md#renderchildren-and-beforerenderchildren-events) +will trigger. -new MyCollectionView({ +When a childview is added to the children +[`add:child` and `before:add:child` events](./events.class.md#addchild-and-beforeaddchild-events) +will trigger + +When a childview is removed from the children +[`remove:child` and `before:remove:child` events](./events.class.md#removechild-and-beforeremovechild-events) +will trigger. + +### Attaching `children` within the `el` + +By default the `CollectionView` will add the HTML of each ChildView +into an element buffer array, and then call the DOM API's +[appendContents](./dom.api.md#appendcontentsel-contents) once at the end +to move all of the HTML within the collection view's `el`. + +You can override this by specifying an `attachHtml` method in your +view definition. This method takes two parameters and has no return value. + +```javascript +import { CollectionView } from 'backbone.marionette'; + +CollectionView.extend({ + + // The default implementation: + attachHtml(els, $container){ + // Unless childViewContainer, $container === this.$el + this.Dom.appendContents(this.el, els); + } +}); +``` + +The first parameter is the HTML buffer, and the second parameter +is the expected container for the children which by default equates +to the view's `el` unless a [`childViewContainer`](#defining-the-childViewContainer) +is set. + +### Destroying All `children` + +`CollectionView` implements a `destroy` method which automatically +destroys its children and cleans up listeners. + +When the children are destroyed the +[`destroy:children` and `before:destroy:children` events](./events.class.md#destroychildren-and-beforedestroychildren-events) +will trigger. + +Read More: +- [View Destroy Events](./events.class.md#destroy-events) + +## CollectionView's `childView` + +When using a `collection` to manage the children of `CollectionView`, +specify a `childView` for your `CollectionView`. This must be +a Backbone view class definition, not an instance. It can be any +`Backbone.View` related class including both Marionette's `View` and +`CollectionView`. + +```javascript +import { View, CollectionView } from 'backbone.marionette'; + +const MyChildView = View.extend({}); + +const MyCollectionView = CollectionView.extend({ childView: MyChildView }); ``` -If you do not specify a `childView`, an exception will be thrown +**Errors** If you do not specify a `childView`, an exception will be thrown stating that you must specify a `childView`. You can also define `childView` as a function. In this form, the value @@ -72,25 +310,21 @@ when a `Model` needs to be initially rendered. This method also gives you the ability to customize per `Model` `ChildViews`. ```javascript -var Bb = require('backbone'); -var Mn = require('backbone.marionette'); +import _ from 'underscore'; +import Backbone from 'backbone'; +import { View, CollectionView } from 'backbone.marionette'; -var FooBar = Bb.Model.extend({ - defaults: { - isFoo: false - } +const FooView =View.extend({ + template: _.template('foo') }); -var FooView = Mn.View.extend({ - template: '#foo-template' -}); -var BarView = Mn.View.extend({ - template: '#bar-template' +const BarView = View.extend({ + bar }); -var MyCollectionView = Mn.CollectionView.extend({ - collection: new Bb.Collection(), - childView: function(item) { +const MyCollectionView = CollectionView.extend({ + collection: new Backbone.Collection(), + childView(item) { // Choose which view class to render, // depending on the properties of the item model if (item.get('isFoo')) { @@ -102,11 +336,13 @@ var MyCollectionView = Mn.CollectionView.extend({ } }); -var collectionView = new MyCollectionView(); -var foo = new FooBar({ +const collectionView = new MyCollectionView(); + +const foo = new Backbone.Model({ isFoo: true }); -var bar = new FooBar({ + +const bar = new Backbone.Model({ isFoo: false }); @@ -117,7 +353,63 @@ collectionView.collection.add(foo); collectionView.collection.add(bar); ``` -### CollectionView's `childViewOptions` +**Errors** If `childView` is a function that does not return a view class +an error will be thrown. + +### Building the `children` + +The `buildChildView` method is responsible for taking the ChildView class and +instantiating it with the appropriate data. This method takes three +parameters and returns a view instance to be used as the child view. + +```javascript +buildChildView(child, ChildViewClass, childViewOptions){ + // build the final list of options for the childView class + const options = _.extend({model: child}, childViewOptions); + // create the child view instance + const view = new ChildViewClass(options); + // return it + return view; +}, +``` + +Override this method when you need a more complicated build, but use [`childView`](#collectionviews-childview) +if you need to determine _which_ View class to instantiate. + +```javascript +import _ from 'underscore'; +import Backbone from 'backbone'; +import { CollectionView } from 'backbone.marionette'; +import MyListView from './my-list-view'; +import MyView from './my-view'; + +const MyCollectionView = CollectionView.extend({ + childView(child) { + if (child.get('type') === 'list') { + return MyListView; + } + + return MyView; + }, + buildChildView(child, ChildViewClass, childViewOptions) { + const options = {}; + + if (child.get('type') === 'list') { + const childList = new Backbone.Collection(child.get('list')); + options = _.extend({collection: childList}, childViewOptions); + } else { + options = _.extend({model: child}, childViewOptions); + } + + // create the child view instance + const view = new ChildViewClass(options); + // return it + return view; + } +}); +``` + +### Passing Data to the `childView` There may be scenarios where you need to pass data from your parent collection view in to each of the childView instances. To do this, provide @@ -126,15 +418,15 @@ literal. This will be passed to the constructor of your childView as part of the `options`. ```javascript -var Mn = require('backbone.marionette'); +import { View, CollectionView } from 'backbone.marionette'; -var ChildView = Mn.View.extend({ - initialize: function(options) { +const ChildView = View.extend({ + initialize(options) { console.log(options.foo); // => "bar" } }); -var CollectionView = Mn.CollectionView.extend({ +const MyCollectionView = CollectionView.extend({ childView: ChildView, childViewOptions: { @@ -150,14 +442,14 @@ the function should you need access to it when calculating of the object will be copied to the `childView` instance's options. ```javascript -var Mn = require('backbone.marionette'); +import { CollectionView } from 'backbone.marionette'; -var CollectionView = Mn.CollectionView.extend({ - childViewOptions: function(model) { +const MyCollectionView = CollectionView.extend({ + childViewOptions(model) { // do some calculations based on the model return { foo: 'bar' - } + }; } }); ``` @@ -170,13 +462,14 @@ collection view. The `emptyView` just like the [`childView`](#collectionviews-ch function that returns the `emptyView`. ```javascript -var Mn = require('backbone.marionette'); +import _ from 'underscore'; +import { View, CollectionView } from 'backbone.marionette'; -var MyEmptyView = Mn.View.extend({ +const MyEmptyView = View.extend({ template: _.template('Nothing to display.') }); -var MyCollectionView = Mn.CollectionView.extend({ +const MyCollectionView = CollectionView.extend({ // ... emptyView: MyEmptyView @@ -196,24 +489,27 @@ Showing things in this region directly is not advised. const isEmptyShowing = myCollectionView.getEmptyRegion().hasView(); ``` -### CollectionView's `emptyViewOptions` +This region can be useful for handling the +[EmptyView Region Events](./events.class.md#collectionview-emptyview-region-events). -Similar to [`childView`](#collectionviews-childview) and [`childViewOptions`](#collectionviews-childviewoptions), +### Passing Data to the `emptyView` + +Similar to [`childView`](#collectionviews-childview) and [`childViewOptions`](#padding-data-to-the-childview), there is an `emptyViewOptions` property that will be passed to the `emptyView` constructor. It can be provided as an object literal or as a function. If `emptyViewOptions` aren't provided the `CollectionView` will default to passing the `childViewOptions` to the `emptyView`. ```javascript -var Mn = require('backbone.marionette'); +import { View, CollectionView } from 'backbone.marionette'; -var EmptyView = Mn.View({ - initialize: function(options){ +const EmptyView = View.extend({ + initialize(options){ console.log(options.foo); // => "bar" } }); -var CollectionView = Mn.CollectionView({ +const MyCollectionView = CollectionView.extend({ emptyView: EmptyView, emptyViewOptions: { @@ -222,204 +518,575 @@ var CollectionView = Mn.CollectionView({ }); ``` -### CollectionView's `isEmpty` +### Defining When an `emptyView` shows If you want to control when the empty view is rendered, you can override `isEmpty`: ```javascript -var Mn = require('backbone.marionette'); +import { CollectionView } from 'backbone.marionette'; -var MyCollectionView = Mn.CollectionView.extend({ - isEmpty: function(allViewsFiltered) { +const MyCollectionView = CollectionView.extend({ + isEmpty() { // some logic to calculate if the view should be rendered as empty return this.collection.length < 2; } }); ``` -In the normal lifecycle of a `CollectionView`, `isEmpty` will be called -twice. Once when a render begins, and again after the [`viewFilter`](#filtering) is run. For the call after filtering, a boolean will be passed indicating if all -of the CollectionView's `children` were filtered. +The default implementation of `isEmpty` returns `!this.children.length`. -## CollectionView's `render` +You can also use this method to determine when the empty view was shown: -The `render` method of the collection view is responsible for -rendering the entire collection. It loops through each of the -children in the collection and renders them individually as a -`childView`. +```javascript +import { CollectionView } from 'backbone.marionette'; + +const MyCollectionView = CollectionView.extend({ + // ... + onRenderChildren() { + if (this.isEmpty()) { console.log('Empty View Shown'); } + } +}); +``` + +## Accessing a Child View + +You can retrieve a view by a number of methods. If the findBy* method cannot find the view, +it will return `undefined`. + +**Note** That children represents the views rendered that are or will be +attached within the view's `el`. + +#### CollectionView `children`'s: `findByCid` +Find a view by it's cid. ```javascript -var Mn = require('backbone.marionette'); +const bView = myCollectionView.children.findByCid(buttonView.cid); +``` -var MyCollectionView = Mn.CollectionView.extend({...}); +#### CollectionView `children`'s: `findByModel` +Find a view by model. -// all of the children views will now be rendered. -new MyCollectionView().render(); +```javascript +const bView = myCollectionView.children.findByModel(buttonView.model); ``` -### Automatic Rendering +#### CollectionView `children`'s: `findByModelCid` +Find a view by model cid. -After the initial render the collection view binds to the `update` and -`reset` events of the collection that is specified. +```javascript +const bView = myCollectionView.children.findByModelCid(buttonView.model.cid); +``` -When the collection for the view is "reset", the view will call `render` on -itself and re-render the entire collection. +#### CollectionView `children`'s: `findByIndex` -When a model is added to the collection, the collection view will render that -one model into the children. +Find by numeric index (unstable) -When a model is removed from a collection (or destroyed / deleted), the collection -view will destroy and remove that model's child view. +```javascript +const bView = myCollectionView.children.findByIndex(0); +``` -When the collection for the view is sorted, the view will automatically re-sort its child views unless the `sortWithCollection` attribute on the CollectionView is set to `false`. +#### CollectionView `children`'s: `findIndexByView` + +Find the index of the view inside the children ```javascript -var Bb = require('backbone'); -var Mn = require('backbone.marionette'); +const index = myCollectionView.children.findIndexByView(bView); +``` -var collection = new Bb.Collection(); +### CollectionView `children` Iterators And Collection Functions + +The container object borrows several functions from +[Underscore.js](http://underscorejs.org/), to provide iterators and other +collection functions, including: + +* [each](http://underscorejs.org/#each) +* [map](http://underscorejs.org/#map) +* [reduce](http://underscorejs.org/#reduce) +* [find](http://underscorejs.org/#find) +* [filter](http://underscorejs.org/#filter) +* [reject](http://underscorejs.org/#reject) +* [every](http://underscorejs.org/#every) +* [some](http://underscorejs.org/#some) +* [contains](http://underscorejs.org/#contains) +* [invoke](http://underscorejs.org/#invoke) +* [toArray](http://underscorejs.org/#toArray) +* [first](http://underscorejs.org/#first) +* [initial](http://underscorejs.org/#initial) +* [rest](http://underscorejs.org/#rest) +* [last](http://underscorejs.org/#last) +* [without](http://underscorejs.org/#without) +* [isEmpty](http://underscorejs.org/#isEmpty) +* [pluck](http://underscorejs.org/#pluck) +* [partition](http://underscorejs.org/#partition) + +These methods can be called directly on the container, to iterate and process +the views held by the container. + +```javascript +import Backbone from 'backbone'; +import { CollectionView } from 'backbone.marionette'; -var MyChildView = Mn.View.extend({ - template: _.noop +const collectionView = new CollectionView({ + collection: new Backbone.Collection() }); -var MyCollectionView = Mn.CollectionView.extend({ - childView: MyChildView, - collection: collection, +collectionView.render(); + +// iterate over all of the views and process them +collectionView.children.each(function(childView) { + // process the `childView` here }); +``` -var myCollectionView = new MyCollectionView(); +## Listening to Events on the `children` -// Collection view will not re-render as it has not been rendered -collection.reset([{foo: 'foo'}]); +The `CollectionView` can take action depending on what +events are triggered in its `children`. + +Read More: +- [Child Event Bubbling](./events.md#event-bubbling) + +## Self Managed `children` + +In addition to children added by Marionette matching the model of a `collection`, +the `children` of the `CollectionView` can be manually managed. + +### Adding a Child View + +The `addChildView` method can be used to add a view that is independent of your +`Backbone.Collection`. This method takes two parameters, the child view instance +and optionally the index for where it should be placed within the +[CollectionView's `children`](#managing-children). It returns the added view. + +```javascript +import { CollectionView } from 'backbone.marionette'; +import ButtonView from './button-view'; + +const MyCollectionView = CollectionView.extend({ + onRender() { + View = new ButtonView(); + this.addChildView(buttonView, this.children.length); + } +}); + +const myCollectionView = new MyCollectionView(); myCollectionView.render(); +``` +**Note** Unless an index is specified, this added view will be subject to filtering +and sorting and may be difficult to manage in complex situations. Use with care. -// Collection view will re-render displaying the new model -collection.reset([{foo: 'bar'}]); +### Removing a Child View + +The `removeChildView` method is useful if you need to remove and destroy a view from +the `CollectionView` without affecting the view's collection. In most cases it is +better to use the data to determine what the `CollectionView` should display. + +This method accepts the child view instance to remove as its parameter. It returns +the removed view; + +```javascript +import { CollectionView } from 'backbone.marionette'; + +const MyCollectionView = CollectionView.extend({ + onChildViewFooEvent(childView, model) { + // NOTE: we must wait for the server to confirm + // the destroy PRIOR to removing it from the collection + model.destroy({wait: true}); + + // but go ahead and remove it visually + this.removeChildView(childView); + } +}); ``` -### Re-render the CollectionView +### Detaching a Child View -If you need to re-render the entire collection, you can call the -`view.render` method. This method takes care of destroying all of -the child views that may have previously been opened. +This method is the same as [`removeChildView`](#removing-a-child-view) +with the exception that the removed view is not destroyed. -### CollectionView's `attachHtml` +### Swapping Child Views -By default the `CollectionView` will append the HTML of each ChildView -into the element buffer, and then calls the DOM API's [appendContents](./dom.api.md#appendcontentsel-contents) once at the -end to move the HTML into the collection view's `el`. +Swap the location of two views in the `CollectionView` `children` and in the `el`. +This can be useful when sorting is arbitrary or is not performant. -You can override this by specifying an `attachHtml` method in your -view definition. This method takes one parameter and has no return -value. +**Errors** If either of the two views aren't part of the `CollectionView` an error will be thrown. + +If one child is in the `el` but the other is not, [filter](#filtering-the-children) will be called. ```javascript -var Mn = require('backbone.marionette'); +import Backbone from 'backbone'; +import { CollectionView } from 'backbone.marionette'; +import MyChildView from './my-child-view'; -Mn.CollectionView.extend({ +const collection = new Backbone.Collection([ + { name: 'first' }, + { name: 'middle' }, + { name: 'last' } +]); - // The default implementation: - attachHtml: function(els){ - this.Dom.appendContents(this.el, els); - } +const myColView = new CollectionView({ + collection: collection, + childView: MyChildView +}); + +myColView.swapChildViews(myColView.children.first(), myColView.children.last()); + +myColView.children.first().model.get('name'); // "last" +myColView.children.last().model.get('name'); // "first" +``` + +## Sorting the `children` + +The `sort` method will loop through the `CollectionView` `children` prior to filtering +and sort them with the [`viewComparator`](#defining-the-viewcomparator). +By default, if a `viewComparator` is not set, the `CollectionView` will sort +the views by the order of the models in the `collection`. If set to `false` view +sorting will be disabled. + +This method is called internally when rendering and +[`sort` and `before:sort` events](./events.class.md#sort-and-beforesort-events) +will trigger. + +By default the `CollectionView` will maintain a sorted collection's order +in the DOM. This behavior can be disabled by specifying `{sortWithCollection: false}` +on initialize. +### Defining the `viewComparator` + +`CollectionView` allows for a custom `viewComparator` option if you want your +`CollectionView`'s children to be rendered with a different sort order than the +underlying Backbone collection uses. + +```javascript +import { CollectionView } from 'backbone.marionette'; + +const myCollectionView = new CollectionView({ + collection: someCollection, + viewComparator: 'otherFieldToSortOn' }); ``` -The first parameter is the instance of the CollectionView that -will receive the HTML from the second parameter, the HTML buffer. +```javascript +import Backbone from 'backbone'; +import { CollectionView } from 'backbone.marionette'; -## CollectionView's `destroy` +const myCollection = new Backbone.Collection([ + { id: 1 }, + { id: 4 }, + { id: 3 }, + { id: 2 } +]); -`CollectionView` implements a `destroy` method which automatically -destroys its children and cleans up listeners. +myCollection.comparator = 'id'; + +const mySortedColView = new CollectionView({ + //... + collection: myCollection +}); + +const myUnsortedColView = new CollectionView({ + //... + collection: myCollection, + viewComparator: false +}); + +mySortedColView.render(); // 1 4 3 2 +myUnsortedColView.render(); // 1 4 3 2 + +myCollection.sort(); +// mySortedColView auto-renders 1 2 3 4 +// myUnsortedColView has no change +``` + +The `viewComparator` can take any of the acceptable `Backbone.Collection` +[comparator formats](http://backbonejs.org/#Collection-comparator) -- a sortBy +(pass a function that takes a single argument), as a sort (pass a comparator +function that expects two arguments), or as a string indicating the attribute to +sort by. + +#### `getComparator` + +Override this method to determine which `viewComparator` to use. ```javascript -var Bb = require('backbone'); -var Mn = require('backbone.marionette'); +import { CollectionView } from 'backbone.marionette'; + +const MyCollectionView = CollectionView.extend({ + sortAsc(model) { + return -model.get('order'); + }, + sortDesc(model) { + return model.get('order'); + }, + getComparator() { + // The collectionView's model + if (this.model.get('sorted') === 'ASC') { + return this.sortAsc; + } -var MyChildView = Mn.View.extend({ - template: _.template('ChildView'), - onDestroy: function() { - console.log('I will get destroyed'); + return this.sortDesc; } -}) +}); +``` -var myCollectionView = new Mn.CollectionView({ - childView: MyChildView, - collection: new Bb.Collection([{ id: 1 }]) +#### `setComparator` + +The `setComparator` method modifies the `CollectionView`'s `viewComparator` +attribute and re-sorts. Passing `{ preventRender: true }` in the options argument +will prevent the view being rendered. + +```javascript +import { CollectionView } from 'backbone.marionette'; + +const cv = new CollectionView({ + collection: someCollection }); -myCollectionView.render(); +cv.render(); -myCollectionView.destroy(); // logs "I will get destroyed" +// Note: the setComparator is preventing the automatic re-render +cv.setComparator('orderBy', { preventRender: true }); + +// Render the children ordered by the orderBy attribute +cv.render(); ``` -## Events +#### `removeComparator` + +This function is actually an alias of `setComparator(null, options)`. It is useful +for removing the comparator. `removeComparator` also accepts `preventRender` as a option. + +```javascript +import { CollectionView } from 'backbone.marionette'; + +const cv = new CollectionView({ + collection: someCollection +}); + +cv.render(); + +cv.setComparator('orderBy'); -The `CollectionView`, like `View`, is able to trigger and respond to events -occurring during their lifecycle. The [Documentation for Events](./events.md) -has the complete documentation for how to set and handle events on views. +//Remove the current comparator without rendering again. +cv.removeComparator({ preventRender: true }); +``` -### Child Event Bubbling +### Maintaining the `collection`'s sort -The collection view is able to monitor and act on events on any children they -own using [`childViewEvents`](./events.md#explicit-event-listeners) and [`childViewTriggers`](./events.md#triggering-events-on-child-events). Additionally when a child -view triggers an event, that [event will bubble up](./events.md#event-bubbling) one level to the parent -collection view. For an example: +By default the `CollectionView` will maintain a sorted collection's order +in the DOM. This behavior can be disabled by specifying `{sortWithCollection: false}` +on initialize or on the view definiton. ```javascript -var Mn = require('backbone.marionette'); +import Backbone from 'backbone'; +import { CollectionView } from 'backbone.marionette'; + +const myCollection = new Backbone.Collection([ + { id: 1 }, + { id: 4 }, + { id: 3 }, + { id: 2 } +]); + +myCollection.comparator = 'id'; + +const mySortedColView = new CollectionView({ + //... + collection: myCollection +}); + +const myUnsortedColView = new CollectionView({ + //... + collection: myCollection, + sortWithCollection: false +}); + +mySortedColView.render(); // 1 4 3 2 +myUnsortedColView.render(); // 1 4 3 2 + +myCollection.sort(); +// mySortedColView auto-renders 1 2 3 4 +// myUnsortedColView has no change +``` + +## Filtering the `children` + +The `filter` method will loop through the `CollectionView`'s sorted `children` +and test them against the [`viewFilter`](#defining-the-viewfilter). +The views that pass the `viewFilter`are rendered if necessary and attached +to the CollectionView and the views that are filtered out will be detached. +After filtering the `children` will only contain the views to be attached. + +If a `viewFilter` exists the +[`filter` and `before:filter` events](./events.class.md#filter-and-beforefilter-events) +will trigger. + +By default the CollectionView will refilter when views change or when the +CollectionView is sorted. + +**Note** This is a presentation functionality used to easily filter in and out +constructed children. All children of a `collection` will be instantiated once +regardless of their filtered status. If you would prefer to manage child view +instantiation, you should filter the `collection` itself. + +### Defining the `viewFilter` + +`CollectionView` allows for a custom `viewFilter` option if you want to prevent +some of the underlying `children` from being attached to the DOM. +A `viewFilter` can be a function, predicate object. or string. -var Item = Mn.View.extend({ - tagName: 'li', +**Errors** An error will be thrown if the `ViewFilter` is not one of these options. - triggers: { - 'click a': 'select:item' +#### `viewFilter` as a function + +The `viewFilter` function takes a view from the `children` and returns a truthy +value if the child should be attached, and a falsey value if it should not. + +```javascript +import Backbone from 'backbone'; +import { CollectionView } from 'backbone.marionette'; + +const cv = new CollectionView({ + childView: SomeChildView, + emptyView: SomeEmptyView, + collection: new Bb.Collection([ + { value: 1 }, + { value: 2 }, + { value: 3 }, + { value: 4 } + ]), + + // Only show views with even values + viewFilter(view, index, children) { + return view.model.get('value') % 2 === 0; } }); -var Collection = Mn.CollectionView.extend({ - tagName: 'ul', +// renders the views with values '2' and '4' +cv.render(); +``` + +#### `viewFilter` as a predicate object + +The `viewFilter` predicate object will filter against the view's model attributes. + +```javascript +import Backbone from 'backbone'; +import { CollectionView } from 'backbone.marionette'; + +const cv = new CollectionView({ + childView: SomeChildView, + emptyView: SomeEmptyView, + collection: new Bb.Collection([ + { value: 1 }, + { value: 2 }, + { value: 3 }, + { value: 4 } + ]), + + // Only show views with value 2 + viewFilter: { value: 2 } +}); - onChildviewSelectItem: function(childView) { - console.log('item selected: ' + childView.model.id); +// renders the view with values '2' +cv.render(); +``` + +#### `viewFilter` as a string + +The `viewFilter` string represents the view's model attribute and will filter +truthy values. + +```javascript +import Backbone from 'backbone'; +import { CollectionView } from 'backbone.marionette'; + +const cv = new CollectionView({ + childView: SomeChildView, + emptyView: SomeEmptyView, + collection: new Bb.Collection([ + { value: 0 }, + { value: 1 }, + { value: 2 }, + { value: null }, + { value: 4 } + ]), + + // Only show views 1,2, and 4 + viewFilter: 'value' +}); + +// renders the view with values '1', '2', and '4' +cv.render(); +``` + +#### `getFilter` + +Override this function to programatically decide which +`viewFilter` to use when `filter` is called. + +```javascript +import { CollectionView } from 'backbone.marionette'; + +const MyCollectionView = CollectionView.extend({ + summaryFilter(view) { + return view.model.get('type') === 'summary'; + }, + getFilter() { + if (this.collection.length > 100) { + return this.summaryFilter; + } + return this.viewFilter; } }); ``` -The event will receive a [`childview:` prefix](./events.md#a-child-views-event-prefix) before going through the magic -method binding logic. See the -[documentation for Child View Events](./events.md#child-view-events) for more -information. +#### `setFilter` + +The `setFilter` method modifies the `CollectionView`'s `viewFilter` attribute and filters. +Passing `{ preventRender: true }` in the options argument will prevent the view +being rendered. + +```javascript +import { CollectionView } from 'backbone.marionette'; + +const cv = new CollectionView({ + collection: someCollection +}); + +cv.render(); -### Lifecycle Events +const newFilter = function(view, index, children) { + return view.model.get('value') % 2 === 0; +}; -The `CollectionView` contains its own lifecycle events, on top of the regular -`View` event lifecycle. For more information on what these are, and how to use -them, see the -[Documentation on `CollectionView` lifecycle events](./viewlifecycle.md#collectionview-lifecycle) +// Note: the setFilter is preventing the automatic re-render +cv.setFilter(newFilter, { preventRender: true }); -## Advanced CollectionView Usage +//Render the new state of the ChildViews instead of the whole DOM. +cv.render(); +``` -For getting advanced information about filtering, sorting or managing `CollectionView` look at -[Advanced CollectionView usage](./marionette.collectionviewadvanced.md) +#### `removeFilter` -### Managing Children +This function is actually an alias of `setFilter(null, options)`. It is useful +for removing filters. `removeFilter` also accepts `preventRender` as a option. -The `CollectionView` can store and manage its child views. This allows you to easily access -the views within the collection view, iterate them, find them by a given indexer such as the -view's model or collection, and more. [Additional Information...](./marionette.collectionviewadvanced.md#collectionviews-children) +```javascript +import { CollectionView } from 'backbone.marionette'; -### Filtering +const cv = new CollectionView({ + collection: someCollection +}); -`CollectionView` allows for a custom `viewFilter` option if you want to prevent some of the -child views from being rendered inside the CollectionView. [Additional Information...](./marionette.collectionviewadvanced.md#collectionviews-filter) +cv.render(); -### Sorting +cv.setFilter(function(view, index, children) { + return view.model.get('value') % 2 === 0; +}); -By default the `CollectionView` will maintain a sorted collection's order in the DOM. -[Additional Information...](./marionette.collectionviewadvanced.md#collectionviews-sort) +//Remove the current filter without rendering again. +cv.removeFilter({ preventRender: true }); +``` diff --git a/docs/marionette.collectionviewadvanced.md b/docs/marionette.collectionviewadvanced.md deleted file mode 100644 index 3e6f033150..0000000000 --- a/docs/marionette.collectionviewadvanced.md +++ /dev/null @@ -1,625 +0,0 @@ -# Advanced CollectionView Usage - -`CollectionView` provides a lot of possibilities to sort, filter and manages children. - - -## Documentation Index - -* [CollectionView's children](#collectionviews-children) - * [CollectionView's `buildChildView`](#collectionviews-buildchildview) - * [CollectionView's `addChildView`](#collectionviews-addchildview) - * [CollectionView: Retrieve Child Views](#collectionview-retrieve-child-views) - * [CollectionView children's: `findByCid`](#collectionview-children-findbycid) - * [CollectionView children's: `findByModel`](#collectionview-children-findbymodel) - * [CollectionView children's: `findByModelCid`](#collectionview-children-findbymodelcid) - * [CollectionView children's: `findByIndex`](#collectionview-children-findbyindex) - * [CollectionView children's: `findIndexByView`](#collectionview-children-findindexbyview) - * [CollectionView's `removeChildView`](#collectionviews-removechildview) - * [CollectionView's `detachChildView`](#collectionviews-detachchildview) - * [CollectionView's `swapChildViews`](#collectionviews-swapchildviews) - * [CollectionView childView Iterators And Collection Functions](#collectionview-childview-iterators-and-collection-functions) - -* [CollectionView's `filter`](#collectionviews-filter) - * [CollectionView's `viewFilter`](#collectionviews-viewfilter) - * [CollectionView's `getFilter`](#collectionviews-getfilter) - * [CollectionView's `setFilter`](#collectionviews-setfilter) - * [CollectionView's `removeFilter`](#collectionviews-removefilter) - -* [CollectionView's `sort`](#collectionviews-sort) - * [CollectionView's `viewComparator`](#collectionviews-viewcomparator) - * [CollectionView's `getComparator`](#collectionviews-getcomparator) - * [CollectionView's `setComparator`](#collectionviews-setcomparator) - * [CollectionView's `removeComparator`](#collectionviews-removecomparator) - * [CollectionView's `sortWithCollection`](#collectionviews-sortwithcollection) - -* [Binding `ui`](#binding-ui) - - -## CollectionView's children - -The `CollectionView` can store and manage its child views. This allows you to easily access -the views within the collection view, iterate them, find them by -a given indexer such as the view's model or collection, and more. - -### CollectionView's `buildChildView` - -The `buildChildView` is responsible for taking the ChildView class and -instantiating it with the appropriate data. This method takes three -parameters and returns a view instance to be used as thechild view. - -```javascript -buildChildView: function(child, ChildViewClass, childViewOptions){ - // build the final list of options for the childView class - var options = _.extend({model: child}, childViewOptions); - // create the child view instance - var view = new ChildViewClass(options); - // return it - return view; -}, -``` -Override this method when you need a more complicated build, but use [`childView`](./marionette.collectionview.md#collectionviews-childview) -if you need to determine _which_ View class to instantiate. - -```javascript -var Bb = require('backbone'); -var Mn = require('backbone.marionette'); - -var MyCollectionView = Mn.CollectionView.extend({ - childView: function(child) { - if (child.get('type') === 'list') { - return MyListView; - } - - return MyView; - }, - buildChildView: function(child, ChildViewClass, childViewOptions) { - var options = {}; - - if (child.get('type') === 'list') { - var childList = new Bb.Collection(child.get('list')); - options = _.extend({collection: childList}, childViewOptions); - } else { - options = _.extend({model: child}, childViewOptions); - } - - // create the child view instance - var view = new ChildViewClass(options); - // return it - return view; - } - -}); - -``` - -### CollectionView's `addChildView` - -The `addChildView` method can be used to add a view that is independent of your -`Backbone.Collection`. Note that this added view will be subject to filtering -and ordering and may be difficult to manage in complex situations. Use with -care. - -This method takes two parameters, the child view instance and the index for -where it should be placed within the [CollectionView's children](#collectionviews-children). It returns the added view. - -```javascript -var Mn = require('backbone.marionette'); -var buttonView = new ButtonView(); -var MyCollectionView = Mn.CollectionView.extend({ - onRender: function() { - this.addChildView(buttonView, this.collection.length); - } -}); - -var myCollectionView = new MyCollectionView(); - -myCollectionView.render(); -``` - -### CollectionView: Retrieve Child Views - -You can retrieve a view by any of the index. If the findBy* method cannot find the view, it will return undefined. - -#### CollectionView children's: `findByCid` -Find a view by it's cid. - -```javascript -var bView = myCollectionView.children.findByCid(buttonView.cid); -``` - -#### CollectionView children's: `findByModel` -Find a view by model. - -```javascript -var bView = myCollectionView.children.findByModel(buttonView.model); -``` - -#### CollectionView children's: `findByModelCid` -Find a view by model cid. - -```javascript -var bView = myCollectionView.children.findByModelCid(buttonView.model.cid); -``` - -#### CollectionView children's: `findByIndex` - -Find by numeric index (unstable) - -```javascript -var bView = myCollectionView.children.findByIndex(0); -``` - -#### CollectionView children's: `findIndexByView` - -Find the index of the view inside the children - -```javascript -var index = myCollectionView.children.findIndexByView(bView); -``` - -### CollectionView's `removeChildView` - -The `removeChildView` method is useful if you need to remove and destroy a view from the `CollectionView` without affecting the view's collection. In most cases it is better to use the data to determine what the `CollectionView` should display. - -This method accepts the child view instance to remove as its parameter. It returns the removed view; - -```javascript -var Mn = require('backbone.marionette'); - -Mn.CollectionView.extend({ - onChildViewClose: function(childView, model) { - // NOTE: we must wait for the server to confirm - // the destroy PRIOR to removing it from the collection - model.destroy({wait: true}); - - // but go ahead and remove it visually - this.removeChildView(childView); - } -}); -``` - -### CollectionView's `detachChildView` - -This method is the same as [`removeChildView`](#collectionviews-removechildview) -with the exception that the removed view is not destroyed. - -### CollectionView's `swapChildViews` - -Swap the location of two views in the `CollectionView` `children` and in the `el`. -This can be useful when sorting is arbitrary or is not performant. - -If either of the two views aren't part of the `CollectionView` an error will be thrown. - -If one child is in the `el` but the other is not, [filter](#collectionviews-filter) will be called. - -```javascript -var Mn = require('backbone.marionette'); - -var collection = new Backbone.Collection([ - { name: 'first' }, - { name: 'middle' }, - { name: 'last' } -]); - -var myCollection = new Mn.CollectionView({ - collection: collection, - childView: MyChildView -}); - -myCollection.swapChildViews(myCollection.children.first(), myCollection.children.last()); - -myCollection.children.first().model.get('name'); // "last" -myCollection.children.last().model.get('name'); // "first" -``` - -### CollectionView childView Iterators And Collection Functions - -The container object borrows several functions from -[Underscore.js](http://underscorejs.org/), to provide iterators and other -collection functions, including: - -* [each](http://underscorejs.org/#each) -* [map](http://underscorejs.org/#map) -* [reduce](http://underscorejs.org/#reduce) -* [find](http://underscorejs.org/#find) -* [filter](http://underscorejs.org/#filter) -* [reject](http://underscorejs.org/#reject) -* [every](http://underscorejs.org/#every) -* [some](http://underscorejs.org/#some) -* [contains](http://underscorejs.org/#contains) -* [invoke](http://underscorejs.org/#invoke) -* [toArray](http://underscorejs.org/#toArray) -* [first](http://underscorejs.org/#first) -* [initial](http://underscorejs.org/#initial) -* [rest](http://underscorejs.org/#rest) -* [last](http://underscorejs.org/#last) -* [without](http://underscorejs.org/#without) -* [isEmpty](http://underscorejs.org/#isEmpty) -* [pluck](http://underscorejs.org/#pluck) -* [partition](http://underscorejs.org/#partition) - -These methods can be called directly on the container, to iterate and process -the views held by the container. - -```javascript -var Bb = require('backbone'); -var Mn = require('backbone.marionette'); - -var collectionView = new Mn.CollectionView({ - collection: new Bb.Collection() -}); - -collectionView.render(); - -// iterate over all of the views and process them -collectionView.children.each(function(childView) { - // process the `childView` here -}); -``` - -## CollectionView's `filter` - -The `filter` method will loop through the `CollectionView` `children` -and test them against the [`viewFilter`](#collectionviews-viewfilter). -The views that pass the `viewFilter`are rendered if necessary and attached -to the CollectionView and the views that are filtered out will be detached. -If a `viewFilter` exists the `before:filter` and `filter` events will be triggered. -By default the CollectionView will refilter when views change or when the -CollectionView is sorted. - -### CollectionView's `viewFilter` - -`CollectionView` allows for a custom `viewFilter` option if you want to prevent -some of the underlying `children` from being attached to the DOM. -A `viewFilter` can be a function, predicate object. or string. - -#### CollectionView's `viewFilter` as a function - -The `viewFilter` function takes a view from the `children` and returns a truthy -value if the child should be attached, and a falsey value if it should not. - -```javascript -var Bb = require('backbone'); -var Mn = require('backbone.marionette'); - -var cv = new Mn.CollectionView({ - childView: SomeChildView, - emptyView: SomeEmptyView, - collection: new Bb.Collection([ - { value: 1 }, - { value: 2 }, - { value: 3 }, - { value: 4 } - ]), - - // Only show views with even values - viewFilter: function (view, index, children) { - return view.model.get('value') % 2 === 0; - } -}); - -// renders the views with values '2' and '4' -cv.render(); -``` - -#### CollectionView's `viewFilter` as a predicate object - -The `viewFilter` predicate object will filter against the view's model attributes. - -```javascript -var Bb = require('backbone'); -var Mn = require('backbone.marionette'); - -var cv = new Mn.CollectionView({ - childView: SomeChildView, - emptyView: SomeEmptyView, - collection: new Bb.Collection([ - { value: 1 }, - { value: 2 }, - { value: 3 }, - { value: 4 } - ]), - - // Only show views with even values - viewFilter: { value: 2 } -}); - -// renders the view with values '2' -cv.render(); -``` - -#### CollectionView's `viewFilter` as a predicate object - -The `viewFilter` string represents the view's model attribute and will filter -truthy values. - -```javascript -var Bb = require('backbone'); -var Mn = require('backbone.marionette'); - -var cv = new Mn.CollectionView({ - childView: SomeChildView, - emptyView: SomeEmptyView, - collection: new Bb.Collection([ - { value: 0 }, - { value: 1 }, - { value: 2 }, - { value: null }, - { value: 4 } - ]), - - // Only show views 1,2, and 4 - viewFilter: 'value' -}); - -// renders the view with values '2' -cv.render(); -``` - -### CollectionView's `getFilter` - -Override this function to programatically decide which -`viewFilter` to use when `filter` is called. - -```javascript -var Mn = require('backbone.marionette'); - -var MyCollectionView = Mn.CollectionView.extend({ - summaryFilter: function(view) { - return view.model.get('type') === 'summary'; - }, - getFilter: function() { - if(this.collection.length > 100) { - return this.summaryFilter; - } - return this.viewFilter; - } -}); -``` - -### CollectionView's `setFilter` - -The `setFilter` method modifies the `CollectionView`'s `viewFilter` attribute and filters. -Passing `{ preventRender: true }` in the options argument will prevent the view -being rendered. - -```javascript -var Mn = require('backbone.marionette'); - -var cv = new Mn.CollectionView({ - collection: someCollection -}); - -cv.render(); - -var newFilter = function(view, index, children) { - return view.model.get('value') % 2 === 0; -}; - -// Note: the setFilter is preventing the automatic re-render -cv.setFilter(newFilter, { preventRender: true }); - -//Render the new state of the ChildViews instead of the whole DOM. -cv.render(); -``` - -### CollectionView's `removeFilter` - -This function is actually an alias of `setFilter(null, options)`. It is useful -for removing filters. `removeFilter` also accepts `preventRender` as a option. - -```javascript -var Mn = require('backbone.marionette'); - -var cv = new Mn.CollectionView({ - collection: someCollection -}); - -cv.render(); - -cv.setFilter(function(view, index, children) { - return view.model.get('value') % 2 === 0; -}); - -//Remove the current filter without rendering again. -cv.removeFilter({ preventRender: true }); -``` - -## CollectionView's `sort` - -The `sort` method will loop through the `CollectionView` `children` -and sort them with the [`viewComparator`](#collectionviews-viewcomparator). -By default, if a `viewComparator` is not set, the `CollectionView` will sort -the views by the order of the models in the collection. If set to `false` view -sorting will be disabled. -This method is also triggered internally when rendering and `before:sort` and -`sort` events will be triggered before and after sorting. - -By default the `CollectionView` will maintain a sorted collection's order -in the DOM. This behavior can be disabled by specifying `{sortWithCollection: false}` -on initialize. - -### CollectionView's `viewComparator` - -`CollectionView` allows for a custom `viewComparator` option if you want your -`CollectionView`'s children to be rendered with a different sort order than the -underlying Backbone collection uses. - -```javascript -var Mn = require('backbone.marionette'); - -var cv = new Mn.CollectionView({ - collection: someCollection, - viewComparator: 'otherFieldToSortOn' -}); -``` - -```javascript -var Bb = require('backbone'); -var Mn = require('backbone.marionette'); - -var myCollection = new Bb.Collection([ - { id: 1 }, - { id: 4 }, - { id: 3 }, - { id: 2 } -]); - -myCollection.comparator = 'id'; - -var mySortedColView = new Mn.CollectionView({ - //... - collection: myCollection -}); - -var myUnsortedColView = new Mn.CollectionView({ - //... - collection: myCollection, - sort: false -}); - -mySortedColView.render(); // 1 4 3 2 -myUnsortedColView.render(); // 1 4 3 2 - -// mySortedColView auto-renders 1 2 3 4 -// myUnsortedColView has no change -myCollection.sort(); -``` - -The `viewComparator` can take any of the acceptable `Backbone.Collection` -[comparator formats](http://backbonejs.org/#Collection-comparator) -- a sortBy -(pass a function that takes a single argument), as a sort (pass a comparator -function that expects two arguments), or as a string indicating the attribute to -sort by. - -### CollectionView's `getComparator` - -Override this method to determine which `viewComparator` to use. - -```javascript -var Mn = require('backbone.marionette'); - -var MyCollectionView = Mn.CollectionView.extend({ - sortAsc: function(model) { - return -model.get('order'); - }, - sortDesc: function(model) { - return model.get('order'); - }, - getComparator: function() { - // The collectionView's model - if (this.model.get('sorted') === 'ASC') { - return this.sortAsc; - } - - return this.sortDesc; - } -}); -``` - -### CollectionView's `setComparator` - -The `setComparator` method modifies the `CollectionView`'s `viewComparator` -attribute and re-sorts. Passing `{ preventRender: true }` in the options argument -will prevent the view being rendered. - -```javascript -var Mn = require('backbone.marionette'); - -var cv = new Mn.CollectionView({ - collection: someCollection -}); - -cv.render(); - -// Note: the setComparator is preventing the automatic re-render -cv.setComparator('orderBy', { preventRender: true }); - -// Render the children ordered by the orderBy attribute -cv.render(); -``` - -### CollectionView's `removeComparator` - -This function is actually an alias of `setComparator(null, options)`. It is useful -for removing the comparator. `removeComparator` also accepts `preventRender` as a option. - -```javascript -var Mn = require('backbone.marionette'); - -var cv = new Mn.CollectionView({ - collection: someCollection -}); - -cv.render(); - -cv.setComparator('orderBy'); - -//Remove the current comparator without rendering again. -cv.removeComparator({ preventRender: true }); -``` - -### CollectionView's `sortWithCollection` - -By default the `CollectionView` will maintain a sorted collection's order -in the DOM. This behavior can be disabled by specifying `{sortWithCollection: false}` on initialize or on the view definiton - -```javascript -var Bb = require('backbone'); -var Mn = require('backbone.marionette'); - -var myCollection = new Bb.Collection([ - { id: 1 }, - { id: 4 }, - { id: 3 }, - { id: 2 } -]); - -myCollection.comparator = 'id'; - -var mySortedColView = new Mn.CollectionView({ - //... - collection: myCollection -}); - -var myUnsortedColView = new Mn.CollectionView({ - //... - collection: myCollection, - sortWithCollection: false -}); - -mySortedColView.render(); // 1 4 3 2 -myUnsortedColView.render(); // 1 4 3 2 - -// mySortedColView auto-renders 1 2 3 4 -// myUnsortedColView has no change -myCollection.sort(); -``` - -## Binding `ui` - -By default, `CollectionView` will not bind the `ui` object. As it has no direct -`template` of its own to manage, this isn't usually an issue. There may be -instances where binding `ui` is helpful when you want to access elements inside -`CollectionView`s children with `getUI()`. - -If you need to bind `ui` yourself, you can just run `bindUIElements` on the -collection: - -```javascript -var Mn = require('backbone.marionette'); - -var MyCollectionView = Mn.CollectionView.extend({ - ... - - ui: { - checkbox: 'input[type="checkbox"]' - } -}); - -var collectionView = new MyCollectionView(); - -collectionView.bindUIElements(); - -console.log(collectionView.getUI('checkbox')); // Output all checkboxes. -``` diff --git a/docs/marionette.mnobject.md b/docs/marionette.mnobject.md index 4030e7d735..5cb5d8f6f5 100644 --- a/docs/marionette.mnobject.md +++ b/docs/marionette.mnobject.md @@ -2,16 +2,29 @@ `MnObject` incorporates backbone conventions `initialize`, `cid` and `extend`. `MnObject` includes: -- [Common Marionette Functionality](./common.md). -- [Radio API](./backbone.radio.md#marionette-integration). +- [Common Marionette Functionality](./common.md) +- [Class Events](./events.class.md#mnobject-events) +- [Radio API](./backbone.radio.md#marionette-integration) ## Documentation Index +* [Instantiating a MnObject](#instantiating-a-mnobject) * [Unique Client ID](#unique-client-id) -* [Initialize](#initialize) * [Destroying a MnObject](#destroying-a-mnobject) * [Basic Use](#basic-use) +## Instantiating a MnObject + +When instantiating a `MnObject` there are several properties, if passed, +that will be attached directly to the instance: +`channelName`, `radioEvents`, `radioRequests` + +```javascript +import { MnObject } from 'backbone.marionette'; + +const myObject = new MnObject({ ... }); +``` + ## Unique Client ID The `cid` or client id is a unique identifier automatically assigned to MnObjects when they're first created and by default is prefixed with `mno`. @@ -29,24 +42,6 @@ const foo = new MyFoo(); console.log(foo.cid); // foo1234 ``` -## Initialize -`initialize` is called immediately after the MnObject has been instantiated, -and is invoked with the same arguments that the constructor received. - -```javascript -import { MnObject } from 'backbone.marionette'; - -const Friend = MnObject.extend({ - initialize(options){ - console.log(options.name); - } -}); - -new Friend({name: 'John'}); -``` - -[Live example](https://jsfiddle.net/marionettejs/1ytrwyog/) - ## Destroying a MnObject ### `destroy` diff --git a/docs/marionette.region.md b/docs/marionette.region.md index a779846c08..8b12687881 100644 --- a/docs/marionette.region.md +++ b/docs/marionette.region.md @@ -1,21 +1,26 @@ # Regions Regions provide consistent methods to manage, show and destroy -views in your applications and layouts. You can use a jQuery selector to -identify where your region must be displayed. +views in your applications and views. + +`Region` includes: +- [Common Marionette Functionality](./common.md) +- [Class Events](./events.class.md#region-events) +- [The DOM API](./dom.api.md) See the documentation for [laying out views](./marionette.view.md#laying-out-views---regions) for an introduction in managing regions throughout your application. -Regions maintain the [View's lifecycle](./viewlifecycle.md#regions-and-the-view-lifecycle) while showing or emptying a view. +Regions maintain the [View's lifecycle](./view.lifecycle.md) while showing or emptying a view. ## Documentation Index +* [Instantiating a Region](#instantiating-a-region) * [Defining the Application Region](#defining-the-application-region) * [Defining Regions](#defining-regions) * [String Selector](#string-selector) * [Additional Options](#additional-options) - * [Specifying regions as a Function](#specifying-regions-as-a-function) + * [Specifying `regions` as a Function](#specifying-regions-as-a-function) * [Using a RegionClass](#using-a-regionclass) * [Referencing UI in `regions`](#referencing-ui-in-regions) * [Adding Regions](#adding-regions) @@ -30,10 +35,26 @@ Regions maintain the [View's lifecycle](./viewlifecycle.md#regions-and-the-view- * [Preserving Existing Views](#preserving-existing-views) * [Detaching Existing Views](#detaching-existing-views) * [`reset` A Region](#reset-a-region) +* [`destroy` A Region](#destroy-a-region) * [Check If View Is Being Swapped By Another](#check-if-view-is-being-swapped-by-another) * [Set How View's `el` Is Attached](#set-how-views-el-is-attached) * [Configure How To Remove View](#configure-how-to-remove-view) +## Instantiating a Region + +When instantiating a `Region` there are two properties, if passed, +that will be attached directly to the instance: +`el`, and `replaceElement`. + +```javascript +import { Region } from 'backbone.marionette'; + +const myRegion = new Region({ ... }); +``` + +While regions may be instantiated and useful on their own, their primary use case is through +the [`Application`](#defining-the-application-region) and [`View`](#defining-regions) classes. + ## Defining the Application Region The Application defines a single region `el` using the `region` attribute. This @@ -41,37 +62,39 @@ can be accessed through `getRegion()` or have a view displayed directly with `showView()`. Below is a short example: ```javascript -var Mn = require('backbone.marionette'); -var SomeView = require('./view'); +import { Application } from 'backbone.marionette'; +import SomeView from './view'; -var App = Mn.Application.extend({ +const MyApp = Application.extend({ region: '#main-content', - onStart: function() { - var main = this.getRegion(); // Has all the properties of a `Region` - main.show(new SomeView()); + onStart() { + const mainRegion = this.getRegion(); // Has all the properties of a `Region` + mainRegion.show(new SomeView()); } }); ``` For more information, see the -[Application docs](./marionette.application.md#root-layout). +[Application docs](./marionette.application.md#application-region). ## Defining Regions -Marionette supports multiple ways to define regions on your `Application` or -`View`. This section will document the different types as applied to `View`, -although they will work for `Application` as well - just replace `regions` with -`region` in your definition. +In Marionette you can define a region with a string selector or an object literal +on your `Application` or `View`. This section will document the two types as applied +to `View`, although they will work for `Application` as well - just replace `regions` +with `region` in your definition. + +**Errors** An error will be thrown for an incorrect region configuration. ### String Selector You can use a jQuery string selector to define regions. ```javascript -var Mn = require('backbone.marionette'); +import { View } from 'backbone.marionette'; -var MyView = Mn.View.extend({ +const MyView = View.extend({ regions: { mainRegion: '#main' } @@ -89,13 +112,13 @@ To overwrite the parent `el` of the region with the rendered contents of the inner View, use `replaceElement` as so: ```javascript -var Mn = require('backbone.marionette'); +import { View } from 'backbone.marionette'; -var OverWriteView = Mn.View.extend({ +const OverWriteView = View.extend({ className: '.new-class' }); -var MyView = Mn.View.extend({ +const MyView = View.extend({ regions: { main: { el: '.overwrite-me', @@ -103,7 +126,7 @@ var MyView = Mn.View.extend({ } } }); -var view = new MyView(); +const view = new MyView(); view.render(); console.log(view.$('.overwrite-me').length); // 1 @@ -122,7 +145,9 @@ these elements are usually very strict on what content they will allow. ```js -var MyView = Mn.View.extend({ +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ regions: { regionDefinition: { el: '.bar', @@ -132,16 +157,19 @@ var MyView = Mn.View.extend({ }); ``` -### Specifying regions as a Function +**Errors** An error will be thrown in the regions `el` is not specified, +or if the `el` does not exist in the html. -The `regions` attribute on a view can be a +### Specifying `regions` as a Function + +On a `View` the `regions` attribute can also be a [function returning an object](./basics.md#functions-returning-values): ```javascript -var Mn = require('backbone.marionette'); +import { View } from 'backbone.marionette'; -var MyView = Mn.View.extend({ - regions: function(){ +const MyView = View.extend({ + regions(){ return { firstRegion: '#first-region' }; @@ -154,24 +182,30 @@ var MyView = Mn.View.extend({ If you've created a custom region class, you can use it to define your region. ```javascript -var Mn = require('backbone.marionette'); +import { Application, Region, View } from 'backbone.marionette'; -var MyRegion = Mn.Region.extend({ - onShow: function(){ +const MyRegion = Region.extend({ + onShow(){ // Scroll to the middle this.$el.scrollTop(this.currentView.$el.height() / 2 - this.$el.height() / 2); } }); -var MyView = Mn.View.extend({ +const MyApp = Application.extend({ + regionClass: MyRegion, + region: '#first-region' +}) + +const MyView = View.extend({ + regionClass: MyRegion, regions: { firstRegion: { el: '#first-region', - regionClass: MyRegion - } + regionClass: Region // Don't scroll this to the top + }, + secondRegion: '#second-region' } }); - ``` [Live example](https://jsfiddle.net/marionettejs/oLLrzx8g/) @@ -182,9 +216,9 @@ The UI attribute can be useful when setting region selectors - simply use the `@ui.` prefix: ```javascript -var Mn = require('backbone.marionette'); +import { View } from 'backbone.marionette'; -var MyView = Mn.View.extend({ +const MyView = View.extend({ ui: { region: '#first-region' }, @@ -192,7 +226,6 @@ var MyView = Mn.View.extend({ firstRegion: '@ui.region' } }); - ``` [Live example](https://jsfiddle.net/marionettejs/ey1od1g8/) @@ -203,9 +236,9 @@ To add regions to a view after it has been instantiated, simply use the `addRegion` method: ```javascript -var MyView = require('./myview'); +import MyView from './myview'; -myView = new MyView(); +const myView = new MyView(); myView.addRegion('thirdRegion', '#third-region'); ``` @@ -214,9 +247,9 @@ Now we can access `thirdRegion` as we would the others. You can also add multiple regions using `addRegions`. ```javascript -var MyView = require('./myview'); +import MyView from './myview'; -myView = new MyView(); +const myView = new MyView(); myView.addRegions({ main: { el: '.overwrite-me', @@ -228,12 +261,13 @@ myView.addRegions({ ## Removing Regions -You can remove all of the regions from a view by calling `removeRegions` or you can remove a region by name using `removeRegion`. When a region is removed the region will be destroyed. +You can remove all of the regions from a view by calling `removeRegions` or you can remove a +region by name using `removeRegion`. When a region is removed the region will be destroyed. ```javascript -var Mn = require('backbone.marionette'); +import { View } from 'backbone.marionette'; -var MyView = Mn.View.extend({ +const MyView = View.extend({ regions: { main: '.main', sidebar: '.sidebar', @@ -241,10 +275,10 @@ var MyView = Mn.View.extend({ } }); -var myView = new MyView(); +const myView = new MyView(); // remove only the main region -var mainRegion = myView.removeRegion('main'); +const mainRegion = myView.removeRegion('main'); mainRegion.isDestroyed(); // -> true @@ -255,12 +289,11 @@ myView.removeRegions(); ## Using Regions on a view In addition to adding and removing regions there are a few -methods to help utilize regions. +methods to help utilize regions. All of these methods will first +render an unrendered view so that regions are properly initialized. - `getRegion(name)` - Request a region from a view by name. - - Note: If the view hasn't been rendered at this point, it will be. - `getRegions()` - Returns an object literal of all regions on the view organized by name. - - Note: If the view hasn't been rendered at this point, it will be. - `hasRegion(name)` - Check if a view has a region. - `emptyRegions()` - Empty all of the regions on a view. @@ -269,29 +302,31 @@ methods to help utilize regions. Once a region is defined, you can call its `show` method to display the view: ```javascript -var myView = new MyView(); -var childView = new MyChildView(); -var mainRegion = myView.getRegion('main'); +const myView = new MyView(); +const childView = new MyChildView(); +const mainRegion = myView.getRegion('main'); // render and display the view -mainRegion.show(childView); +mainRegion.show(childView, { fooOption: 'bar' }); ``` This is equivalent to a view's `showChildView` which can be used as: ```javascript -var myView = new MyView(); -var childView = new MyChildView(); +const myView = new MyView(); +const childView = new MyChildView(); // render and display the view -myView.showChildView('main', childView); +myView.showChildView('main', childView, { fooOption: 'bar' }); ``` Both forms take an `options` object that will be passed to the -[events fired during `show`](./viewlifecycle.md#show-view-events). +[events fired during `show`](./events.class.md#show-and-beforeshow-events). For more information on `showChildView` and `getChildView`, see the -[Documentation for Views](./marionette.view.md#managing-sub-views) +[Documentation for Views](./marionette.view.md#managing-children) + +**Errors** An error will be thrown if the view is falsy or destroyed. ### Checking whether a region is showing a view @@ -300,8 +335,8 @@ function. This will return a boolean value depending whether or not the region is showing a view. ```javascript -var myView = new MyView(); -var mainRegion = myView.getRegion('main'); +const myView = new MyView(); +const mainRegion = myView.getRegion('main'); mainRegion.hasView() // false mainRegion.show(new OtherView()); @@ -314,78 +349,52 @@ If you show a view in a region with an existing view, Marionette will ### Non-Marionette Views Marionette Regions aren't just for showing Marionette Views - they can also -display instances of regular [`Backbone.View`](http://backbonejs.org/#View). +display instances of a [`Backbone.View`](http://backbonejs.org/#View). To do this, ensure your view defines a `render()` method and just treat it like a regular Marionette View: ```javascript -var Bb = require('backbone'); -var Mn = require('backbone.marionette'); -var _ = require('underscore'); +import _ from 'underscore'; +import Bb from 'backbone'; +import { View } from 'backbone.marionette'; -var MyChildView = Bb.View.extend({ - render: function() { +const MyChildView = Bb.View.extend({ + render() { this.$el.append('

    Some text

    '); }, - onRender: function() { + onRender() { console.log('Regions also fire Lifecycle events on Backbone.View!'); } }); -var MyParentView = Mn.View.extend({ +const MyParentView = View.extend({ regions: { child: '.child-view' }, template: _.template('
    '), - onRender: function() { + onRender() { this.showChildView('child', new MyChildView()); } }); ``` -As you can see above, you can listen to [Lifecycle Events](./viewlifecycle.md) +As you can see above, you can listen to [Lifecycle Events](./view.lifecycle.md) on `Backbone.View` and Marionette will fire the events for you. -#### Partially-rendered Views - -Some libraries will partially "render" a view by setting their `$el`. This can -cause issues with Marionette assuming it's already been rendered. To get around -this, you must manually call `render` before showing the view: - -```javascript -var MyParentView = Mn.View.extend({ - regions: { - child: '.child-view' - }, - - template: _.template('
    '), - - onRender: function() { - var backgridView = new BackgridView({collection: myCollection}); - backgridView.render(); - this.showChildView('child', backgridView); - } -}); -``` - -Libraries that are known to exhibit this behavior are: - -* [Backgrid 0.3.7](http://backgridjs.com) - -This behavior is part of [`Marionette.View#setElement()`](./marionette.view.md). - ## Showing a Template -You can show a template or a string directly into a region. Additionally you can pass an object literal containing a template and any other view options. Under the hood a `Marionette.View` is instantiated using the template. +You can show a template or a string directly into a region. Additionally you can pass an object +literal containing a template and any other view options. Under the hood a `Marionette.View` is +instantiated using the template. ```javascript -var myView = new MyView(); +const myView = new MyView(); -var template = _.template('This is the <%- section %> page'); -var templateContext = templateContext: { section: 'main' }; +const template = _.template('This is the <%- section %> page'); +const templateContext = templateContext: { section: 'main' }; myView.showChildView('main', { template: template, @@ -403,17 +412,17 @@ You can remove a view from a region (effectively "unshowing" it) with `region.empty()` on a region: ```javascript -var myView = new MyView(); +const myView = new MyView(); myView.showChildView('main', new OtherView()); -var mainRegion = myView.getRegion('main'); +const mainRegion = myView.getRegion('main'); mainRegion.empty(); ``` This will destroy the view, clean up any event handlers and remove it from -the DOM. When a region is emptied [empty events are triggered](./viewlifecycle.md#empty-region-events). +the DOM. When a region is emptied [empty events are triggered](./events.class.md#empty-and-beforeempty-events). -**Note**: If the region does _not_ currently contain a View it will detach +**NOTE** If the region does _not_ currently contain a View it will detach any HTML inside the region when emptying. If the region _does_ contain a View [any HTML that doesn't belong to the View will remain](./upgrade.md#changes-to-regionshow). @@ -428,11 +437,11 @@ automatically destroy the previous view. You can prevent this behavior by If you want to detach an existing view from a region, use `detachView`. ```javascript -var myView = new MyView(); +const myView = new MyView(); -var myOtherView = new MyView(); +const myOtherView = new MyView(); -var childView = new MyChildView(); +const childView = new MyChildView(); // render and display the view myView.showChildView('main', childView); @@ -441,25 +450,51 @@ myView.showChildView('main', childView); myOtherView.showChildView('main', myView.getRegion('main').detachView()); ``` -**NOTE** When detaching a view you must pass it to a new region so Marionette -can handle its life cycle automatically or destroy it manually to prevent memory leaks. +**Note** When detaching a view you must pass it to a new region so Marionette +can handle its life cycle automatically or `destroy` it manually to prevent memory leaks. ## `reset` A Region A region can be `reset` at any time. This destroys any existing view being displayed, and deletes the cached `el`. The next time the -region shows a view, the region's `el` is queried from -the DOM. +region shows a view, the region's `el` is queried from the DOM. ```javascript -var myView = new MyView(); +const myView = new MyView(); myView.showChildView('main', new OtherView()); -var mainRegion = myView.getRegion('main'); +const myRegion = myView.getRegion('main'); myRegion.reset(); ``` This can be useful in unit testing your views. +## `destroy` A Region + +A region can be destroyed which will `reset` the region, remove it from any parent view, +and stop any internal region listeners. A destroyed region should not be reused. + +```javascript +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + regions: { + mainRegion: '#main' + } +}); + +const myView = new MyView(); + +const myRegion = myView.getRegion('mainRegion'); + +myRegion.show(new ChildView()); + +myRegion.destroy(); + +myRegion.isDestroyed(); // true +myRegion.hasView(); // false +myView.hasRegion('mainRegion'); // false +``` + ## Check If View Is Being Swapped By Another The `isSwappingView` method returns if a view is being swapped by another one. It's useful @@ -468,9 +503,9 @@ inside region lifecycle events / methods. The example will show an message when the region is empty: ```javascript -var Mn = require('backbone.marionette'); +import { Region } from 'backbone.marionette'; -var EmptyMsgRegion = Mn.Region.extend({ +const EmptyMsgRegion = Region.extend({ onEmpty() { if (!this.isSwappingView()) { this.$el.append('Empty Region'); @@ -480,39 +515,45 @@ var EmptyMsgRegion = Mn.Region.extend({ ``` [Live example](https://jsfiddle.net/marionettejs/c1nacq0c/1/) -## Set How View's `el` Is Attached +## Set How View's `el` Is Attached and Detached Override the region's `attachHtml` method to change how the view is attached -to the DOM. This method receives one parameter - the view to show. +to the DOM (when not using `replaceElement: true`. This method receives one +parameter - the view to show. The default implementation of `attachHtml` is essentially: ```javascript -var Mn = require('backbone.marionette'); +import { Region } from 'backbone.marionette'; -Mn.Region.prototype.attachHtml = function(view){ +Region.prototype.attachHtml = function(view){ this.el.appendChild(view.el); } ``` -It is also possible to define a custom attach method for a single region by -extending from the Region class and including a custom `attachHtml` method. +Similar to `attachHtml`, override `detachHtml` to determine how the region detaches +the contents from its `el`. This method receives no parameters. + +For most cases you will want to use the [DOM API](./dom.api.md) to determine how +a region html is attached, but in some cases you may want to override a single Region +class for situations like animation where you want to control both attaching and +[view removal](#configure-how-to-remove-view). This example will make a view slide down from the top of the screen instead of just appearing in place: ```javascript -var Mn = require('backbone.marionette'); +import { Region, View } from 'backbone.marionette'; -var ModalRegion = Mn.Region.extend({ - attachHtml: function(view){ +const ModalRegion = Region.extend({ + attachHtml(view){ // Some effect to show the view: this.$el.empty().append(view.el); this.$el.hide().slideDown('fast'); } }); -var MyView = Mn.View.extend({ +const MyView = View.extend({ regions: { mainRegion: '#main-region', modalRegion: { @@ -531,9 +572,9 @@ from the DOM. This method receives one parameter - the view to remove. The default implementation of `removeView` is: ```javascript -var Mn = require('backbone.marionette'); +import { Region } from 'backbone.marionette'; -Mn.Region.prototype.removeView = function(view){ +Region.prototype.removeView = function(view){ this.destroyView(view); } ``` @@ -545,9 +586,9 @@ Mn.Region.prototype.removeView = function(view){ This example will animate with a fade effect showing and hiding the view: ```javascript -var Mn = require('backbone.marionette'); +import { Region, View } from 'backbone.marionette'; -var AnimatedRegion = Mn.Region.extend({ +const AnimatedRegion = Region.extend({ attachHtml(view) { view.$el .css({display: 'none'}) @@ -556,15 +597,14 @@ var AnimatedRegion = Mn.Region.extend({ }, removeView(view) { - var self = this; - view.$el.fadeOut('slow', function() { - self.destroyView(view); - if (self.currentView) self.currentView.$el.fadeIn('slow'); + view.$el.fadeOut('slow', () => { + this.destroyView(view); + if (this.currentView) this.currentView.$el.fadeIn('slow'); }); } }); -var MyView = Mn.View.extend({ +const MyView = View.extend({ regions: { animatedRegion: { regionClass: AnimatedRegion, diff --git a/docs/marionette.view.md b/docs/marionette.view.md index da3967f1dc..1b21e4378a 100644 --- a/docs/marionette.view.md +++ b/docs/marionette.view.md @@ -1,136 +1,129 @@ # Marionette.View -A `View` is a view that represents an item to be displayed with a template. -This is typically a `Backbone.Model`, `Backbone.Collection`, or nothing at all. - -Views are also used to build up your application hierarchy - you can easily nest -multiple views through the `regions` attribute. - -**_Note: From Marionette v3.x, `Marionette.View` replaces -`Marionette.LayoutView` and `Marionette.ItemView`._** +A `View` is used for managing portions of the DOM via a single parent DOM element or `el`. +It provides a consistent interface for managing the content of the `el` which is typically +administered by serializing a `Backbone.Model` or `Backbone.Collection` and rendering +a template with the serialized data into the `View`s `el`. + +The `View` provides event delegation for capturing and handling DOM interactions as well as +the ability to separate concerns into smaller, managed child views. + +`View` includes: +- [The DOM API](./dom.api.md) +- [Class Events](./events.class.md#view-events) +- [DOM Interactions](./dom.interactions.md) +- [Child Event Bubbling](./events.md#event-bubbling) +- [Entity Events](./events.entity.md) +- [View Rendering](./view.rendering.md) +- [Prerendered Content](./dom.prerendered.md) +- [View Lifecycle](./view.lifecycle.md) + +A `View` can have [`Region`s](./marionette.region.md) and [`Behavior`s](./marionette.behavior.md) ## Documentation Index -* [Rendering a Template](#rendering-a-template) - * [Set How Template is Attached to the `el`](#set-how-template-is-attached-to-the-el) -* [Managing an Existing Page](#managing-an-existing-page) - * [Setting a `template` to `false`](#setting-a-template-to-false) -* [Laying Out Views - Regions](#laying-out-views-regions) - * [Managing Sub-views](#managing-sub-views) - * [Showing a View](#showing-a-view) - * [Accessing a Child View](#accessing-a-child-view) - * [Detaching a Child View](#detaching-a-child-view) +* [Instantiating a View](#instantiating-a-view) +* [Rendering a View](#rendering-a-view) + * [Using a View Without a Template](#using-a-view-without-a-template) +* [View Lifecycle and Events](#view-lifecycle-and-events) +* [Entity Events](#entity-events) +* [DOM Interactions](#dom-interactions) +* [Behaviors](#behaviors) +* [Managing Children](#managing-children) + * [Laying Out Views - Regions](#laying-out-views---regions) + * [Showing a Child View](#showing-a-child-view) + * [Attaching a Child View](#attaching-a-child-view) + * [Detaching a Child View](#detaching-a-child-view) + * [Destroying a Child View](#destroying-a-child-view) * [Region Availability](#region-availability) - * [Efficient Nested View Structures](#efficient-nested-view-structures) - * [Listening to Events on Children](#listening-to-events-on-children) -* [Organizing Your View](#organizing-your-view) - * [Defining `ui`](#defining-ui) - * [Accessing UI Elements](#accessing-ui-elements) - * [Referencing UI in `events` and `triggers`](#referencing-ui-in-events-and-triggers) -* [Events](#events) - * [onEvent Listeners](#onevent-listeners) - * [Lifecycle Events](#lifecycle-events) - * [Binding To User Input](#binding-to-user-input) - * [Event and Trigger Mapping](#event-and-trigger-mapping) - * [View `events`](#view-events) - * [View `triggers`](#view-triggers) - * [View `triggers` Event Object](#view-triggers-event-object) -* [Model and Collection Events](#model-and-collection-events) - * [Model Events](#model-events) - * [Function Callback](#function-callback) - * [Collection Events](#collection-events) - * [Listening to Both](#listening-to-both) - -## Rendering a Template - -The Marionette View implements a powerful render method which, given a template, -will build your HTML from that template, mixing in model information and any -extra template context. - -**Overriding `render`** If you want to add extra behavior to your view's render, -you would be best off doing your logic in the -[`onBeforeRender` or `onRender` handlers](#lifecycle-events). - -To render a template, set the `template` attribute on your view: +* [Efficient Nested View Structures](#efficient-nested-view-structures) +* [Listening to Events on Children](#listening-to-events-on-children) -```javascript -var Mn = require('backbone.marionette'); -var _ = require('underscore'); +## Instantiating a View -var MyView = Mn.View.extend({ - tagName: 'h1', - template: _.template('Contents') -}); +When instantiating a `View` there are several properties, if passed, +that will be attached directly to the instance: +`attributes`, `behaviors`, `childViewEventPrefix`, `childViewEvents`, +`childViewTriggers`, `className`, `collection`, `collectionEvents`, `el`, +`events`, `id`, `model`, `modelEvents`, `regionClass`, `regions`, +`tagName`, `template`, `templateContext`, `triggers`, `ui` -var myView = new MyView(); -myView.render(); +```javascript +import { View } from 'backbone.marionette'; + +const myView = new View({ ... }); ``` -[Live example](https://jsfiddle.net/marionettejs/dhsjcka4/) +Some of these properties come from Marionette, but many are inherited from +[`Backbone.View`](http://backbonejs.org/#View-constructor). -For more detail on how to render templates, see the -[Template documentation](./template.md). +## Rendering a View -### Set How Template is Attached to the `el` +The Marionette View implements a powerful render method which, given a +[`template`](./view.rendering.md#setting-a-view-template), will build your +HTML from that template, mixing in `model` or `collection` data and any +extra [template context](./view.rendering.md#adding-context-data). -Override the view's `attachElContent` method to change how the a rendered -template is attached to the view's `el`. -This method receives one parameter - the rendered html. +Unlike `Backbone.View` Marionette defines `render` and this method should +not be overridden. To add functionality to the render use the +[`render` and `before:render` events](./events.class.md#render-and-beforerender-events). -The default implementation of `attachElContent` is: +[Live example](https://jsfiddle.net/marionettejs/dhsjcka4/) -```javascript -attachElContent(html) { - this.$el.html(html); -}, -``` +For more detail on how to render templates, see +[View Template Rendering](./view.rendering.md). -## Managing an Existing Page +### Using a View Without a Template -Marionette is able to manage pre-generated pages - either static or -server-generated - and treat them as though they were generated from Marionette. +By setting [`template` to `false`](./view.rendering.md#using-a-view-without-a-template) you can entirely disable +the view rendering and events. This may be useful for cases where you only need the `el` or have +[`prerendered content`](./dom.prerendered.md) that you do not intend to re-render. -To use the existing page, set the `el` to match the existing DOM element: +## View Lifecycle and Events -```javascript -var Mn = require('backbone.marionette'); +An instantiated `View` is aware of its lifecycle state and will throw events related to when that state changes. -var MyView = Mn.View({ - el: '#base-element' -}); +The view states indicate whether the view is rendered, attached to the DOM, or destroyed. -new myView(); -myView.isRendered(); // true if '#base-element` exists -myView.isAttached(); // true if '#base-element` is in the DOM -``` +Read More: +- [View Lifecycle](./view.lifecycle.md) +- [View DOM Change Events](./events.class.md#dom-change-events) +- [View Destroy Events](./events.class.md#destroy-events) -[Live example](https://jsfiddle.net/marionettejs/b2yz38gj/) +## Entity Events +The `View` can bind to events that occur on the attached `model` and `collection` - this +includes both [standard backbone-events](http://backbonejs.org/#Events-catalog) and custom events. +Read More: +- [Entity Events](./events.entity.md) -Marionette will [set the appropriate state of the view](./viewlifecycle.md#views-associated-with-previously-rendered-or-attached-dom). +## DOM Interactions -### Setting a `template` to `false` +In addition to what Backbone provides the views, Marionette has additional API +for DOM interactions: `events`, `triggers`, and `ui`. -Setting the `template` to `false` prevents the creation of view bindings and -does not trigger any view events. +Read More: +- [DOM Interactions](./dom.interactions.md) -```javascript -var Mn = require('backbone.marionette'); +## Behaviors -var MyView = Mn.View({ - el: '#base-element', - template: false -}); +A `Behavior` provides a clean separation of concerns to your view logic, +allowing you to share common user-facing operations between your views. -new myView(); -myView.render(); +Read More: +- [Using `Behavior`s](./marionette.behavior.md#using-behaviors) -// logs as false -myView.isRendered(); -``` +## Managing Children + +`View` provides a simple interface for managing child-views with +[`showChildView`](#showing-a-child-view), [`getChildView`](#accessing-a-child-view), and +[`detachChildView`](#detaching-a-child-view). +These methods all access `regions` within the view. +We will cover this here but for more advanced information, see the +[documentation for regions](./marionette.region.md). -## Laying Out Views - Regions +### Laying Out Views - Regions The `Marionette.View` class lets us manage a hierarchy of views using `regions`. Regions are a hook point that lets us show views inside views, manage the @@ -144,18 +137,22 @@ another view. This is especially useful for independently re-rendering chunks of your application without having to completely re-draw the entire screen every time some data is updated. -Regions can be added to a View at class definition, with `regions`, or at -runtime using `addRegion`. +Regions can be added to a View at class definition, with [`regions`](./marionette.region.md#defining-regions), +or at runtime using [`addRegion`](./marionette.region.md#adding-regions). -When you extend `View`, we use the `regions` attribute to point to the selector +When you extend `View`, we use the `regions` attribute to point to the selector where the new view will be displayed: ```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - template: '#tpl-view-with-regions', - +import _ from 'underscore'; +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + template: _.template(` +
    +
    +
    + `), regions: { firstRegion: '#first-region', secondRegion: '#second-region' @@ -163,16 +160,6 @@ var MyView = Mn.View.extend({ }); ``` -If we have the following template: - -```html - -``` - [Live example](https://jsfiddle.net/marionettejs/4e3qdgwr/) When we show views in the region, the contents of `#first-region` and @@ -180,30 +167,24 @@ When we show views in the region, the contents of `#first-region` and value in the `regions` hash is just a jQuery selector, and any valid jQuery syntax will suffice. -### Managing Sub-views - -`View` provides a simple interface for managing sub-views with -[`showChildView`](#showing-a-view) and [`getChildView`](#accessing-a-child-view). -We will cover both here but for more advanced information, see the -[documentation for regions](./marionette.region.md). - -#### Showing a View +### Showing a Child View -To show a view inside a region, simply call `showChildView(region, view)`. This +To show a view inside a region, simply call `showChildView(regionName, view)`. This will handle rendering the view's HTML and attaching it to the DOM for you: ```javascript -var Mn = require('backbone.marionette'); -var SubView = require('./subview'); +import _ from 'underscore'; +import { View } from 'backbone.marionette'; +import SubView from './subview'; -var MyView = Mn.View.extend({ - template: '#tpl-view-with-regions', +const MyView = View.extend({ + template: _.template('

    Title

    '), regions: { firstRegion: '#first-region' }, - onRender: function() { + onRender() { this.showChildView('firstRegion', new SubView()); } }); @@ -213,29 +194,30 @@ Note: If `view.showChildView(region, subView)` is invoked before the `view` has [Live example](https://jsfiddle.net/marionettejs/98u073m0/) -#### Accessing a Child View +### Accessing a Child View -To access the child view of a `View` - use the `getChildView(region)` method. +To access the child view of a `View` - use the `getChildView(regionName)` method. This will return the view instance that is currently being displayed at that region, or `null`: ```javascript -var Mn = require('backbone.marionette'); -var SubView = require('./subview'); +import _ from 'underscore'; +import { View } from 'backbone.marionette' +import SubView from './subview'; -var MyView = Mn.View.extend({ - template: '#tpl-view-with-regions', +const MyView = View.extend({ + _.template('

    Title

    '), regions: { firstRegion: '#first-region' }, - onRender: function() { + onRender() { this.showChildView('firstRegion', new SubView()); }, - onSomeEvent: function() { - var first = this.getChildView('firstRegion'); + onSomeEvent() { + const first = this.getChildView('firstRegion'); first.doSomething(); } }); @@ -245,534 +227,91 @@ var MyView = Mn.View.extend({ If no view is available, `getChildView` returns `null`. -#### Detaching a Child View +### Detaching a Child View You can detach a child view from a region through `detachChildView(region)` ```javascript - -var Mn = require('backbone.marionette'); -var SubView = require('./subview'); - -var MyView = Mn.View.extend({ - template: '#tpl-view-with-regions', - +import _ from 'underscore'; +import { View } from 'backbone.marionette' +import SubView from './subview'; + +const MyView = View.extend({ + template: _.template(` +

    Title

    +
    +
    + `), regions: { firstRegion: '#first-region', secondRegion: '#second-region' }, - onRender: function() { + onRender() { this.showChildView('firstRegion', new SubView()); }, - onMoveView: function() { - var view = this.detachChildView('firstRegion'); + onMoveView() { + const view = this.detachChildView('firstRegion'); this.showChildView('secondRegion', view); } }); ``` This is a proxy for [region.detachView()](./marionette.region.md#detaching-existing-views) +### Destroying a Child View + +There are two ways to easily destroy a child view. + +```javascript +// Directly +myChildView.getChildView('regionName').destroy(); + +// Indirectly +myChildView.getRegion('regionName').empty(); +``` + ### Region Availability Any defined regions within a `View` will be available to the `View` or any -calling code immediately after instantiating the `View`. This allows a View to -be attached to an existing DOM element in an HTML page, without the need to call -a render method or anything else, to create the regions. +calling code immediately after rendering the `View`. Using `getRegion` or any +of the child view methods above will first render the view so that the region is +available. -However, a region will only be able to populate itself if the `View` has access -to the elements specified within the region definitions. That is, if your view -has not yet rendered, your regions may not be able to find the element that -you've specified for them to manage. In that scenario, using the region will -result in no changes to the DOM. - -### Efficient Nested View Structures +## Efficient Nested View Structures When your views get some more regions, you may want to think of the most efficient way to render your views. Since manipulating the DOM is performance heavy, it's best practice to render most of your views at once. Marionette provides a simple mechanism to infinitely nest views in a single -paint: just render all of the children in the onRender callback. +paint: just render all of the children in the `onRender` callback for the +[`render` event](./events.class.md#render-and-beforerender-events). ```javascript -var Mn = require('backbone.marionette'); +import { View } from 'backbone.marionette'; -var ParentView = Mn.View.extend({ - onRender: function() { +const ParentView = View.extend({ + // ... + onRender() { this.showChildView('header', new HeaderView()); this.showChildView('footer', new FooterView()); } }); -myRegion.show(new ParentView(), options); +myRegion.show(new ParentView()); ``` -In this example, the doubly-nested view structure will be rendered in a single -paint. +In this example, the doubly-nested view structure will be rendered in a single paint. This system is recursive, so it works for any deeply nested structure. The child views you show can render their own child views within their onRender callbacks! -### Listening to Events on Children +## Listening to Events on Children Using regions lets you listen to the events that fire on child views - views attached inside a region. This lets a parent view take action depending on what -is happening in views it directly owns. - -**To see more information about events, see the [events documentation](./events.md#child-view-events)** - -## Organizing Your View - -The `View` provides a mechanism to name parts of your template to be used -throughout the view with the `ui` attribute. This provides a number of benefits: - -1. Provide a reference to commonly used UI elements -2. Cache the jQuery selector -3. Change the selector later in only one place in your view - -### Defining `ui` - -To define your `ui` hash, just set an object of key to jQuery selectors to the -`ui` attribute of your View: - -```javascript -var Mn = require('backbone.marionette'); +events are triggered in views it directly owns. -var MyView = Mn.View.extend({ - template: '#my-template', - ui: { - save: '#save-button', - close: '.close-button' - } -}); -``` - -Inside your view, the `save` and `close` references will point to the jQuery -selectors `#save-button` and `.close-button` respectively. - -### Accessing UI Elements - -To get the handles to your UI elements, use the `getUI(ui)` method: - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - template: '#my-template', - ui: { - save: '#save-button', - close: '.close-button' - }, - - onDoSomething: function() { - var saveButton = this.getUI('save'); - saveButton.addClass('disabled'); - saveButton.attr('disabled', 'disabled'); - } -}); -``` - -[Live example](https://jsfiddle.net/marionettejs/rpa58v0g/) - -As `saveButton` here is a jQuery selector, you can call any jQuery methods on -it, according to the jQuery documentation. - -#### Referencing UI in `events` and `triggers` - -The UI attribute is especially useful when setting handlers in the -[`events`](#view-events) and [`triggers`](#view-triggers) objects - simply use -the `@ui.` prefix: - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - template: '#my-template', - ui: { - save: '#save-button', - close: '.close-button' - }, - - events: { - 'click @ui.save': 'handleSave' - }, - - triggers: { - 'click @ui.close': 'close:view' - }, - - handleSave: function() { - this.model.save(); - } -}); -``` - -[Live example](https://jsfiddle.net/marionettejs/f2k0wu05/) - -In this example, when the user clicks on `#save-button`, `handleSave` will be -called. If the user clicks on `.close-button`, then the event `close:view` will -be fired on `MyView`. - -By prefixing with `@ui`, we can change the underlying template without having to -hunt through our view for every place where that selector is referenced - just -update the `ui` object. - -## Events - -Firing events on views allows you to communicate that something has happened -on that view and allowing it to decide whether to act on it or not. - -During the create/destroy lifecycle for a `View`, Marionette will call a number -of events on the view being created and attached. You can listen to these events -and act on them in two ways: - - 1. The typical Backbone manner: `view.on('render', function() {})` - 2. Overriding the onEvent listener methods: `onRender: function() {}` - -### onEvent Listeners - -Marionette creates onEvent listeners for all events fired using -`view.triggerMethod('event')` - if there is an `onEvent` method, Marionette will -call it for you. An example: - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - onRender: function() { - console.log("Fired whenever view.triggerMethod('render') is called."); - }, - - onOtherEvent: function(argument) { - console.log("Fired other:event with '" + argument + "' as an argument"); - } -}); - -var view = new MyView(); - -view.triggerMethod('other:event', 'test argument'); -``` - -[Live example](https://jsfiddle.net/marionettejs/wb95xd3m/) - -This will display in the console: -`Fired other:event with 'test argument' as an argument` - -To set up handlers for events, see the rules in the -[Documentation for Events](./events.md#magic-method-binding). - -### Lifecycle Events - -When rendering and showing a `View`, a number of events will be fired to denote -certain stages of the creation, or destruction, lifecycle have been reached. -For a full list of events, and how to use them, see the -[documentation for `View` lifecycle events](./viewlifecycle.md#view-lifecycle). - -### Binding To User Input - -Views can bind custom events whenever users perform some interaction with the -DOM. Using the view `events` and `triggers` handlers lets us either bind user -input directly to an action or fire a generic trigger that may or may not be -handled. - -#### Event and Trigger Mapping - -The `events` and `triggers` attributes bind DOM events to actions to perform on -the view. They each take a DOM event key and a mapping to the handler. - -We'll cover a simple example: - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - events: { - 'drop': 'coversEntireElement', - 'click a': 'showModal', - 'click @ui.save': 'saveForm' - }, - - triggers: { - 'click @ui.close': 'cancel:entry' - }, - - ui: { - save: '.btn-save', - close: '.btn-cancel' - }, - - showModal: function() { - console.log('Show the modal'); - }, - - saveForm: function() { - console.log('Save the form'); - } - coversEntireElement: function() { - console.log('Handle a drop event anywhere in the element'); - } -}); -``` - -Event listeners are constructed by: - -```javascript -' [dom node]': 'listener' -``` - -The `dom event` can be a jQuery DOM event - such as `click` - or another custom -event, such as Bootstrap's `show.bs.modal`. - -The `dom node` represents a jQuery selector or a `ui` key prefixed by `@.`. This -must exist inside the view once it has completed rendering. The `dom node` is -optional, and if omitted, the view's `$el` will be used as the -selector. For more information about the `ui` object, and how it works, see -[the documentation on ui](#organizing-your-view). - -#### View `events` - -The view `events` attribute binds DOM events to functions or methods on the -view. The simplest form is to reference a method on the view: - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - events: { - 'click a': 'showModal' - }, - - showModal: function(event) { - console.log('Show the modal'); - } -}); -``` - -[Live example](https://jsfiddle.net/marionettejs/jfxwtmxj/) - -The DOM event gets passed in as the first argument, allowing you to see any -information passed as part of the event. - -**When passing a method reference, the method must exist on the View.** - -The `events` attribute can also directly bind functions: - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - events: { - 'click a': function(event) { - console.log('Show the modal'); - } - } -}); -``` - -[Live example](https://jsfiddle.net/marionettejs/obt5vt09/) - -As when passing a string reference to a view method, the `events` attribute -passes in the `event` as the argument to the function called. - -#### View `triggers` - -The view `triggers` attribute binds DOM events to Marionette View events that -can be responded to at the view or parent level. For more information on events, -see the [events documentation](./events.md). This section will just -cover how to bind these events to views. - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - triggers: { - 'click a': 'link:clicked' - }, - - onLinkClicked: function(view, event) { - console.log('Show the modal'); - } -}); -``` - -[Live example](https://jsfiddle.net/marionettejs/exu2s3tL/) - -When the `a` tag is clicked here, the `link:click` event is fired. This event -can be listened to using the -[`onEvent` Binding](./events.md#onevent-binding) technique -discussed in the [events documentation](./events.md). - -The major benefit of the `triggers` attribute over `events` is that triggered -events can bubble up to any parent views. For a full explanation of bubbling -events and listening to child events, see the -[event bubbling documentation](./events.md#child-view-events). - -#### View `triggers` Event Object - -Event handlers will receive the triggering view as the first argument and the -DOM Event object as the second. It is _strongly recommended_ that View's handle -their own DOM event objects. It should be considered a best practice to not -utilize the DOM event in external listeners. - -By default all trigger events are stopped with `preventDefault` and -`stopPropagation` methods, but you can manually configure the triggers using -a hash instead of event name. The example below triggers an event and prevents -default browser behaviour using `preventDefault` method. - -```js -var MyView = Mn.View.extend({ - triggers: { - 'click a': { - event: 'link:clicked', - preventDefault: true, // this param is optional and will default to true - stopPropagation: false - } - } -}); -``` - -The default behavior for calling `preventDefault` can be changed with the feature flag [`triggersPreventDefault`](./marionette.features.md#triggerspreventdefault), and `stopPropagation` can be changed with the feature flag [`triggersStopPropagation`](./marionette.features.md#triggersstoppropagation). - -## Model and Collection events - -The Marionette View can bind to events that occur on attached models and -collections - this includes both [standard backbone-events](http://backbonejs.org/#Events-catalog) and custom events. - -### Model Events - -For example, to listen to a model's events: - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - modelEvents: { - 'change:attribute': 'actOnChange' - }, - - actOnChange: function(model, value) { - console.log('New value: ' + value); - } -}); -``` - -[Live example](https://jsfiddle.net/marionettejs/auvk4hps/) - -The `modelEvents` attribute passes through all the arguments that are passed -to `model.trigger('event', arguments)`. - -The `modelEvents` attribute can also take a -[function returning an object](basics.md#functions-returning-values). - -#### Function Callback - -You can also bind a function callback directly in the `modelEvents` attribute: - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - modelEvents: { - 'change:attribute': function() { - console.log('attribute was changed'); - } - } -}) -``` - -[Live example](https://jsfiddle.net/marionettejs/zaxLe6au/) - -### Collection Events - -Collection events work exactly the same way as [`modelEvents`](#model-events) -with their own `collectionEvents` key: - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - collectionEvents: { - sync: 'actOnSync' - }, - - actOnSync: function(collection) { - console.log('Collection was synchronised with the server'); - } -}); -``` - -[Live example](https://jsfiddle.net/marionettejs/7qyfeh9r/) - -The `collectionEvents` attribute can also take a -[function returning an object](basics.md#functions-returning-values). - -Just as in `modelEvents`, you can bind function callbacks directly inside the -`collectionEvents` object: - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - collectionEvents: { - 'update': function() { - console.log('the collection was updated'); - } - } -}); -``` - -[Live example](https://jsfiddle.net/marionettejs/ze8po0x5/) - -### Listening to Both - -If your view has a `model` and `collection` attached, it will listen for events -on both: - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - modelEvents: { - 'change:someattribute': 'changeMyAttribute' - }, - - collectionEvents: { - update: 'modelsChanged' - }, - - changeMyAttribute: function() { - console.log('someattribute was changed'); - }, - - modelsChanged: function() { - console.log('models were added or removed in the collection'); - } -}); -``` - -[Live example](https://jsfiddle.net/marionettejs/h9ub5hp3/) - -In this case, Marionette will bind event handlers to both. - -#### Destroying a View - -It's possible to manually destroy a view by calling the `destroy` method. -The method unbinds the UI elements, removes the view and its children from -the DOM and unbinds the listeners. It also triggers -[lifecycle events](viewlifecycle.md#view-destruction-lifecycle). It can be -useful in non-isolated test environments. - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - onDestroy: function() { - console.log("Fired whenever view.destroy() is called."); - }, -}); - -var myView = new MyView(); -myView.destroy(); -``` +Read More: +- [Child Event Bubbling](./events.md#event-bubbling) diff --git a/docs/routing.md b/docs/routing.md new file mode 100644 index 0000000000..0c05d59d4f --- /dev/null +++ b/docs/routing.md @@ -0,0 +1,45 @@ +# Routing in Marionette + +Users of versions of Marionette prior to v4 will notice that a router is no longer a [bundled class](./classes.md). +The [Marionette.AppRouter](https://github.com/marionettejs/marionette.approuter) was extracted +and the core library will no longer hold an opinion on routing. + +## Some Routing Solutions + +Besides the router [bundled with Backbone](http://backbonejs.org/#Router) there are many viable +routing solutions available. Some specifically designed for Backbone or Marionette and some +that are generic solutions for any framework. Here are a few of those options. + +## Marionette Community Routers + +### [Marionette.AppRouter](https://github.com/marionettejs/marionette.approuter) + +Previously bundled router. Extends [backbone.router](http://backbonejs.org/#Router) and is helpful +for breaking a large amount of routes on a single backbone.router instance into smaller more managable +approuters. + +### [Marionette.Routing](https://github.com/blikblum/marionette.routing) + +An advanced router for MarionetteJS applications. Includes nested routes, states, rendering, +async operations, lazy loading routes, Radio channel eventing, and inherits most of CherryTree +features while maintaining a similar to Marionette API. + +### [Backbone.Eventrouter](https://github.com/RoundingWellOS/backbone.eventrouter) + +A highly opinionated, simplistic Backbone.Router coupled with a Backbone.Radio.Channel. +When an event is triggered on the channel, it will set the route URL, or when a URL matches +a route it will throw an event on the channel. + +## Generic Routers + +[Stateman](https://github.com/leeluolee/stateman) +Angular-UI style routing, without the Angular + +[Cherrytree](https://github.com/QubitProducts/cherrytree) +Nested routes, like Ember, but without the transition lifecycle. + +[router.js](https://github.com/tildeio/router.js) +This is what Ember's router is built on top of. It has all of the features needed for good routing + +## Know of other routers that should be listed here? +[Add them!](https://github.com/marionettejs/backbone.marionette/edit/next/docs/routing.md) diff --git a/docs/template.md b/docs/template.md deleted file mode 100644 index e6d8c41ab0..0000000000 --- a/docs/template.md +++ /dev/null @@ -1,401 +0,0 @@ -# Template Rendering - -The Marionette View's primary purpose is to render your model and collection -data into the template you assign it. The basic syntax for setting a template -is similar to the syntax for -[Backbone.js View `template`](http://backbonejs.org/#View-template): - -```javascript -var Mn = require('backbone.marionette'); -var _ = require('underscore'); - -var MyView = Mn.View.extend({ - tagName: 'h1', - template: _.template('Contents') -}); - -var myView = new MyView(); -myView.render(); -``` - -This will cause the contents of the `template` attribute to be rendered inside -a `

    ` tag. - -[Live example](https://jsfiddle.net/marionettejs/h762zjua/) - -## Documentation Index - -* [Rendering a Template](#rendering-a-template) - * [jQuery Selector](#jquery-selector) - * [Template Function](#template-function) - * [The `getTemplate` function](#the-gettemplate-function) -* [Models and Collections](#models-and-collections) - * [Rendering a Model](#rendering-a-model) - * [Rendering a Collection](#rendering-a-collection) - * [User Interaction with Collections](#user-interaction-with-collections) - * [Model/Collection Rendering Rules](#modelcollection-rendering-rules) -* [Template Context](#template-context) - * [Context Object](#context-object) - * [Binding of `this`](#binding-of-this) -* [Serializing Model and Collection Data](#serializing-model-and-collection-data) - -## Rendering a Template - -### jQuery Selector - -If your index page contains a template element formatted for Underscore, you can -simply pass in the jQuery selector for it to `template` and Marionette will look -it up: - -```javascript -var Mn = require('backbone.marionette'); - -export.MyView = Mn.View.extend({ - template: '#template-layout' -}); -``` - -```html - -``` - -[Live example](https://jsfiddle.net/marionettejs/z4sd7ssh/) - -Marionette compiles the template above using `_.template` and renders it for -you when `MyView` gets rendered. - -### Template Function - -A more common way of setting a template is to assign a function to `template` -that renders its argument. This will commonly be the `_.template` function: - -```javascript -var _ = require('underscore'); -var Mn = require('backbone.marionette'); - -export.MyView = Mn.View.extend({ - template: _.template('

    Hello, world

    ') -}); -``` - -This doesn't have to be an underscore template, you can pass your own rendering -function: - -```javascript -var Mn = require('backbone.marionette'); -var Handlebars = require('handlebars'); - -var MyView = Mn.View.extend({ - template: function(data) { - return Handlebars.compile('

    Hello, {{ name }}')(data); - } -}); -``` - -[Live example](https://jsfiddle.net/marionettejs/ep0e4qkt/) - -Using a custom function can give you a lot of control over the output of your -view after its context is calculated. If this logic is common, you may be best -[overriding your renderer](./marionette.renderer.md) to change your default -template renderer. - -### The `getTemplate` function - -The `getTemplate` function is used to choose the template to render after the -view has been instantiated. You can use this to change the template based on -some simple logic such as the value of a specific attribute in the view's model. -The returned value can be either a jQuery selector or a compiled template -function that will be called with the view's data and context. - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - getTemplate: function(){ - if (this.model.get('is_active')){ - return '#template-when-active'; - } else { - return '#template-when-inactive'; - } - } -}); -``` - -[Live example](https://jsfiddle.net/marionettejs/9k5v4p92/) - -This differs from setting `template` as this method must be executed and -calculated when the view is rendered. If your template is always the same, use -the `template` attribute directly. - -## Models and Collections - -### Rendering a Model - -Marionette will happily render a template without a model. This won't give us a -particularly interesting result. As with Backbone, we can attach a model to our -views and render the data they represent: - -```javascript -var Bb = require('backbone'); -var Mn = require('backbone.marionette'); - -var MyModel = Bb.Model.extend({ - defaults: { - name: 'world' - } -}); - -var MyView = Mn.View.extend({ - template: _.template('

    Hello, <%- name %>

    ') -}); - -var myView = new MyView({model: new MyModel()}); -``` - -[Live example](https://jsfiddle.net/marionettejs/warfa6rL/) - -Now our template has full access to the attributes on the model passed into the -view. - -### Rendering a Collection - -The `Marionette.View` also provides a simple tool for rendering collections into -a template. Simply pass in the collection as `collection` and Marionette will -provide an `items` attribute to render: - -```javascript -var Bb = require('backbone'); -var Mn = require('backbone.marionette'); - -var MyCollection = Bb.Collection.extend({ -}); - -var MyView = Mn.View.extend({ - template: '#hello-template' -}); - -var collection = new MyCollection([ - {name: 'Steve'}, {name: 'Helen'} -]); - -var myView = new MyView({collection: collection}); -``` - -For clarity, we've moved the template into this script tag: - -```html - -``` - -[Live example](https://jsfiddle.net/marionettejs/qyodkakf/) - -As you can see, `items` is provided to the template representing each record in -the collection. - -### User Interaction with Collections - -While possible, reacting to user interaction with individual items in your -collection is tricky with just a `View`. If you want to act on individual items, -it's recommended that you use [`CollectionView`](./marionette.collectionview.md) -and handle the behavior at the individual item level. - -### Model/Collection Rendering Rules - -Marionette uses a simple method to determine whether to make a model or -collection available to the template: - -1. If `view.model` is set, the attributes from `model` -2. If `view.model` is not set, but `view.collection` is, set `items` to the - individual items in the collection -3. If neither are set, an empty object is used - -The result of this is mixed into the -[`templateContext` object](#template-context) and made available to your -template. Using this means you can setup a wrapper `View` that can act on -`collectionEvents` but will render its `model` attribute - if your `model` has -an `items` attribute then that will always be used. If your view needs to serialize -by different rules override [`serializeData()`](#serializing-model-and-collection-data). - -## Template Context - -The `Marionette.View` provides a `templateContext` attribute that is used to add -extra information to your templates. This can be either an object, or a function -returning an object. The keys on the returned object will be mixed into the -model or collection keys and made available to the template. - -### Context Object - -Using the context object, simply attach an object to `templateContext` as so: - -```javascript -var _ = require('underscore'); -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - template: _.template('

    Hello, <%- contextKey %>

    '), - - templateContext: { - contextKey: 'world' - } -}); - -var myView = new MyView(); -``` - -[Live example](https://jsfiddle.net/marionettejs/rw09r7e6/) - -The `myView` instance will be rendered without errors even though we have no -model or collection - `contextKey` is provided by `templateContext`. - -The `templateContext` attribute can also -[take a function](./basics.md#functions-returning-values). - -### Context Function - -The `templateContext` object can also be a [function returning an object](basics.md#functions-returning-values). -This is useful when you want to access [information from the surrounding view](#binding-of-this) -(e.g. model methods). - -To use a `templateContext`, simply assign a function: - -```javascript -var _ = require('underscore'); -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - template: _.template('

    Hello, <%- contextKey %>

    '), - - templateContext: function() { - return { - contextKey: this.getOption('contextKey') - } - } -}); - -var myView = new MyView({contextKey: 'world'}); -``` - -[Live example](https://jsfiddle.net/marionettejs/4qxk99ya/) - -Here, we've passed an option that can be accessed from the `templateContext` -function using `getOption()`. More information on `getOption` can be found in -the [documentation for `Marionette.Object`](./marionette.object.md#getoption). - -### Binding of `this` - -When using functions in the `templateContext` it's important to know that `this` -is _bound to the result of [`serializeData()`](#serializing-model-and-collection-data) and **not the view**_. An -illustrative example: - -```javascript -var _ = require('underscore'); -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - template: _.template('

    Hello, <%- contextKey() %>

    '), - - templateContext: { - contextKey: function() { - return this.getOption('contextKey'); // ERROR - } - } -}); - -var myView = new MyView({contextKey: 'world'}); -``` - -The above code will fail because the context object in the template -_cannot see_ the view's `getOption`. This would also apply to functions -returned by a `templateContext` function, even though the function itself is -bound to the view context. The following example should provide some clarity: - -```javascript -var _ = require('underscore'); -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - template: _.template('

    Hello, <%- contextKey() %>

    '), - - templateContext: function() { - return { - contextKeyVar: this.getOption('contextKey'), // OK - "this" refers to view - contextKeyFunction: function() { - return this.getOption('contextKey'); // ERROR - "this" refers to data - } - }; - - } -}); - -var myView = new MyView({contextKey: 'world'}); -``` - -[Live example](https://jsfiddle.net/marionettejs/cwt31k9p/1/) - -## Serializing Model and Collection Data - -The `serializeData` method is used to convert a View's `model` or `collection` -into a usable form for a template. It follows the [Model/Collection Rendering Rules](#modelcollection-rendering-rules) -to determine how to serialize the data. - -The result of `serializeData` is included in the data passed to -the view's template. - -Let's take a look at some examples of how serializing data works. - -```javascript -var myModel = new MyModel({foo: 'bar'}); - -new MyView({ - template: '#myItemTemplate', - model: myModel -}); - -MyView.render(); -``` - -```html - -``` - -[Live example](https://jsfiddle.net/marionettejs/brp0t7pq/) - -If the serialization is a collection, the results are passed in as an -`items` array: - -```javascript -var myCollection = new MyCollection([{foo: 'bar'}, {foo: 'baz'}]); - -new MyView({ - template: '#myCollectionTemplate', - collection: myCollection -}); - -MyView.render(); -``` - -```html - -``` - -[Live example](https://jsfiddle.net/marionettejs/yv3hrvkf/) - -If you need to serialize the View's `model` or `collection` in a custom way, -then you should override either `serializeModel` or `serializeCollection`. - -On the other hand, you should not use this method to add arbitrary extra data -to your template. Instead, use [View.templateContext](./template.md#templatecontext). diff --git a/docs/upgrade.md b/docs/upgrade.md index 67892a5389..57c3295e5d 100644 --- a/docs/upgrade.md +++ b/docs/upgrade.md @@ -77,7 +77,7 @@ parent. These can be chained all the way up to the level you require them to be. Bubbled child events no longer pass the `childView` implicitly and only pass the arguments passed as part of `triggerMethod`. This means that the arguments passed to `onEvent` and `onChildviewEvent` are now identical. See the -[documentation on event lifecycles](./viewlifecycle.md) for more information. +[documentation on event lifecycles](./view.lifecycle.md) for more information. In Marionette 2, `childEvents` were bound on every event. In Marionette 3, `childViewEvents` are bound once and cached. This means that you cannot add new @@ -175,7 +175,7 @@ In Marionette 3, the HTML will be: The arguments for a number of lifecycle events were changed. For consistency, all events will now receive the view that is emitting the event as the first -argument. See the [documentation for view lifecycles](./viewlifecycle.md) for +argument. See the [documentation for view lifecycles](./view.lifecycle.md) for more complete information. #### Upgrading to Marionette 3 @@ -272,7 +272,7 @@ in Marionette to manage the `CollectionView` children. The main difference between Babysitter and the Marionette implementation is the removal of `.call` and `.apply` on `CollectionView.children`. Instead you should use `.invoke` or -[any of the methods provided](./marionette.collectionviewadvanced.md#collectionview-childview-iterators-and-collection-functions). +[any of the methods provided](./marionette.collectionview.md#collectionview-childview-iterators-and-collection-functions). For example: diff --git a/docs/view.lifecycle.md b/docs/view.lifecycle.md new file mode 100644 index 0000000000..d95c769112 --- /dev/null +++ b/docs/view.lifecycle.md @@ -0,0 +1,211 @@ +# View Lifecycle + +Both [`View` and `CollectionView`](./classes.md) are aware of their lifecycle state +which indicates if the view is rendered, attached to the DOM or destroyed. + +## Documentation Index + +* [View Lifecycle](#view-lifecycle) +* [Lifecycle State Methods](#lifecycle-state-methods) + * [`isRendered()`](#isrendered) + * [`isAttached()`](#isattached) + * [`isDestroyed()`](#isdestroyed) +* [Instantiating a View](#instantiating-a-view) + * [Using `setElement`](#using-setelement) +* [Rendering a View](#rendering-a-view) + * [`View` Rendering](#view-rendering) + * [`CollectionView` Rendering](#collectionview-rendering) +* [Rendering Children](#rendering-children) +* [Attaching a View](#attaching-a-view) +* [Detaching a View](#detaching-a-view) +* [Destroying a View](#destroying-a-view) +* [Destroying Children](#rendering-children) + +## Lifecycle State Methods + +Both `View` and `CollectionView` share methods for checking lifecycle state. + +### `isRendered()` + +Returns a boolean value reflecting if the view is considered rendered. + +### `isAttached()` + +Returns a boolean value reflecting if the view is considered attached to the DOM. + +### `isDestroyed()` + +Returns a boolean value reflecting if the view has been destroyed. + +## Instantiating a View + +Marionette Views are Backbone Views and so when they are instantiated the view +has an `el`. That `el` will be the root node for the view and other than its contents it +will not change for the life of the view unless directly manipulated (ie: `view.$el.addClass`) + +The view can be passed an existing `el` either in the DOM (ie: `el: $('.foo-selector')`) +or in memory (ie: `el: $('
    ')`) or most commonly, the view constructs +its own `el` at instantiation as [documented on backbonejs.org](http://backbonejs.org/#View-el). + +Marionette will determine the initial state of the view as to whether the view is considered +already [rendered](#rendering-a-view) or [attached](#attaching-a-view). If a view is already +rendered or attached its [state](#lifecycle-state-methods) will reflect that status, but the +[related events](./events.class.md#dom-change-events) will not have fired. + +For more information on instanting a view with pre-rendered DOM see: [Prerendered Content](./dom.prerendered.md). + +### Using `setElement` + +`Backbone.View` allows the user to change the view's `el` after instantiaton using +[`setElement`](http://backbonejs.org/#View-setElement). This method can be used in Marionette +as well, but should be done with caution. `setElement` will redelegate view events, but it will +essentially ignore children of the view, whether through `regions` or through `children` and the +view's `behaviors` will also be unaware of the change. It is likely better to reconstuct a new +view with the new `el` than to try to change the `el` of an existing view. + +## Rendering a View + +In Marionette [rendering a view](./view.rendering.md) is changing a view's `el`'s contents. + +What rendering indicates varies slightly between the two Marionette views. + +**Note** Once a view is considered "rendered" it cannot be unrendered until it is [destroyed](#destroying-a-view). + +### `View` Rendering + +For [`View`](./marionette.view.md), rendering entails serializing the view's data, passing it to a template, +and taking the results of that template and replacing the contents of the view's `el`. So when a `View` is +instantiated it is considered rendered if the `el` node contains any content. However after instantiation +a template may render empty in which case the `View` will still be considered "rendered" even though it +contains no content. + +### `CollectionView` Rendering + +For [`CollectionView`](./marionette.collectionview.md), rendering signifies that the view's +[`children`](./marionette.collectionview.md#collectionviews-children) were created and attached to the +view's `el`. So unlike `View` a `CollectionView` can be instantiated with content in its `el`, but until +the `children` are "rendered" the entire view is not considered rendered. + +Notably if there are no `children` when rendering, the view will still be considered rendered. This is +true whether or not an [`emptyView`](./marionette.collectionview.md#collectionviews-emptyview) is rendered. +So it is possible for a `CollectionView` to be "rendered" but the `el` to only be an empty tag. +Also note that just like `View` a `CollectionView` may have a `template` which is rendered and attached to +the `el` during the `render`, but the template rendering itself has no bearing on the status of the `CollectionView`. + +## Rendering Children + +Rendering child views is often best accomplish after the view render as typically the first render happens prior to +the view entering the DOM. This helps to prevent unnecessary repaints and reflows by making the DOM insert at the +highest possible view in the view tree. + +The exception is views with [prerendered content](./dom.prerendered.md). In the case that the view is instantiated +rendered, child views are best managed in the view's [`initialize`](./common.md#initialize). + +### `View` Children + +In general the best method for adding a child view to a `View` is to use [`showChildView`](./marionette.view.md#showing-a-view) +in the [`render` event](./events.class.md#render-and-beforerender-events). + +View regions will be emptied on each render so views shown outside of the `render` event will still need be reshown +on subsequent renders. + +### `CollectionView` Children + +The primary use case for a `CollectionView` is maintaining child views to match the state of a Backbone Collection. +By default children will be added or removed to match the models within the collection. +However a `CollectionView` can have children in addition to, or instead of, views matching the `collection`. + +#### Adding managed children + +If you add a view to a `CollectionView`s children by default it will treat it as any other view added from the `collection`. +This means it is subject to the [`viewComparator`](./marionette.collectionview.md#defining-the-viewcomparator) and +[`viewFilter`](./marionette.collectionview.md#defining-the-viewfilter). + +So if you are accounting for added views in your `viewFilter` and `viewComparator` the best place to add these children is +likely in the [`render` event](./events.class.md#render-and-beforerender-events) as the views will only be added once +(or re-added if the children are rebuilt in a subsequent `render`) and managed in the sort or filter as the `collection` is updated. + +#### Adding unmanaged children + +Unlike managed children there may be cases where you want to insert views to the results of the `CollectionView` after the +`collection` changes, or after sorting and/or filtering. In these cases the solution might depend slightly on the features +used on the `CollectionView`. + +The goal will be to add the unmanaged views after other views are added and to remove any unmanaged views prior to any +managed `children` changes. To do so you must understand which [`CollectionView` event](./events.class.md#collectionview-events) +will occur prior to changes to the `children` for your particular use case. By default a `CollectionView` sorts according +to the `collection` sort, so unless `viewComparator` is disabled, the best event for removing unmanaged views is the +[`before:sort` event](./events.class.md#sort-and-beforesort-events), but if `viewComparator` is false the next event +to consider is the [`before:filter` event](./events.class.md#filter-and-beforefilter-events) if your `CollectionView` has +a `viewFilter`, otherwise the [`before:render:children` event](./events.class.md#renderchildren-and-beforerenderchildren-events) +is ideal. + +Once you have determined the best strategy for removing your unmanaged child views, adding them is best handled in the +[`render:children` event](./events.class.md#renderchildren-and-beforerenderchildren-events). Additionally adding a child +with `addChildView` will itself cause these events to occur, so to prevent stack overflows, it is best to use a flag to guard +the adds and to insert a new view at a specified index. + +The following simplistic example will add an unmanaged view at the 5th index and remove it prior to any changes to the `children`. +In a real world scenario it will likely be more complicated to keep track of which view to remove in the `onBeforeSort`. + +```javascript +import { CollectionView } from 'backbone.marionette'; + +const MyCollectionView = CollectionView.extend({ + childView: MyChildView, + onBeforeSort() { + this.removeChildView(this.children.findByIndex(5)); + }, + onRenderChildren() { + this.addFooView(); + }, + addFooView() { + if (this.addingFooView) { + return; + } + + this.addingFooView = true; + this.addChildView(new FooView(), 5); + this.addingFooView = false; + } +}); +``` + +## Attaching a View + +In Marionette a view is attached if the view's `el` can be found in the DOM. +The best time to add listeners to the view's `el` is likely in the [`attach` event](./events.class.md#attach-and-beforeattach-events). + +While the `el` of the view can be attached the contents of the view can be removed and added to +during the lifetime of the view. If you are adding listeners to the contents of the view rather than +`attach` the [`dom:refresh` event](./events.class.md#domrefresh-event) would be best. + +The attached state is maintained when attaching a view with a `Region` or as a child of a `CollectionView` +or during [view instantiation](#instantiating-a-view). +If a view is attached by other means like `$.append` [`isAttached`] may not reflect the actual state of attachment. + +## Detaching a View + +A view is detached when its `el` is removed from the DOM. +The best time to clean up any listeners added to the `el` is in the [`before:detach` event](./events.class.md#detach-and-beforedetach-events). + +While the `el` of the view may remain attached, its contents will be removed on render. +If you have added listeners to the contents of the view rather than `before:detach` the +[`dom:remove` event](./events.class.md#domremove-event) would be best. + +## Destroying a View + +Destroying a view (ie: `myView.destroy()`) cleans up anything constucted within Marionette so that if +a view's instance is no longer referenced the view can be cleaned up by the browser's garbage collector. + +The [`before:destroy` event](./events.class.md#destroy-and-beforedestroy-events) is the best place to clean +up any added listeners not related to the view's DOM. + +The state of the view after the destroy is not attached and not rendered although the `el` is not emptied. + +## Destroying Children + +Children added to a `View`'s region or through a `CollectionView` will be automatically destroyed if the +view is re-rendered, if the view is destroyed, or for `CollectionView` if the `collection` is reset. + +**Note** Children are removed after the DOM detach of the parent to prevent multiple reflows or repaints. diff --git a/docs/view.rendering.md b/docs/view.rendering.md new file mode 100644 index 0000000000..e6202d77a2 --- /dev/null +++ b/docs/view.rendering.md @@ -0,0 +1,490 @@ +# View Template Rendering + +Unlike [`Backbone.View`](http://backbonejs.org/#View-template), [Marionette views](./classes.md) +provide a customizable solution for rendering a template with data and placing the +results in the DOM. + +```javascript +import _ from 'underscore'; +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + tagName: 'h1', + template: _.template('Contents') +}); + +const myView = new MyView(); +myView.render(); +``` + +In the above example the contents of the `template` attribute will be rendered inside +a `

    ` tag available at `myView.el`. + +[Live example](https://jsfiddle.net/marionettejs/h762zjua/) + +## Documentation Index + +* [What is a template](#what-is-a-template) +* [Setting a View Template](#setting-a-view-template) + * [Using a View Without a Template](#using-a-view-without-a-template) +* [Rendering the Template](#rendering-the-template) + * [Using a Custom Renderer](#using-a-custom-renderer) + * [Rendering to HTML](#rendering-to-html) + * [Rendering to DOM](#rendering-to-dom) +* [Serializing Data](#serializing-data) + * [Serializing a Model](#serializing-a-model) + * [Serializing a Collection](#serializing-a-collection) + * [Serializing with a `CollectionView`](#serializing-with-a-collectionview) +* [Adding Context Data](#adding-context-data) + * [What is Context Data?](#what-is-context-data) + +## What is a template? + +A template is a function that given data returns either an HTML string or DOM. +[The default renderer](#rendering-the-template) in Marionette expects the template to +return an HTML string. Marionette's dependency Underscore comes with an HTML string +[template compiler](http://underscorejs.org/#template). + +```javascript +import _ from 'underscore'; +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + template: _.template('

    Hello, world

    ') +}); +``` +This doesn't have to be an underscore template, you can pass your own rendering +function: + +```javascript +import Handlebars from 'handlebars'; +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + template: Handlebars.compile('

    Hello, {{ name }}') +}); +``` + +[Live example](https://jsfiddle.net/marionettejs/ep0e4qkt/) + +## Setting a View Template + +Marionette views use the `getTemplate` method to determine which template to use for +rendering into its `el`. By default `getTemplate` is predefined on the view as simply: + +```javascript +getTemplate() { + return this.template +} +``` + +In most cases by using the default `getTemplate` you can simply set the `template` on the +view to define the view's template, but in some circumstances you may want to set the template +conditionally. + +```javascript +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + template: _.template('Hello World!'), + getTemplate() { + if (this.model.has('user')) { + return _.template('Hello User!'); + } + + return this.template; + } +}); +``` + +[Live example](https://jsfiddle.net/marionettejs/9k5v4p92/) + +### Using a View Without a Template + +By default `CollectionView` has no defined `template` and will only attempt to render the `template` +if one is defined. For `View` there may be some situations where you do not intend to use a `template`. +Perhaps you only need the view's `el` or you are using [prerendered content](./dom.prerendered.md). + +In this case setting `template` to `false` will prevent the template render. In the case of `View` +it will also prevent the [`render` events](./events.class.md#render-and-beforerender-events). + +```javascript +import { View } from 'backbone.marionette'; + +const MyIconButtonView = View.extend({ + template: false, + tagName: 'button', + className: '.icon-button', + triggers: { + 'click': 'click' + }, + onRender() { + console.log('You will never see me!'); + } +}); +``` + +## Rendering the Template + +Each view class has a renderer which by default passes the [view data](#serializing-data) +to the template function and returns the html string it generates. + +The current default renderer is essentially the following: +```javascript +import { View, CollectionView } from 'backbone.marionette'; + +function renderer(template, data) { + return template(data); +} + +View.setRenderer(renderer); +CollectionView.setRenderer(renderer); +``` + +Previous to Marionette v4 the default renderer was the `TemplateCache`. This renderer has been extracted +to a separate library: https://github.com/marionettejs/marionette.templatecache and can be used with v4. + +### Using a Custom Renderer + +You can set the renderer for a view class by using the class method `setRenderer`. +The renderer accepts two arguments. The first is the template passed to the view, +and the second argument is the data to be rendered into the template. + +Here's an example that allows for the `template` of a view to be an underscore template string. + +```javascript +import _ from 'underscore'; +import { View } from 'backbone.marionette'; + +View.setRenderer(function(template, data) { + return _.template(template)(data); +}); + +const myView = new View({ + template: 'Hello <%- name %>!', + model: new Backbone.Model({ name: 'World' }) +}); + +myView.render(); + +// myView.el is
    Hello World!
    +``` + +The renderer can also be customized separately on any extended View. + +```javascript +const MyHBSView = View.extend(); + +// Similar example as above but for handlebars +MyHBSView.setRenderer(function(template, data) { + return Handlebars.compile(template)(data); +}); + +const myHBSView = new MyHBSView({ + template: 'Hello {{ name }}!', + model: new Backbone.Model({ name: 'World' }) +}); + +myHBSView.render(); + +// myView.el is
    Hello World!
    +``` + +**Note** These examples while functional may not be ideal. If possible it is recommend to +precompile your templates which can be done for a number of templating using various plugins +for bundling tools such as [Browserify or Webpack](./installation.md). + +### Rendering to HTML + +The default Marionette renders return the HTML as a string. This string is passed to the view's +`attachElContents` method which in turn uses the DOM API's [`setContents`](./dom.api.md#setcontentsel-html). +to set the contents of the view's `el` with DOM from the string. + +#### Customizing `attachElContents` + +You can modify the way any particular view attaches a compiled template to the `el` by overriding `attachElContents`. +This method receives only the results of the view's renderer and is only called if the renderer returned a value. + +For instance, perhaps for one particular view you need to bypass the [DOM API](./dom.api.md) and set the html directly: + +```javascript +attachElContent(html) { + this.el.innerHTML = html; +} +``` + +### Rendering to DOM + +Marionette also supports templates that render to DOM instead of html strings by using a custom render. + +In the following example the `template` method passed to the renderer will return a DOM element, and then +if the view is already rendered utilize [morphdom](https://github.com/patrick-steele-idem/morphdom) to patch +the DOM or otherwise it will set the view's `el` to the result of the template. (Note in this case the view's +`el` created at instantiation would be overridden). + +```javascript +import morphdom from 'morphdom'; +import { View } from 'backbone.marionette'; + +const VDomView = View.extend(); + +VDomView.setRenderer(function(template, data) { + const el = template(data); + + if (this.isRendered()) { + // Patch the view's el contents in the DOM + morphdom(this.el, el, { childrenOnly: true }); + return; + } + + this.setElement(el.cloneNode(true)); +}); +``` + +In this case because the renderer is modifying the `el` directly, there is no need to return the result +of the template rendering for the view to handle in [`attachElContents`](#customizing-attachelcontents). +It is certainly an option to return the compiled DOM and modify [`attachElContents`](#customizing-attachelcontents) +to handle a DOM object instead of a string literal, but in many cases it may be overcomplicated to do so. + +There are a variety of possibilities for rendering with Marionette. If you are looking into alternatives +from the default this may be a useful resource: https://github.com/blikblum/marionette.renderers#renderers + +## Serializing Data + +Marionette will automatically serialize the data from its `model` or `collection` for the template to use +at [rendering](#rendering-the-template). You can override this logic and provide serialization of other +data with the `serializeData` method. The method is called with no arguments, but has the context of the +view and should return a javascript object for the template to consume. If `serializeData` does not return +data the template may still receive [added context](#adding-context-data) or an empty object for rendering. + +```javascript +import _ from 'underscore'; +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + template: _.template(` +
    <% user.name %>
    +
      + <% _.each(groups, function(group) { %> +
    • <%- group.name %>
    • + <% }) %> +
    + `), + serializeData() { + // For this view I need both the + // model and collection serialized + return { + user: this.serializeModel(), + groups: this.serializeCollection(), + }; + } +}); +``` + +**Note** You should not use this method to add arbitrary extra data to your template. +Instead use `templateContext` to [add context data to your template](#adding-context-data). + +### Serializing a Model + +If the view has a `model` it will pass that model's attributes +to the template. + +```javascript +import _ from 'underscore'; +import Backbone from 'backbone'; +import { View } from 'backbone.marionette'; + +const MyModel = Backbone.Model.extend({ + defaults: { + name: 'world' + } +}); + +const MyView = View.extend({ + template: _.template('

    Hello, <%- name %>

    ') +}); + +const myView = new MyView({ model: new MyModel() }); +``` + +[Live example](https://jsfiddle.net/marionettejs/warfa6rL/) + +How the `model` is serialized can also be customized per view. + +```javascript +import _ from 'underscore'; +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + serializeModel() { + const data = _.clone(this.model.attributes); + + // serialize nested model data + data.sub_model = data.sub_model.attributes; + + return data; + } +}); +``` + +### Serializing a Collection + +If the view does not have a `model` but has a `collection` the collection's models will +be serialized to an array provided as an `items` attribute to the template. + +```javascript +import _ from 'underscore'; +import Backbone from 'backbone'; +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + template: _.template(` +
      + <% _.each(items, function(item) { %> +
    • <%- item.name %>
    • + <% }) %> +
    + `) +}); + +const collection = new Backbone.Collection([ + {name: 'Steve'}, {name: 'Helen'} +]); + +const myView = new MyView({ collection }); +``` + +[Live example](https://jsfiddle.net/marionettejs/qyodkakf/) + +How the `collection` is serialized can also be customized per view. + +```javascript +import _ from 'underscore'; +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + serializeCollection() { + return _.map(this.collection.models, model => { + const data = _.clone(model.attributes); + + // serialize nested model data + data.sub_model = data.sub_model.attributes; + + return data; + }); + } +}); +``` + +### Serializing with a `CollectionView` + +if you are using a `template` with a `CollectionView` that is not also given a `model`, your `CollectionView` +will [serialize the collection](serializing-a-collection) for the template. This could be costly and unnecessary. +If your `CollectionView` has a `template` it is advised to either use an empty `model` or override the +[`serializeData`](#serializing-data) method. + +## Adding Context Data + +Marionette views provide a `templateContext` attribute that is used to add +extra information to your templates. This can be either an object, or a function +returning an object. The keys on the returned object will be mixed into the +model or collection keys and made available to the template. + +```javascript +import _ from 'underscore'; +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + template: _.template('

    Hello, <%- name %>

    '), + templateContext: { + name: 'World' + } +}); +``` + +Additionally context data overwrites the serialized data + +```javascript +import _ from 'underscore'; +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + template: _.template('

    Hello, <%- name %>

    '), + templateContext() { + return { + name: this.model.get('name').toUpperCase() + }; + } +}); +``` + +You can also define a template context value as a method. How this method is called is determined +by your templating solution. For instance with handlebars a method is called with the context of +the data passed to the template. + +```javascript +import Handlebars from 'handlebars'; +import Backbone from 'backbone'; +import { View } from 'backbone.marionette'; + +const MyView = View.extend({ + template: Handlebars.compile(` + Hello {{ fullName }}, + `), + templateContext: { + isDr() { + return (this.degree) === 'phd'; + }, + fullName() { + // Because of Handlebars `this` here is the data object + // passed to the template which is the result of the + // templateContext mixed with the serialized data of the view + return this.isDr() ? `Dr. { this.name }` : this.name; + } + } +}); + +const myView = new MyView({ + model: new Backbone.Model({ degree: 'masters', name: 'Joe' }); +}); +``` + +**Note** the data object passed to the template is not deeply cloned and in some cases is not cloned at all. +Take caution when modifying the data passed to the template, that you are not also modifying your model's +data indirectly. + +### What is Context Data? + +While [serializing data](#serializing-data) deals more with getting the data belonging to the view +into the template, template context mixes in other needed data, or in some cases, might do extra +computations that go beyond simply "serializing" the view's `model` or `collection` + +```javascript +import _ from 'underscore' +import { CollectionView } from 'backbone.marionette'; +import GroupView from './group-view'; + +const MyCollectionView = CollectionView.extend({ + tagName: 'div', + childViewContainer: 'ul', + childView: GroupView, + template: _.template(` +

    Hello <% name %> of <% orgName %>

    +
    You have <% stats.public %> group(s).
    +
    You have <% stats.private %> group(s).
    +

    Groups:

    +
      + `), + templateContext() { + const user = this.model; + const organization = user.getOrganization(); + const groups = this.collection; + + return { + orgName: organization.get('name'), + name: user.getFullName(), + stats: groups.countBy('type') + }; + } +}) +``` diff --git a/docs/viewlifecycle.md b/docs/viewlifecycle.md deleted file mode 100644 index 1d150b6db8..0000000000 --- a/docs/viewlifecycle.md +++ /dev/null @@ -1,689 +0,0 @@ -# View Lifecycle - -The Marionette views use an event lifecycle, triggering events on any listeners -to act at different points inside the creation and destruction of views and -their children. - -## Documentation Index - -* [`View` Lifecycle](#view-lifecycle) - * [View Creation Lifecycle](#view-creation-lifecycle) - * [View Destruction Lifecycle](#view-destruction-lifecycle) - * [View Creation Events](#view-creation-events) - * [View Destruction Events](#view-destruction-events) - * [Other View Events](#other-view-events) -* [`CollectionView` Lifecycle](#collectionview-lifecycle) - * [CollectionView Creation Lifecycle](#collectionview-creation-lifecycle) - * [CollectionView Destruction Lifecycle](#collectionview-destruction-lifecycle) - * [CollectionView Creation Events](#collectionview-creation-events) - * [CollectionView Destruction Events](#collectionview-destruction-events) - * [CollectionView EmptyView Events](#collectionview-emptyview-events) -* [Lifecycle State Methods](#lifecycle-state-methods) - * [`isRendered()`](#isrendered) - * [`isAttached()`](#isattached) -* [Views associated with previously rendered or attached DOM](#views-associated-with-previously-rendered-or-attached-dom) -* [`Region`s and the View Lifecycle](#regions-and-the-view-lifecycle) - * [Show View Events](#show-view-events) - * [Empty Region Events](#empty-region-events) -* [Advanced Event Settings](#advanced-event-settings) - -## `View` Lifecycle -Marionette views define a number of events during the creation and destruction -lifecycle - when the view is displayed in and emptied from a region. In the -documentation, we will reference the event name, though -[`onEvent` handling](./events.md#onevent-binding) can be used. - -All automatically fired events pass the triggering view to all event handlers as -the first argument. - -### View Creation Lifecycle - -When a view is initialized and then displayed inside a region (using -`showChildView()`) a set of events will be called in a specific order. - -| Order | Event | Arguments | -| :---: |-----------------|------------------------------| -| 1 | `before:render` | `view` - view being rendered | -| 2 | `render` | `view` - view being rendered | -| 3* | `before:attach` | `view` - view being attached | -| 4* | `attach` | `view` - view being attached | -| 5* | `dom:refresh` | `view` - view being rendered | - -The events marked with "\*" only fire if/when the region's `el` is attached to the DOM. - -### View Destruction Lifecycle - -When `region.empty()` is called, the view will be destroyed, calling events as -part of the destruction lifecycle. - -| Order | Event | Arguments | -| :---: |-------------------|-------------------------------------------| -| 1 | `before:destroy` | `view` - view being destroyed | -| | | `...args` - arguments passed to `destroy` | -| 2* | `before:detach` | `view` - view being detached | -| 3* | `dom:remove` | `view` - view being detached | -| 4* | `detach` | `view` - view being detached | -| 5 | `destroy` | `view` - view being destroyed | -| | | `...args` - arguments passed to `destroy` | - -The events marked with "\*" only fire if/when the view was attached to the DOM. - -#### ChildView Destruction Lifecycle - -The order of the destruction events is dependent on when the view (or a parent view) -is detached. When a parent attached view is destroyed it will receive the events -as listed above, but its children will receive both detach events first when the parent -is detached and the children will be destroyed after the detach is complete. - -| Order | Event | Arguments | -| :---: |-------------------|-------------------------------------------| -| 1 | `before:detach` | `view` - view being detached | -| 2* | `dom:remove` | `view` - view being detached | -| 3* | `detach` | `view` - view being detached | -| 4* | `before:destroy` | `view` - view being destroyed | -| | | `...args` - arguments passed to `destroy` | -| 5 | `destroy` | `view` - view being destroyed | -| | | `...args` - arguments passed to `destroy` | - -The events marked with "\*" only fire if/when the view was attached to the DOM. - -### View Creation Events - -These events are fired during the view's creation and rendering in a region. - -#### View `before:render` - -Triggered before a View is rendered. - -```javascript -var Mn = require('backbone.marionette'); - -Mn.View.extend({ - onBeforeRender: function() { - // set up final bits just before rendering the view's `el` - } -}); -``` - -#### View `render` - -This is the optimal event for handling child views. - -Triggered after the view has been rendered. -You can implement this in your view to provide custom code for dealing -with the view's `el` after it has been rendered. - -```javascript -var Mn = require('backbone.marionette'); - -Mn.View.extend({ - onRender: function() { - console.log('el exists but is not visible in the DOM'); - } -}); -``` - -#### View `before:attach` - -Triggered after the View has been rendered but just before it is first bound -into the page DOM. This will only be triggered once per `region.show()`. If -you are re-rendering your view after it has been shown, you most likely want to -listen to the `render` or `dom:refresh` events. - -#### View `attach` - -This is the optimal event to handle when the view's `el` must be in the DOM. -Clean up any added handlers in [`before:detach`](#view-beforedetach). - -Triggered once the View has been bound into the DOM. This is only triggered -once - the first time the View is attached to the DOM. If you are re-rendering -your view after it has been shown, you most likely want to listen to the -`dom:refresh` event. - -#### View `dom:refresh` - -This is the optimal event to handle when the view's contents must be in the DOM. -Clean up any added handlers in [`dom:remove`](#view-domremove). - -The `dom:refresh` event is fired in two separate places: - -1. After the view is attached to the DOM (after the `attach` event) -2. Every time the `render` method is called - -```javascript -const myView = new Mn.View({ - template: _.template('<%= count %>'), - templateContext: function() { - this.count = (this.count || 0) + 1; - return { - count: this.count - }; - }, - - onRender: function() { - console.log('render'); - }, - - onAttach: function() { - console.log('attach'); - }, - - onDomRefresh: function() { - console.log('dom:refresh'); - } -}); - -// some layout view -layout.showChildView('myRegion', myView); -/* - Output: - render - attach - dom:refresh -*/ - -myView.render(); -/* - Output: - render - dom:refresh -*/ -``` - -### View Destruction Events - -These events are fired during the view's destruction and removal from a region. - -#### View `before:destroy` - -Triggered just prior to destroying the view, when the view's `destroy()` method has been called. -The view may or may not be in the DOM at this point. - -```javascript -var Mn = require('backbone.marionette'); - -Mn.View.extend({ - onBeforeDestroy: function() { - // custom destroying and non-DOM related cleanup goes here - } -}); -``` - -#### View `before:detach` - -This is the optimal event for cleaning up anything added in [`onAttach`](#view-attach). - -The `View` will trigger the `before:detach` event when the view is rendered and -is about to be removed from the DOM. -If the view has not been attached to the DOM, this event will not be fired. - -```javascript -var Mn = require('backbone.marionette'); - -Mn.View.extend({ - onBeforeDetach: function() { - // custom destroying and DOM related cleanup goes here - } -}); -``` - -#### View `detach` - -The `View` will trigger the `detach` event when the view was rendered and has -just been removed from the DOM. - -#### View `dom:remove` - -This is the optimal event for cleaning up anything added in [`onDomRefresh`](#view-domrefresh). - -The `dom:remove` event is fired in two separate places: - -1. Before the view is detached from the DOM (after the `before:detach` event) -2. Each time the `render` method is called if the view is already rendered. - -```javascript -const myView = new Mn.View({ - template: _.template('<%= count %>'), - templateContext: function() { - this.count = (this.count || 0) + 1; - return { - count: this.count - }; - }, - - onBeforeRender: function() { - console.log('before:render'); - }, - - onRender: function() { - console.log('render'); - }, - - onBeforeDetach: function() { - console.log('before:detach'); - }, - - onDetach: function() { - console.log('detach'); - }, - - onDomRemove: function() { - console.log('dom:remove'); - } -}); - -// some layout view -layout.showChildView('myRegion', myView); - -myView.render(); -/* - Output: - before:render - dom:remove - render -*/ - -myView.destroy(); -/* - Output: - before:detach - dom:remove - detach -*/ -``` - -#### View `destroy` - -Triggered just after the view has been destroyed. At this point, the view has -been completely removed from the DOM. - -### Other View Events - -These events are fired on specific actions performed on the view. - -#### View `before:add:region` - -When you add a region to a view - through `addRegion()` - the -`before:add:region` event will fire just before the region is actually added. - -#### View `add:region` - -When a region is added to a view - through `addRegion()` - the `add:region` -event will be fired. This happens just after the region is added and is -available to use on the view. - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - onAddRegion: function(name, region) { - console.log('Region called ' + name + ' was added'); - } -}); - -var myView = new MyView(); -myView.addRegion('regionName', '#selector'); -``` - -#### View `before:remove:region` - -The `View` will trigger a `before:remove:region` -event before a region is removed from the view. -This allows you to perform any cleanup operations before the region is removed. - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.View.extend({ - onBeforeRemoveRegion: function(name) { - console.log('Region called ' + name + ' is about to be removed'); - }, - - regions: { - regionName: '#selector' - } -}); - -var myView = new MyView(); -myView.removeRegion('foo'); -``` - -#### View `remove:region` - -The `View` will trigger a `remove:region` -event when a region is removed from the view. -This allows you to use the region instance one last -time, or remove the region from an object that has a -reference to it: - -```javascript -var Mn = require('backbone.marionette'); - -var view = new Mn.View(); - -view.on('remove:region', function(name, region) { - // add the region instance to an object - delete myObject[name]; -}); - -view.addRegion('foo', '#bar'); - -view.removeRegion('foo'); -``` - -## `CollectionView` Lifecycle - -The `CollectionView` has its own lifecycle around the standard `View` event -rendering lifecycle. This section covers the events that get triggered and what -they indicate. - -### CollectionView Creation Lifecycle - -The `CollectionView` creation lifecycle can go down two paths depending on -whether the collection is populated or empty. The below table shows the order of -rendering events firing: - -| Order | Event | Arguments | -| :---: |--------------------------|---------------------------------------------------| -| 1* | `before:render` | `collectionview` - collection view being rendered | -| 2 | `before:remove:child` | `collectionview` - collection view being rendered | -| | | `child` - child view being rendered | -| 3 | `remove:child` | `collectionview` - collection view being rendered | -| | | `child` - child view being rendered | -| 4 | `before:add:child` | `collectionview` - collection view being rendered | -| | | `child` - child view being rendered | -| 5 | `add:child` | `collectionview` - collection view being rendered | -| | | `child` - child view being rendered | -| | | `view` - empty view being rendered | -| 6+ | `before:sort ` | `collectionview` - collection view being rendered | -| 7 | `sort` | `collectionview` - collection view being rendered | -| 8# | `before:filter` | `collectionview` - collection view being rendered | -| 9# | `filter` | `collectionview` - collection view being rendered | -| 10 | `before:render:children` | `collectionview` - collection view being rendered | -| 11 | `render:children` | `collectionview` - collection view being rendered | -| 12* | `render` | `collectionview` - collection view being rendered | -| 13** | `before:attach` | `collectionview` - collection view being rendered | -| 14** | `attach` | `collectionview` - collection view being rendered | -| 15** | `dom:refresh` | `collectionview` - collection view being rendered | - -"\*" only fire if the `CollectionView` is fully rendering from either `collectionView.render()` or `collectionView.collection.reset()`. -"+" including and after this point only occur if there are some children to render. -"#" only fires if a `viewFilter` is defined. -"\*\*" fires from use in the parent when a CollectionView is shown in a Region or -as a childView of another CollectionView. - - -### CollectionView Destruction Lifecycle - -When a `CollectionView` is destroyed it fires a series of events in order to -reflect the different stages of the destruction process. - -| Order | Event | Arguments | -| :---: |------------------------------|-----------------------------------------------------| -| 1 | `before:destroy` | `collectionview` - collection view being destroyed | -| | | `...args` - arguments passed to `destroy` | -| 2 | `before:detach` | `collectionview` - collection view being destroyed | -| 3 | `detach` | `collectionview` - collection view being destroyed | -| 4 | `before:destroy:children` | `collectionview` - collection view being destroyed | -| 5+ | `before:remove:child` | `collectionview` - collection view being destroyed | -| | | `view` - child view being destroyed | -| 6+ | `remove:child` | `collectionview` - collection view being destroyed | -| | | `view` - child view being destroyed | -| 7 | `destroy:children` | `collectionview` - collection view being destroyed | -| 8 | `destroy` | `collectionview` - collection view being destroyed | -| | | `...args` - arguments passed to `destroy` | - -The events marked with "+" only fire on collections with children. - -### CollectionView Creation Events - -#### CollectionView `before:render` - -Triggers before the `CollectionView` render process starts. See the -[`before:render` Documentation](#view-before-render) for an -example. - -#### CollectionView `before:add:child` - -This event fires before each child is added to the children. - -#### CollectionView `add:child` - -This event fires after each child is added to the children. This fires once for each -item in the attached collection. - -#### CollectionView `before:sort` - -This event fires just before sorting the children in the `CollectionView`. -By default this only fires if the collectionView has at least one child. - -#### CollectionView `sort` - -This event fires after sorting the children in the `CollectionView`. -By default this only fires if the collectionView has at least one child. - -#### CollectionView `before:filter` - -This event fires just before filtering the children in the `CollectionView`. -By default this only fires if the collectionView has at least one child and has a `viewFilter`. - -#### CollectionView `filter` - -This event fires after filtering the children in the `CollectionView`. -By default this only fires if the collectionView has at least one child and has a `viewFilter`. - -#### CollectionView `before:render:children` - -This event fires just before rendering the children in the `CollectionView`. -By default this only fires if the collectionView has at least one view not filtered out. - -#### CollectionView `render:children` - -This event fires once all the collection's child views have been rendered. -By default this only fires if the collectionView has at least one view not filtered out. - -``` -var Bb = require('backbone'); -var Mn = require('backbone.marionette'); - -var MyView = Mn.CollectionView.extend({ - onRenderChildren: function({ - console.log('The collectionview children have been rendered'); - }) -}); - -var myView = new MyView({ - collection: new Bb.Collection([{ id: 1 }]); -}); - -myView.render(); -``` - -#### CollectionView `render` - -Fires when the collection has completely finished rendering. See the -[`render` Documentation](#view-render) for more information. - -### CollectionView Destruction Events - -#### CollectionView `before:destroy` - -Fires as the destruction process is beginning. This is best used to perform any -necessary cleanup within the `CollectionView`. - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.CollectionView.extend({ - onBeforeDestroy: function() { - console.log('The CollectionView is about to be destroyed'); - } -}); - -var myView = new MyView(); - -myView.destroy(); -``` - -#### CollectionView `before:detach` - -Fires just before the `CollectionView` is removed from the DOM. If you need to -remove any event handlers or UI modifications, this would be the best time to do -that. - -#### CollectionView `detach` - -Fires just after the `CollectionView` is removed from the DOM. The view's -elements will still exist in memory if you need to access them. - -#### CollectionView `before:destroy:children` - -This is triggered just before the `childView` items are destroyed. - -Triggered when the `CollectionView` is destroyed or before the `CollectionView`'s children are re-rendered. - -#### CollectionView `before:remove:child` - -This is triggered for each `childView` that is removed from the -`CollectionView`. This can *only* fire if the `collection` contains items. - -Each item in the `CollectionView` will undergo the -[destruction lifecycle](#view-destruction-lifecycle) - -#### CollectionView `remove:child` - -Fired for each view that is removed from the `CollectionView`. This can only -fire if the `collection` has items. - -#### CollectionView `destroy:children` - -This is triggered just after the `childView` items are destroyed. - -Triggered when the `CollectionView` is destroyed or before the `CollectionView`'s children are re-rendered. - -#### CollectionView `destroy` - -Fired once the `CollectionView` has been destroyed and no longer exists. - -### CollectionView EmptyView Events - -The `CollectionView` uses a region internally that can be used to know when the empty view is show or destroyed. - -```javascript -var Mn = require('backbone.marionette'); - -var MyView = Mn.CollectionView.extend({ - emptyView: MyEmptyView -}); - -var myView = new MyView(); - -myView.emptyRegion.on({ - show: function() { - console.log('CollectionView is empty!'); - }, - empty: function() { - console.log('CollectionView is removing the emptyView'); - } -}); - -myView.render(); -``` - -## Lifecycle State Methods - -Both `View` and `CollectionView` share methods for checking if the view is attached or rendered. - -### `isRendered()` - -This function will return a boolean value reflecting if the view has been rendered. - -### `isAttached()` - -This function will return a boolean value reflecting if the view believes it is attached. -This is maintained when attaching a view with a `Region` or during [View instantiation](#views-associated-with-previously-rendered-or-attached-dom). -If a view is attached by other means this function may not reflect the actual state of attachment. - -## Views associated with previously rendered or attached DOM - -When a view is instantiated, if the View's `el` is set to an existing node -the view's [`isRendered()`](#isrendered) will return `true` and `before:render` -and `render` events will not be fired when the view is shown in a Region. - -Similarly if the `el` is attached to a node in the DOM the view's [`isAttached()`](#isattached) -will return `true` and `before:attach`, `attach` and `dom:refresh` will not be fired -when the view is shown in a Region. - -## `Region`s and the View Lifecycle - -When you show a view inside a region - either using [`region.show(view)`](./marionette.region.md#showing-a-view) or -[`showChildView('region', view)`](./marionette.view.md#showing-a-view) - the `Region` will emit events around the view -events that you can hook into. - -### Show View Events - -When showing a view inside a region, the region emits a number of events: - -| Order | Event | Arguments | -| :---: |--------------------------------------------|-------------------------------------------| -| 1 | `before:show` | `region` - region showing the child view | -| | | `view` - view being shown in the region | -| | | `options` - options passed to `show()` | -| 2 | [View Creation Lifecycle](#view-creation-lifecycle) | | -| 3 | `show` | `region` - region showing the child view | -| | | `view` - view being shown in the region | -| | | `options` - options passed to `show()` | - -#### Region `before:show` - -Emitted on `region.show(view)`, before the view lifecycle begins. At this point, -none of the view rendering will have been performed. - -#### Region `show` - -Emitted after the view has been rendered and attached to the DOM (if this -region is already attached to the DOM). This can be used to handle any -extra manipulation that needs to occur. - -### Empty Region Events - -When [emptying a region](./marionette.region.md#emptying-a-region), it will emit destruction events around the view's -destruction lifecycle: - -| Order | Event | Arguments | -| :---: |-----------------------------------------------|---------------------------------| -| 1 | `before:empty` | `region` - region being emptied | -| | | `view` - view being removed | -| 2 | [View Destruction Lifecycle](#view-destruction-lifecycle) | | -| 3 | `empty` | `region` - region being emptied | -| | | `view` - view being removed | - -#### Region `before:empty` - -Emitted before the view's destruction process begins. This can occur either by -calling `region.empty()` or by running `region.show(view)` on a region that's -displaying another view. It will also trigger if the view in the region is -destroyed. - -#### Region `empty` - -Fired after the entire destruction process is complete. At this point, the view -has been removed from the DOM completely. - - -## Advanced Event Settings -Marionette is able to trigger `attach`/`detach` events down the view tree along with -triggering the `dom:refresh`/`dom:remove` events because of the view event monitor. -This monitor starts when a view is created or shown in a region (to handle non-Marionette views). - -In some cases it may be a useful performance improvement to disable this functionality. -Doing so is as easy as setting `monitorViewEvents: false` on the view class. - -```javascript -const NonMonitoredView = Mn.View.extend({ - monitorViewEvents: false -}); -``` - -**Important Note**: Disabling the view monitor will break the monitor generated -events for this view _and all child views_ of this view. Disabling should be done carefully. diff --git a/readme.md b/readme.md index f8b88eabe8..c06e610da7 100644 --- a/readme.md +++ b/readme.md @@ -15,7 +15,7 @@

      - + Cross Browser Testing

      ## Marionette v3 @@ -62,8 +62,7 @@ to engage in an all-or-nothing migration to begin using Marionette. ### Chat with us -Find us [on gitter](https://gitter.im/marionettejs/backbone.marionette) or on -IRC in the FreeNode.net [#marionette channel](http://freenode.net). +Find us [on gitter](https://gitter.im/marionettejs/backbone.marionette). We're happy to discuss design patterns and learn how you're using Marionette. diff --git a/src/behavior.js b/src/behavior.js index fe4a00b621..5102a19237 100644 --- a/src/behavior.js +++ b/src/behavior.js @@ -32,14 +32,13 @@ const Behavior = function(options, view) { this._setOptions(options, ClassOptions); this.cid = _.uniqueId(this.cidPrefix); - // Construct an internal UI hash using - // the behaviors UI hash and then the view UI hash. - // This allows the user to use UI hash elements - // defined in the parent view as well as those - // defined in the given behavior. + // Construct an internal UI hash using the behaviors UI + // hash combined and overridden by the view UI hash. + // This allows the user to use UI hash elements defined + // in the parent view as well as those defined in the behavior. // This order will help the reuse and share of a behavior - // between multiple views, while letting a view override a - // selector under an UI key. + // between multiple views, while letting a view override + // a selector under an UI key. this.ui = _.extend({}, _.result(this, 'ui'), _.result(view, 'ui')); // Proxy view triggers diff --git a/src/collection-view.js b/src/collection-view.js index 8e7a0d7f38..aad33c98fc 100644 --- a/src/collection-view.js +++ b/src/collection-view.js @@ -310,7 +310,7 @@ const CollectionView = Backbone.View.extend({ throw new MarionetteError({ name: classErrorName, message: `The specified "childViewContainer" was not found: ${childViewContainer}`, - url: 'marionette.collectionview.html#collectionviews-childviewcontainer' + url: 'marionette.collectionview.html#defining-the-childviewcontainer' }); } }, @@ -452,7 +452,8 @@ const CollectionView = Backbone.View.extend({ throw new MarionetteError({ name: classErrorName, - message: '"viewFilter" must be a function, predicate object literal, a string indicating a model attribute, or falsy' + message: '"viewFilter" must be a function, predicate object literal, a string indicating a model attribute, or falsy', + url: 'marionette.collectionview.html#defining-the-viewfilter' }); }, @@ -615,7 +616,7 @@ const CollectionView = Backbone.View.extend({ throw new MarionetteError({ name: classErrorName, message: 'Both views must be children of the collection view to swap.', - url: 'marionette.collectionviewadvanced.html#collectionviews-swapchildviews' + url: 'marionette.collectionview.html#swapping-child-views' }); } diff --git a/src/common/bind-events.js b/src/common/bind-events.js index a65fee62ad..8e5c1af9d5 100644 --- a/src/common/bind-events.js +++ b/src/common/bind-events.js @@ -21,7 +21,7 @@ function normalizeBindings(context, bindings) { if (!_.isObject(bindings)) { throw new MarionetteError({ message: 'Bindings must be an object.', - url: 'marionette.functions.html#marionettebindevents' + url: 'common.html#bindevents' }); } diff --git a/src/common/bind-requests.js b/src/common/bind-requests.js index 1991bb3cef..3ba684aa0b 100644 --- a/src/common/bind-requests.js +++ b/src/common/bind-requests.js @@ -19,7 +19,7 @@ function normalizeBindings(context, bindings) { if (!_.isObject(bindings)) { throw new MarionetteError({ message: 'Bindings must be an object.', - url: 'marionette.functions.html#marionettebindrequests' + url: 'common.html#bindrequests' }); } diff --git a/src/mixins/radio.js b/src/mixins/radio.js index 4598257294..f54e42a533 100644 --- a/src/mixins/radio.js +++ b/src/mixins/radio.js @@ -20,7 +20,7 @@ export default { if (!Radio) { throw new MarionetteError({ message: 'The dependency "backbone.radio" is missing.', - url: 'backbone.radio.html#backbone-radio' + url: 'backbone.radio.html#marionette-integration' }); } diff --git a/src/mixins/regions.js b/src/mixins/regions.js index d6544124a2..0a728c7a39 100644 --- a/src/mixins/regions.js +++ b/src/mixins/regions.js @@ -133,11 +133,11 @@ export default { return this._regions[name]; }, - // Get all regions _getRegions() { return _.clone(this._regions); }, + // Get all regions getRegions() { if (!this._isRendered) { this.render(); diff --git a/src/mixins/view.js b/src/mixins/view.js index 6aa64a6467..0f2efb130a 100644 --- a/src/mixins/view.js +++ b/src/mixins/view.js @@ -65,6 +65,7 @@ const ViewMixin = { return this; }, + // Allows Backbone.View events to utilize `@ui.` selectors _getEvents(events) { if (events) { return this.normalizeUIKeys(events); @@ -154,6 +155,7 @@ const ViewMixin = { this.Dom.detachEl(this.el, this.$el); }, + // This method binds the elements specified in the "ui" hash bindUIElements() { this._bindUIElements(); this._bindBehaviorUIElements(); diff --git a/src/region.js b/src/region.js index b7d0784be7..be056c133a 100644 --- a/src/region.js +++ b/src/region.js @@ -60,9 +60,8 @@ _.extend(Region.prototype, CommonMixin, { // This is a noop method intended to be overridden initialize() {}, - // Displays a backbone view instance inside of the region. Handles calling the `render` - // method for you. Reads content directly from the `el` attribute. The `preventDestroy` - // option can be used to prevent a view from the old view being destroyed on show. + // Displays a view instance inside of the region. If necessary handles calling the `render` + // method for you. Reads content directly from the `el` attribute. show(view, options) { if (!this._ensureElement(options)) { return; @@ -263,8 +262,8 @@ _.extend(Region.prototype, CommonMixin, { this.Dom.appendContents(this.el, view.el, {_$el: this.$el, _$contents: view.$el}); }, - // Destroy the current view, if there is one. If there is no current view, it does - // nothing and returns immediately. + // Destroy the current view, if there is one. If there is no current view, + // it will detach any html inside the region's `el`. empty(options = { allowMissingEl: true }) { const view = this.currentView; @@ -308,6 +307,7 @@ _.extend(Region.prototype, CommonMixin, { this._parentView.stopListening(view); }, + // Non-Marionette safe view.destroy destroyView(view) { if (view._isDestroyed) { return view; @@ -317,6 +317,8 @@ _.extend(Region.prototype, CommonMixin, { return view; }, + // Override this method to determine what happens when the view + // is removed from the region when the view is not being detached removeView(view) { this.destroyView(view); }, @@ -385,6 +387,8 @@ _.extend(Region.prototype, CommonMixin, { return this._isDestroyed; }, + // Destroy the region, remove any child view + // and remove the region from any associated view destroy(options) { if (this._isDestroyed) { return this; } diff --git a/src/view.js b/src/view.js index 51f939c316..6abc0b7e6a 100644 --- a/src/view.js +++ b/src/view.js @@ -35,7 +35,7 @@ function childReducer(children, region) { } // The standard view. Includes view events, automatic rendering -// of Underscore templates, nested views, and more. +// templates, nested views, and more. const View = Backbone.View.extend({ constructor(options) { @@ -69,13 +69,8 @@ const View = Backbone.View.extend({ return this; }, - // Render the view, defaulting to underscore.js templates. - // You can override this in your view definition to provide - // a very specific rendering for your view. In general, though, - // you should override the `Marionette.Renderer` object to - // change how Marionette renders views. - // Subsequent renders after the first will re-render all nested - // views. + // If a template is available, renders it into the view's `el` + // Re-inits regions and binds UI. render() { const template = this.getTemplate();