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 2, 2014
1 parent 42eb37a commit 83c144a
Show file tree
Hide file tree
Showing 6 changed files with 750 additions and 2 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
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
16 changes: 15 additions & 1 deletion docs/marionette.region.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,6 @@ 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
Expand All @@ -266,6 +265,21 @@ 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;
```

### 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
30 changes: 29 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: 12, maxstatements: 37, maxlen: 120 */

// Region
// ------
Expand Down Expand Up @@ -180,8 +180,24 @@ _.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 = [];

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

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

triggerBeforeAttach: true,
triggerAttach: true,

_triggerAttach: function(views, before) {
var prefix = before ? 'before:' : '';
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 83c144a

Please sign in to comment.