Skip to content

Commit

Permalink
Adds onAttach triggerMethod.
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesplease committed Oct 4, 2014
1 parent 42eb37a commit dd358e4
Show file tree
Hide file tree
Showing 7 changed files with 858 additions and 3 deletions.
1 change: 1 addition & 0 deletions SpecRunner.html
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
<script src="test/unit/collection-view.reset.spec.js"></script>
<script src="test/unit/collection-view.spec.js"></script>
<script src="test/unit/commands.spec.js"></script>
<script src="test/unit/on-attach.spec.js"></script>
<script src="test/unit/composite-view.child-view-container.spec.js"></script>
<script src="test/unit/composite-view.on-before-render.spec.js"></script>
<script src="test/unit/composite-view.spec.js"></script>
Expand Down
19 changes: 18 additions & 1 deletion api/region.jsdoc
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,20 @@ properties:
The `currentView` property references the currently shown view.
If `currentView` is `undefined`, the region is empty.

triggerBeforeAttach: |
Whether or not the Region will trigger the `before:attach` event on nested views that are about
to be attached. Defaults to `true`.

In general, the only time you'll want to set this to false is if you're rendering hundreds or
thousands of views and are running into performance problems.

triggerAttach: |
Whether or not the Region will trigger the `attach` event on nested views that have been attached.
Defaults to `true`.

As with triggerBeforeAttach, you might want to set this to `false` if you're rendering many, many
views and are running into performance problems.

initialize: |
If `initialize` is set in the Region class, it will be called when new regions are instantiated.

Expand Down Expand Up @@ -189,7 +203,7 @@ functions:
Shows `newView` inside the region if `newView` is not already shown within the region. The previous view, if one exists,
will be destroyed in this process. The `show` methods fires the show and swap triggerMethods.

You can modify the behavior of `show` by passing in an options object.
You can modify the behavior of `show` by passing in an `options` object. The following options are supported:

`preventDestroy`
Pass this as `true` to prevent the destruction of the old view. This is not recommended, as Views
Expand Down Expand Up @@ -221,6 +235,9 @@ functions:
// the second show call will re-show the view
MyApp.mainRegion.show(myView, {forceShow: true});
```

`triggerAttach`
Whether or not to trigger the `attach` event on the views being shown.

@api public
@param {Marionette.View} view
Expand Down
37 changes: 37 additions & 0 deletions docs/marionette.layoutview.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ will provide features such as `onShow` callbacks, etc. Please see
* [Re-Rendering A LayoutView](#re-rendering-a-layoutview)
* [Avoid Re-Rendering The Entire LayoutView](#avoid-re-rendering-the-entire-layoutview)
* [Nested LayoutViews And Views](#nested-layoutviews-and-views)
* [Efficient Nested View Structures](#efficient-nested-view-structures)
* [Use of the `attach` Event](#use-of-the-attach-event)
* [Destroying A LayoutView](#destroying-a-layoutview)
* [Custom Region Class](#custom-region-class)
* [Adding And Removing Regions](#adding-and-removing-regions)
Expand Down Expand Up @@ -224,6 +226,41 @@ layout1.getRegion('region1').show(layout2);
layout2.getRegion('region2').show(layout3);
```
### Efficient Nested View Structures
The above example works great, but it causes three separate paints: one for each layout that's being
shown. Marionette provides a simple mechanism to infinitely nest views in a single paint: just render all
of the children in the `onBeforeShow` callback.
```js
var ParentLayout = Marionette.LayoutView.extend({
onBeforeShow: function() {
this.getRegion('header').show(new HeaderView());
this.getRegion('footer').show(new FooterView());
}
});

myRegion.show(new ParentLayout());
```
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 `onBeforeShow` callbacks!
#### Use of the `attach` event
Often times you need to know when your views in the view tree have been attached to the `document`,
like when using certain jQuery plugins. The `attach` event, and associated `onAttach` callback, are perfect for this
use case. Start with a Region that's a child of the `document` and show any LayoutView in it: every view in the tree
(including the parent LayoutView) will have the `attach` event triggered on it when they have been
attached to the `document`.
Note that inefficient tree rendering will cause the `attach` event to be fired multiple times. This
situation can occur if you render the children views *after* the parent has been rendered, such as using
`onShow` to render children. As a rule of thumb, most of the time you'll want to render any nested views in
the `onBeforeShow` callback.
## Destroying A LayoutView
When you are finished with a layoutView, you can call the
Expand Down
28 changes: 27 additions & 1 deletion docs/marionette.region.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ MyApp.mainRegion.empty();
```

#### preventDestroy

If you replace the current view with a new view by calling `show`,
by default it will automatically destroy the previous view.
You can prevent this behavior by passing `{preventDestroy: true}` in the options
Expand All @@ -252,8 +253,8 @@ MyApp.mainRegion.show(anotherView2, { preventDestroy: true });
NOTE: When using `preventDestroy: true` you must be careful to cleanup your old views
manually to prevent memory leaks.


#### forceShow

If you re-call `show` with the same view, by default nothing will happen
because the view is already in the region. You can force the view to be re-shown
by passing in `{forceShow: true}` in the options parameter.
Expand All @@ -266,6 +267,31 @@ MyApp.mainRegion.show(myView);
MyApp.mainRegion.show(myView, {forceShow: true});
```

#### onBeforeAttach & onAttach

Regions that are attached to the document when you execute `show` are special in that the
views that they show will also become attached to the document. These regions fire a pair of triggerMethods on *all*
of the views that are about to be attached – even the nested ones. This can cause a performance issue if you're
rendering hundreds or thousands of views at once.

If you think these events might be causing some lag in your app, you can selectively turn them off
with the `triggerBeforeAttach` and `triggerAttach` properties.

```js
// No longer trigger attach
myRegion.triggerAttach = false;
```

You can override this on a per-show basis by passing it in as an option to show.

```js
// This region won't trigger beforeAttach...
myRegion.triggerBeforeAttach = false;

// Unless we tell it to
myRegion.show(myView, {triggerBeforeAttach: true});
```

### Checking whether a region is showing a view

If you wish to check whether a region has a view, you can use the `hasView`
Expand Down
24 changes: 24 additions & 0 deletions docs/marionette.view.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ behaviors that are shared across all views.
* [View onShow](#view-onshow)
* [View destroy](#view-destroy)
* [View onBeforeDestroy](#view-onbeforedestroy)
* [View "attach" / onAttach event](#view-attach--onattach-event)
* [View "before:attach" / onBeforeAttach event](#view-beforeattach--onbeforeattach-event)
* [View "dom:refresh" / onDomRefresh event](#view-domrefresh--ondomrefresh-event)
* [View.triggers](#viewtriggers)
* [View.events](#viewevents)
Expand Down Expand Up @@ -128,6 +130,28 @@ When destroying a view, an `onBeforeDestroy` method will be called, if it
has been provided, just before the view destroys. It will be passed any arguments
that `destroy` was invoked with.

### View "attach" / onAttach event

Every view in Marionette has a special event called "attach," which is triggered anytime that showing
the view in a Region causes it to be attached to the `document`. Like other Marionette events, it also
executes a callback method, `onAttach`, if you've specified one. The `"attach"` event is great for jQuery
plugins or other logic that must be executed *after* the view is attached to the `document`.

Because the `attach` event is only fired when the view is a child of the `document`, it is a requirement
that the Region you're showing it in be a child of the `document` at the time that you call `show`.

This event is unique in that it propagates down the view tree. For instance, when a CollectionView's
`attach` event is fired, all of its children views will have the `attach` event fired as well. In
addition, deeply nested Layout View structures will all have their `attach` event fired at the proper
time, too.

For more on efficient, deeply-nested view structures, refer to the LayoutView docs.

### View "before:attach" / onBeforeAttach

This is just like the attach event described above, but it's triggered right before the view is
attached to the document.

### View "dom:refresh" / onDomRefresh event

Triggered after the view has been rendered, has been shown in the DOM via a Marionette.Region, and has been
Expand Down
32 changes: 31 additions & 1 deletion src/region.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* jshint maxcomplexity: 10, maxstatements: 29, maxlen: 120 */
/* jshint maxcomplexity: 15, maxstatements: 40, maxlen: 120 */

// Region
// ------
Expand Down Expand Up @@ -180,8 +180,27 @@ _.extend(Marionette.Region.prototype, Backbone.Events, {
this.triggerMethod('swapOut', this.currentView);
}

// An array of views that we're about to display
var attachedRegion = Marionette.isNodeAttached(this.el);

// The views that we're about to attach to the document
// It's important that we prevent _getNestedViews from being executed unnecessarily
// as it's a potentially-slow method
var displayedViews = [];

var triggerBeforeAttach = showOptions.triggerBeforeAttach || this.triggerBeforeAttach;
var triggerAttach = showOptions.triggerAttach || this.triggerAttach;

if (attachedRegion && triggerBeforeAttach) {
displayedViews = [view].concat(_.result(view, '_getNestedViews'));
this._triggerAttach(displayedViews, 'before:');
}
this.attachHtml(view);
this.currentView = view;
if (attachedRegion && triggerAttach) {
displayedViews = [view].concat(_.result(view, '_getNestedViews'));
this._triggerAttach(displayedViews);
}

if (isChangingView) {
this.triggerMethod('swap', view);
Expand All @@ -195,6 +214,17 @@ _.extend(Marionette.Region.prototype, Backbone.Events, {
return this;
},

triggerBeforeAttach: true,
triggerAttach: true,

_triggerAttach: function(views, prefix) {
var eventName = (prefix || '') + 'attach';
_.each(views, function(view) {
if (!view) { return; }
Marionette.triggerMethodOn(view, eventName);
});
},

_ensureElement: function(){
if (!_.isObject(this.el)) {
this.$el = this.getEl(this.el);
Expand Down
Loading

0 comments on commit dd358e4

Please sign in to comment.