aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2015-06-09 12:13:56 +0200
committerStas Vilchik <vilchiks@gmail.com>2015-06-09 16:20:22 +0200
commite9ac473ec7dc0433cadf3f64f5d7980e38e0b93c (patch)
treef641493172b0f5a82b6207e1bb3c058f6f2d4914 /server/sonar-web/src/main/js
parent6e85ae2bcf98c12c634e792742a72b8e02a04d33 (diff)
downloadsonarqube-e9ac473ec7dc0433cadf3f64f5d7980e38e0b93c.tar.gz
sonarqube-e9ac473ec7dc0433cadf3f64f5d7980e38e0b93c.zip
upgrade backbone.marionette
Diffstat (limited to 'server/sonar-web/src/main/js')
-rw-r--r--server/sonar-web/src/main/js/apps/api-documentation/actions-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/api-documentation/layout.js2
-rw-r--r--server/sonar-web/src/main/js/apps/api-documentation/list-view.js4
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/facets-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/layout.js2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/rule-details-view.js4
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/rule/custom-rule-creation-view.js4
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/rule/custom-rule-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/rule/custom-rules-view.js6
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/rule/manual-rule-creation-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/rule/profile-activation-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/rule/rule-filter-mixin.js2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/rule/rule-meta-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/rule/rule-profiles-view.js6
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/workspace-list-view.js4
-rw-r--r--server/sonar-web/src/main/js/apps/computation/layout.js2
-rw-r--r--server/sonar-web/src/main/js/apps/computation/list-item-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/computation/list-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/groups/create-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/groups/delete-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/groups/form-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/groups/layout.js2
-rw-r--r--server/sonar-web/src/main/js/apps/groups/list-item-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/groups/list-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/groups/update-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/groups/users-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/component-viewer/main.js4
-rw-r--r--server/sonar-web/src/main/js/apps/issues/facets-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/facets/base-facet.js2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/layout.js2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/workspace-header-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/workspace-list-item-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/workspace-list-view.js22
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/create-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/delete-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/form-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/layout.js2
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/list-item-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/list-view.js4
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/update-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/nav/context-navbar-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/nav/global-navbar-view.js4
-rw-r--r--server/sonar-web/src/main/js/apps/nav/search-view.js4
-rw-r--r--server/sonar-web/src/main/js/apps/provisioning/bulk-delete-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/provisioning/create-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/provisioning/delete-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/provisioning/form-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/provisioning/layout.js2
-rw-r--r--server/sonar-web/src/main/js/apps/provisioning/list-item-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/provisioning/list-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/delete-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/details-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/form-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/gate-condition-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-delete-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-view.js6
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/gates-view.js6
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/layout.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/change-profile-parent-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/copy-profile-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/create-profile-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/delete-profile-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/layout.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/profile-details-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/profile-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/profiles-view.js12
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/rename-profile-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/restore-built-in-profiles-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/restore-profile-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/users/change-password-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/users/create-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/users/deactivate-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/users/form-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/users/groups-view.js4
-rw-r--r--server/sonar-web/src/main/js/apps/users/layout.js2
-rw-r--r--server/sonar-web/src/main/js/apps/users/list-item-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/users/list-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/users/update-view.js2
-rw-r--r--server/sonar-web/src/main/js/components/common/modals.js8
-rw-r--r--server/sonar-web/src/main/js/components/common/popup.js8
-rw-r--r--server/sonar-web/src/main/js/components/common/selectable-collection-view.js2
-rw-r--r--server/sonar-web/src/main/js/components/issue/issue-view.js6
-rw-r--r--server/sonar-web/src/main/js/components/issue/manual-issue-view.js6
-rw-r--r--server/sonar-web/src/main/js/components/issue/views/action-options-view.js8
-rw-r--r--server/sonar-web/src/main/js/components/issue/views/assign-form-view.js2
-rw-r--r--server/sonar-web/src/main/js/components/issue/views/comment-form-view.js2
-rw-r--r--server/sonar-web/src/main/js/components/issue/views/issue-popup.js6
-rw-r--r--server/sonar-web/src/main/js/components/issue/views/tags-form-view.js2
-rw-r--r--server/sonar-web/src/main/js/components/navigator/facets-view.js4
-rw-r--r--server/sonar-web/src/main/js/components/navigator/filters/filter-bar.js12
-rw-r--r--server/sonar-web/src/main/js/components/navigator/workspace-header-view.js2
-rw-r--r--server/sonar-web/src/main/js/components/navigator/workspace-list-view.js4
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/main.js6
-rw-r--r--server/sonar-web/src/main/js/components/source-viewer/more-actions.js2
-rw-r--r--server/sonar-web/src/main/js/components/workspace/main.js4
-rw-r--r--server/sonar-web/src/main/js/components/workspace/views/base-viewer-view.js4
-rw-r--r--server/sonar-web/src/main/js/components/workspace/views/items-view.js6
-rw-r--r--server/sonar-web/src/main/js/components/workspace/views/rule-view.js6
-rw-r--r--server/sonar-web/src/main/js/components/workspace/views/viewer-header-view.js2
-rw-r--r--server/sonar-web/src/main/js/libs/third-party/backbone.marionette.js6076
100 files changed, 3836 insertions, 2562 deletions
diff --git a/server/sonar-web/src/main/js/apps/api-documentation/actions-view.js b/server/sonar-web/src/main/js/apps/api-documentation/actions-view.js
index 099e1ea73d0..683485c101b 100644
--- a/server/sonar-web/src/main/js/apps/api-documentation/actions-view.js
+++ b/server/sonar-web/src/main/js/apps/api-documentation/actions-view.js
@@ -24,7 +24,7 @@ define([
var $ = jQuery;
return Marionette.CollectionView.extend({
- itemView: ActionView,
+ childView: ActionView,
scrollToTop: function () {
var parent = this.$el.scrollParent();
diff --git a/server/sonar-web/src/main/js/apps/api-documentation/layout.js b/server/sonar-web/src/main/js/apps/api-documentation/layout.js
index 64f17b220ed..ae4ce851798 100644
--- a/server/sonar-web/src/main/js/apps/api-documentation/layout.js
+++ b/server/sonar-web/src/main/js/apps/api-documentation/layout.js
@@ -23,7 +23,7 @@ define([
var $ = jQuery;
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
template: Templates['api-documentation-layout'],
regions: {
diff --git a/server/sonar-web/src/main/js/apps/api-documentation/list-view.js b/server/sonar-web/src/main/js/apps/api-documentation/list-view.js
index a833cf7b48b..b90ba7f725a 100644
--- a/server/sonar-web/src/main/js/apps/api-documentation/list-view.js
+++ b/server/sonar-web/src/main/js/apps/api-documentation/list-view.js
@@ -23,9 +23,9 @@ define([
return Marionette.CollectionView.extend({
className: 'list-group',
- itemView: ItemView,
+ childView: ItemView,
- itemViewOptions: function (model) {
+ childViewOptions: function (model) {
return {
collectionView: this,
highlighted: model.get('path') === this.highlighted,
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/facets-view.js b/server/sonar-web/src/main/js/apps/coding-rules/facets-view.js
index b9c2a7f4921..cf9e8d92bc5 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/facets-view.js
+++ b/server/sonar-web/src/main/js/apps/coding-rules/facets-view.js
@@ -68,7 +68,7 @@ define([
return FacetsView.extend({
- getItemView: function (model) {
+ getChildView: function (model) {
var view = viewsMapping[model.get('property')];
return view ? view : BaseFacet;
}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/layout.js b/server/sonar-web/src/main/js/apps/coding-rules/layout.js
index 87ec56e9694..238db774707 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/layout.js
+++ b/server/sonar-web/src/main/js/apps/coding-rules/layout.js
@@ -23,7 +23,7 @@ define([
var $ = jQuery;
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
template: Templates['coding-rules-layout'],
regions: {
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/rule-details-view.js b/server/sonar-web/src/main/js/apps/coding-rules/rule-details-view.js
index 941cfc42912..05856342aa4 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/rule-details-view.js
+++ b/server/sonar-web/src/main/js/apps/coding-rules/rule-details-view.js
@@ -41,7 +41,7 @@ define([
var $ = jQuery;
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
className: 'coding-rule-details',
template: Templates['coding-rules-rule-details'],
@@ -99,7 +99,7 @@ define([
this.$el.scrollParent().scrollTop(0);
},
- onClose: function () {
+ onDestroy: function () {
this.unbindShortcuts();
},
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/rule/custom-rule-creation-view.js b/server/sonar-web/src/main/js/apps/coding-rules/rule/custom-rule-creation-view.js
index 5c3829854a6..e15a1149e69 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/rule/custom-rule-creation-view.js
+++ b/server/sonar-web/src/main/js/apps/coding-rules/rule/custom-rule-creation-view.js
@@ -51,7 +51,7 @@ define([
'keydown @ui.customRuleCreationKey': 'flagKey',
'keyup @ui.customRuleCreationKey': 'flagKey',
- 'click #coding-rules-custom-rule-creation-cancel': 'close',
+ 'click #coding-rules-custom-rule-creation-cancel': 'destroy',
'click @ui.customRuleCreationCreate': 'create',
'click @ui.customRuleCreationReactivate': 'reactivate'
});
@@ -168,7 +168,7 @@ define([
} else {
that.options.app.controller.showDetails(that.model);
}
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
if (jqXHR.status === 409) {
that.existingRule = jqXHR.responseJSON.rule;
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/rule/custom-rule-view.js b/server/sonar-web/src/main/js/apps/coding-rules/rule/custom-rule-view.js
index 98d9634516e..e50b7c11005 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/rule/custom-rule-view.js
+++ b/server/sonar-web/src/main/js/apps/coding-rules/rule/custom-rule-view.js
@@ -45,7 +45,7 @@ define([
options = { key: that.model.id };
$.post(url, options).done(function () {
that.model.collection.remove(that.model);
- that.close();
+ that.destroy();
});
}
});
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/rule/custom-rules-view.js b/server/sonar-web/src/main/js/apps/coding-rules/rule/custom-rules-view.js
index 3785b8ed6b6..56ae2a95833 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/rule/custom-rules-view.js
+++ b/server/sonar-web/src/main/js/apps/coding-rules/rule/custom-rules-view.js
@@ -25,10 +25,10 @@ define([
return Marionette.CompositeView.extend({
template: Templates['coding-rules-custom-rules'],
- itemView: CustomRuleView,
- itemViewContainer: '#coding-rules-detail-custom-rules',
+ childView: CustomRuleView,
+ childViewContainer: '#coding-rules-detail-custom-rules',
- itemViewOptions: function () {
+ childViewOptions: function () {
return {
app: this.options.app,
templateRule: this.model
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/rule/manual-rule-creation-view.js b/server/sonar-web/src/main/js/apps/coding-rules/rule/manual-rule-creation-view.js
index 6417beb3c8a..66617fc33ed 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/rule/manual-rule-creation-view.js
+++ b/server/sonar-web/src/main/js/apps/coding-rules/rule/manual-rule-creation-view.js
@@ -115,7 +115,7 @@ define([
r = JSON.parse(r);
}
that.options.app.controller.showDetails(r.rule.key);
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
if (jqXHR.status === 409) {
that.existingRule = jqXHR.responseJSON.rule;
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/rule/profile-activation-view.js b/server/sonar-web/src/main/js/apps/coding-rules/rule/profile-activation-view.js
index e12c4730469..a310ce97674 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/rule/profile-activation-view.js
+++ b/server/sonar-web/src/main/js/apps/coding-rules/rule/profile-activation-view.js
@@ -111,7 +111,7 @@ define([
400: null
}
}).done(function () {
- that.close();
+ that.destroy();
that.trigger('profileActivated', severity, params);
}).fail(function (jqXHR) {
that.enableForm();
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/rule/rule-filter-mixin.js b/server/sonar-web/src/main/js/apps/coding-rules/rule/rule-filter-mixin.js
index 839e1743af0..5bbe6a2d3cd 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/rule/rule-filter-mixin.js
+++ b/server/sonar-web/src/main/js/apps/coding-rules/rule/rule-filter-mixin.js
@@ -38,7 +38,7 @@ define([
var obj = {};
obj[property] = '' + value;
that.options.app.state.updateFilter(obj);
- popup.close();
+ popup.destroy();
});
popup.render();
}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/rule/rule-meta-view.js b/server/sonar-web/src/main/js/apps/coding-rules/rule/rule-meta-view.js
index 1a4cb4c3f7e..d8565818c52 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/rule/rule-meta-view.js
+++ b/server/sonar-web/src/main/js/apps/coding-rules/rule/rule-meta-view.js
@@ -51,7 +51,7 @@ define([
});
},
- onClose: function () {
+ onDestroy: function () {
this.$('[data-toggle="tooltip"]').tooltip('destroy');
},
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/rule/rule-profiles-view.js b/server/sonar-web/src/main/js/apps/coding-rules/rule/rule-profiles-view.js
index 7c3dc3df275..fc7662e5073 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/rule/rule-profiles-view.js
+++ b/server/sonar-web/src/main/js/apps/coding-rules/rule/rule-profiles-view.js
@@ -25,10 +25,10 @@ define([
return Marionette.CompositeView.extend({
template: Templates['coding-rules-rule-profiles'],
- itemView: ProfileView,
- itemViewContainer: '#coding-rules-detail-quality-profiles',
+ childView: ProfileView,
+ childViewContainer: '#coding-rules-detail-quality-profiles',
- itemViewOptions: function () {
+ childViewOptions: function () {
return {
app: this.options.app,
rule: this.model
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/workspace-list-view.js b/server/sonar-web/src/main/js/apps/coding-rules/workspace-list-view.js
index e5f1ac02eb2..e77a504fa7f 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/workspace-list-view.js
+++ b/server/sonar-web/src/main/js/apps/coding-rules/workspace-list-view.js
@@ -26,8 +26,8 @@ define([
return WorkspaceListView.extend({
template: Templates['coding-rules-workspace-list'],
- itemView: WorkspaceListItemView,
- itemViewContainer: '.js-list',
+ childView: WorkspaceListItemView,
+ childViewContainer: '.js-list',
emptyView: WorkspaceListEmptyView,
bindShortcuts: function () {
diff --git a/server/sonar-web/src/main/js/apps/computation/layout.js b/server/sonar-web/src/main/js/apps/computation/layout.js
index f747efea06c..2e7edcd22bf 100644
--- a/server/sonar-web/src/main/js/apps/computation/layout.js
+++ b/server/sonar-web/src/main/js/apps/computation/layout.js
@@ -2,7 +2,7 @@ define([
'./templates'
], function () {
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
template: Templates['computation-layout'],
regions: {
diff --git a/server/sonar-web/src/main/js/apps/computation/list-item-view.js b/server/sonar-web/src/main/js/apps/computation/list-item-view.js
index e457b39c2f3..afcf2f15d3f 100644
--- a/server/sonar-web/src/main/js/apps/computation/list-item-view.js
+++ b/server/sonar-web/src/main/js/apps/computation/list-item-view.js
@@ -13,7 +13,7 @@ define([
this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
},
- onClose: function () {
+ onDestroy: function () {
this.$('[data-toggle="tooltip"]').tooltip('destroy');
},
diff --git a/server/sonar-web/src/main/js/apps/computation/list-view.js b/server/sonar-web/src/main/js/apps/computation/list-view.js
index 138c36b7619..24878864d30 100644
--- a/server/sonar-web/src/main/js/apps/computation/list-view.js
+++ b/server/sonar-web/src/main/js/apps/computation/list-view.js
@@ -5,7 +5,7 @@ define([
return Marionette.CollectionView.extend({
tagName: 'ul',
- itemView: ListItemView
+ childView: ListItemView
});
});
diff --git a/server/sonar-web/src/main/js/apps/groups/create-view.js b/server/sonar-web/src/main/js/apps/groups/create-view.js
index 8d5cfce55aa..cddde867a99 100644
--- a/server/sonar-web/src/main/js/apps/groups/create-view.js
+++ b/server/sonar-web/src/main/js/apps/groups/create-view.js
@@ -19,7 +19,7 @@ define([
}
}).done(function () {
that.collection.refresh();
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.enableForm();
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
diff --git a/server/sonar-web/src/main/js/apps/groups/delete-view.js b/server/sonar-web/src/main/js/apps/groups/delete-view.js
index 05e07c0d032..85b33a632b5 100644
--- a/server/sonar-web/src/main/js/apps/groups/delete-view.js
+++ b/server/sonar-web/src/main/js/apps/groups/delete-view.js
@@ -22,7 +22,7 @@ define([
}
}).done(function () {
collection.total--;
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
});
diff --git a/server/sonar-web/src/main/js/apps/groups/form-view.js b/server/sonar-web/src/main/js/apps/groups/form-view.js
index e79ea6eec65..7e3c26b98ee 100644
--- a/server/sonar-web/src/main/js/apps/groups/form-view.js
+++ b/server/sonar-web/src/main/js/apps/groups/form-view.js
@@ -11,7 +11,7 @@ define([
this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
},
- onClose: function () {
+ onDestroy: function () {
this._super();
this.$('[data-toggle="tooltip"]').tooltip('destroy');
},
diff --git a/server/sonar-web/src/main/js/apps/groups/layout.js b/server/sonar-web/src/main/js/apps/groups/layout.js
index a60fb06f35f..18f6c7738d1 100644
--- a/server/sonar-web/src/main/js/apps/groups/layout.js
+++ b/server/sonar-web/src/main/js/apps/groups/layout.js
@@ -2,7 +2,7 @@ define([
'./templates'
], function () {
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
template: Templates['groups-layout'],
regions: {
diff --git a/server/sonar-web/src/main/js/apps/groups/list-item-view.js b/server/sonar-web/src/main/js/apps/groups/list-item-view.js
index 43eaa5b0d24..c09af127003 100644
--- a/server/sonar-web/src/main/js/apps/groups/list-item-view.js
+++ b/server/sonar-web/src/main/js/apps/groups/list-item-view.js
@@ -21,7 +21,7 @@ define([
this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
},
- onClose: function () {
+ onDestroy: function () {
this.$('[data-toggle="tooltip"]').tooltip('destroy');
},
diff --git a/server/sonar-web/src/main/js/apps/groups/list-view.js b/server/sonar-web/src/main/js/apps/groups/list-view.js
index 138c36b7619..24878864d30 100644
--- a/server/sonar-web/src/main/js/apps/groups/list-view.js
+++ b/server/sonar-web/src/main/js/apps/groups/list-view.js
@@ -5,7 +5,7 @@ define([
return Marionette.CollectionView.extend({
tagName: 'ul',
- itemView: ListItemView
+ childView: ListItemView
});
});
diff --git a/server/sonar-web/src/main/js/apps/groups/update-view.js b/server/sonar-web/src/main/js/apps/groups/update-view.js
index 71383a1793d..850ddb7510f 100644
--- a/server/sonar-web/src/main/js/apps/groups/update-view.js
+++ b/server/sonar-web/src/main/js/apps/groups/update-view.js
@@ -18,7 +18,7 @@ define([
}
}).done(function () {
that.collection.refresh();
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.enableForm();
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
diff --git a/server/sonar-web/src/main/js/apps/groups/users-view.js b/server/sonar-web/src/main/js/apps/groups/users-view.js
index 25db7e80158..de5901fc5f1 100644
--- a/server/sonar-web/src/main/js/apps/groups/users-view.js
+++ b/server/sonar-web/src/main/js/apps/groups/users-view.js
@@ -33,7 +33,7 @@ define([
});
},
- onClose: function () {
+ onDestroy: function () {
this.model.collection.refresh();
this._super();
}
diff --git a/server/sonar-web/src/main/js/apps/issues/component-viewer/main.js b/server/sonar-web/src/main/js/apps/issues/component-viewer/main.js
index f138a860d30..1e79d92e45d 100644
--- a/server/sonar-web/src/main/js/apps/issues/component-viewer/main.js
+++ b/server/sonar-web/src/main/js/apps/issues/component-viewer/main.js
@@ -73,8 +73,8 @@ define([
return key.deleteScope('componentViewer');
},
- onClose: function () {
- SourceViewer.prototype.onClose.apply(this, arguments);
+ onDestroy: function () {
+ SourceViewer.prototype.onDestroy.apply(this, arguments);
this.unbindScrollEvents();
return this.unbindShortcuts();
},
diff --git a/server/sonar-web/src/main/js/apps/issues/facets-view.js b/server/sonar-web/src/main/js/apps/issues/facets-view.js
index eb5b4e127fa..26477d4788c 100644
--- a/server/sonar-web/src/main/js/apps/issues/facets-view.js
+++ b/server/sonar-web/src/main/js/apps/issues/facets-view.js
@@ -41,7 +41,7 @@ define([
};
return FacetsView.extend({
- getItemView: function (model) {
+ getChildView: function (model) {
var view = viewsMapping[model.get('property')];
return view ? view : BaseFacet;
}
diff --git a/server/sonar-web/src/main/js/apps/issues/facets/base-facet.js b/server/sonar-web/src/main/js/apps/issues/facets/base-facet.js
index 1376e8b6611..4f7d9d8ea55 100644
--- a/server/sonar-web/src/main/js/apps/issues/facets/base-facet.js
+++ b/server/sonar-web/src/main/js/apps/issues/facets/base-facet.js
@@ -11,7 +11,7 @@ define([
return this.$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
},
- onClose: function () {
+ onDestroy: function () {
return this.$('[data-toggle="tooltip"]').tooltip('destroy');
}
});
diff --git a/server/sonar-web/src/main/js/apps/issues/layout.js b/server/sonar-web/src/main/js/apps/issues/layout.js
index 06b2f893af6..13a71301a24 100644
--- a/server/sonar-web/src/main/js/apps/issues/layout.js
+++ b/server/sonar-web/src/main/js/apps/issues/layout.js
@@ -3,7 +3,7 @@ define([
], function () {
var $ = jQuery;
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
template: Templates['issues-layout'],
regions: {
diff --git a/server/sonar-web/src/main/js/apps/issues/workspace-header-view.js b/server/sonar-web/src/main/js/apps/issues/workspace-header-view.js
index c301c40e872..928b4b42c5f 100644
--- a/server/sonar-web/src/main/js/apps/issues/workspace-header-view.js
+++ b/server/sonar-web/src/main/js/apps/issues/workspace-header-view.js
@@ -23,7 +23,7 @@ define([
window.onBulkIssues = _.bind(this.afterBulkChange, this);
},
- onClose: function () {
+ onDestroy: function () {
this._super();
window.onBulkIssues = this._onBulkIssues;
},
diff --git a/server/sonar-web/src/main/js/apps/issues/workspace-list-item-view.js b/server/sonar-web/src/main/js/apps/issues/workspace-list-item-view.js
index 510f26c63ea..28a6eb7e433 100644
--- a/server/sonar-web/src/main/js/apps/issues/workspace-list-item-view.js
+++ b/server/sonar-web/src/main/js/apps/issues/workspace-list-item-view.js
@@ -68,7 +68,7 @@ define([
});
}
that.options.app.state.updateFilter(obj);
- that.popup.close();
+ that.popup.destroy();
});
this.popup.render();
},
diff --git a/server/sonar-web/src/main/js/apps/issues/workspace-list-view.js b/server/sonar-web/src/main/js/apps/issues/workspace-list-view.js
index d4bdad5f65b..5734479fc36 100644
--- a/server/sonar-web/src/main/js/apps/issues/workspace-list-view.js
+++ b/server/sonar-web/src/main/js/apps/issues/workspace-list-view.js
@@ -12,10 +12,18 @@ define([
return WorkspaceListView.extend({
template: Templates['issues-workspace-list'],
componentTemplate: Templates['issues-workspace-list-component'],
- itemView: IssueView,
- itemViewContainer: '.js-list',
+ childView: IssueView,
+ childViewContainer: '.js-list',
emptyView: EmptyView,
+ collectionEvents: {
+ 'all': 'log'
+ },
+
+ log: function () {
+ console.log(arguments);
+ },
+
bindShortcuts: function () {
var that = this;
var doAction = function (action) {
@@ -82,8 +90,8 @@ define([
}
},
- appendHtml: function (compositeView, itemView, index) {
- var $container = this.getItemViewContainer(compositeView),
+ attachHtml: function (compositeView, childView, index) {
+ var $container = this.getChildViewContainer(compositeView),
model = this.collection.at(index);
if (model != null) {
var prev = this.collection.at(index - 1),
@@ -99,11 +107,11 @@ define([
$container.append(this.componentTemplate(model.toJSON()));
}
}
- $container.append(itemView.el);
+ $container.append(childView.el);
},
- closeChildren: function () {
- WorkspaceListView.prototype.closeChildren.apply(this, arguments);
+ destroyChildren: function () {
+ WorkspaceListView.prototype.destroyChildren.apply(this, arguments);
this.$('.issues-workspace-list-component').remove();
}
});
diff --git a/server/sonar-web/src/main/js/apps/metrics/create-view.js b/server/sonar-web/src/main/js/apps/metrics/create-view.js
index 0db9fa70f11..5be34dac1ec 100644
--- a/server/sonar-web/src/main/js/apps/metrics/create-view.js
+++ b/server/sonar-web/src/main/js/apps/metrics/create-view.js
@@ -22,7 +22,7 @@ define([
}
}).done(function () {
that.collection.refresh();
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.enableForm();
that.showErrors([{ msg: jqXHR.responseJSON.err_msg }]);
diff --git a/server/sonar-web/src/main/js/apps/metrics/delete-view.js b/server/sonar-web/src/main/js/apps/metrics/delete-view.js
index 0acc3c055a9..8719b9fdb3a 100644
--- a/server/sonar-web/src/main/js/apps/metrics/delete-view.js
+++ b/server/sonar-web/src/main/js/apps/metrics/delete-view.js
@@ -22,7 +22,7 @@ define([
}
}).done(function () {
collection.refresh();
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
});
diff --git a/server/sonar-web/src/main/js/apps/metrics/form-view.js b/server/sonar-web/src/main/js/apps/metrics/form-view.js
index 77b5f2a54a5..03e66f715b6 100644
--- a/server/sonar-web/src/main/js/apps/metrics/form-view.js
+++ b/server/sonar-web/src/main/js/apps/metrics/form-view.js
@@ -35,7 +35,7 @@ define([
this.$('#create-metric-type').select2({ width: '250px' });
},
- onClose: function () {
+ onDestroy: function () {
this._super();
this.$('[data-toggle="tooltip"]').tooltip('destroy');
},
diff --git a/server/sonar-web/src/main/js/apps/metrics/layout.js b/server/sonar-web/src/main/js/apps/metrics/layout.js
index 812212a42fa..9575307d96f 100644
--- a/server/sonar-web/src/main/js/apps/metrics/layout.js
+++ b/server/sonar-web/src/main/js/apps/metrics/layout.js
@@ -2,7 +2,7 @@ define([
'./templates'
], function () {
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
template: Templates['metrics-layout'],
regions: {
diff --git a/server/sonar-web/src/main/js/apps/metrics/list-item-view.js b/server/sonar-web/src/main/js/apps/metrics/list-item-view.js
index 6328aea11d5..c6989e3b7b3 100644
--- a/server/sonar-web/src/main/js/apps/metrics/list-item-view.js
+++ b/server/sonar-web/src/main/js/apps/metrics/list-item-view.js
@@ -21,7 +21,7 @@ define([
this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
},
- onClose: function () {
+ onDestroy: function () {
this.$('[data-toggle="tooltip"]').tooltip('destroy');
},
diff --git a/server/sonar-web/src/main/js/apps/metrics/list-view.js b/server/sonar-web/src/main/js/apps/metrics/list-view.js
index 27060bbe7d4..b015c65d966 100644
--- a/server/sonar-web/src/main/js/apps/metrics/list-view.js
+++ b/server/sonar-web/src/main/js/apps/metrics/list-view.js
@@ -5,9 +5,9 @@ define([
return Marionette.CollectionView.extend({
tagName: 'ul',
- itemView: ListItemView,
+ childView: ListItemView,
- itemViewOptions: function () {
+ childViewOptions: function () {
return {
types: this.options.types,
domains: this.options.domains
diff --git a/server/sonar-web/src/main/js/apps/metrics/update-view.js b/server/sonar-web/src/main/js/apps/metrics/update-view.js
index c4edec81a8d..3dfea4a584e 100644
--- a/server/sonar-web/src/main/js/apps/metrics/update-view.js
+++ b/server/sonar-web/src/main/js/apps/metrics/update-view.js
@@ -21,7 +21,7 @@ define([
}
}).done(function () {
that.collection.refresh();
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.enableForm();
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
diff --git a/server/sonar-web/src/main/js/apps/nav/context-navbar-view.js b/server/sonar-web/src/main/js/apps/nav/context-navbar-view.js
index 07eb0cd159a..2ff9d41d518 100644
--- a/server/sonar-web/src/main/js/apps/nav/context-navbar-view.js
+++ b/server/sonar-web/src/main/js/apps/nav/context-navbar-view.js
@@ -68,7 +68,7 @@ define([
return href.indexOf(url) !== -1;
}),
isOverviewActive = !isMoreActive && href.indexOf('/dashboard') !== -1 && search.indexOf('did=') === -1;
- return _.extend(Marionette.Layout.prototype.serializeData.apply(this, arguments), {
+ return _.extend(Marionette.LayoutView.prototype.serializeData.apply(this, arguments), {
canManageContextDashboards: !!window.SS.user,
contextKeyEncoded: encodeURIComponent(this.model.get('componentKey')),
diff --git a/server/sonar-web/src/main/js/apps/nav/global-navbar-view.js b/server/sonar-web/src/main/js/apps/nav/global-navbar-view.js
index 5a0915e7b20..b4d8dd76f95 100644
--- a/server/sonar-web/src/main/js/apps/nav/global-navbar-view.js
+++ b/server/sonar-web/src/main/js/apps/nav/global-navbar-view.js
@@ -23,7 +23,7 @@ define([
'./templates'
], function (SearchView, ShortcutsHelpView) {
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
template: Templates['nav-global-navbar'],
modelEvents: {
@@ -82,7 +82,7 @@ define([
},
serializeData: function () {
- return _.extend(Marionette.Layout.prototype.serializeData.apply(this, arguments), {
+ return _.extend(Marionette.LayoutView.prototype.serializeData.apply(this, arguments), {
user: window.SS.user,
userName: window.SS.userName,
userEmail: window.SS.userEmail,
diff --git a/server/sonar-web/src/main/js/apps/nav/search-view.js b/server/sonar-web/src/main/js/apps/nav/search-view.js
index 09c7468851e..da26004aa57 100644
--- a/server/sonar-web/src/main/js/apps/nav/search-view.js
+++ b/server/sonar-web/src/main/js/apps/nav/search-view.js
@@ -55,11 +55,11 @@ define([
SearchResultsView = SelectableCollectionView.extend({
className: 'menu',
tagName: 'ul',
- itemView: SearchItemView,
+ childView: SearchItemView,
emptyView: SearchEmptyView
});
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
className: 'navbar-search',
tagName: 'form',
template: Templates['nav-search'],
diff --git a/server/sonar-web/src/main/js/apps/provisioning/bulk-delete-view.js b/server/sonar-web/src/main/js/apps/provisioning/bulk-delete-view.js
index 91d09a12ee7..731e3e163d2 100644
--- a/server/sonar-web/src/main/js/apps/provisioning/bulk-delete-view.js
+++ b/server/sonar-web/src/main/js/apps/provisioning/bulk-delete-view.js
@@ -21,7 +21,7 @@ define([
}
}).done(function () {
that.collection.refresh();
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
});
diff --git a/server/sonar-web/src/main/js/apps/provisioning/create-view.js b/server/sonar-web/src/main/js/apps/provisioning/create-view.js
index 2f7d3b3965c..2aba2786c3c 100644
--- a/server/sonar-web/src/main/js/apps/provisioning/create-view.js
+++ b/server/sonar-web/src/main/js/apps/provisioning/create-view.js
@@ -20,7 +20,7 @@ define([
}
}).done(function () {
that.collection.refresh();
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.enableForm();
that.showErrors([{ msg: jqXHR.responseJSON.err_msg }]);
diff --git a/server/sonar-web/src/main/js/apps/provisioning/delete-view.js b/server/sonar-web/src/main/js/apps/provisioning/delete-view.js
index 7797227aa40..dd503c4b1a4 100644
--- a/server/sonar-web/src/main/js/apps/provisioning/delete-view.js
+++ b/server/sonar-web/src/main/js/apps/provisioning/delete-view.js
@@ -22,7 +22,7 @@ define([
}
}).done(function () {
collection.refresh();
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
});
diff --git a/server/sonar-web/src/main/js/apps/provisioning/form-view.js b/server/sonar-web/src/main/js/apps/provisioning/form-view.js
index ce359c6fa07..bb0fc0bcef5 100644
--- a/server/sonar-web/src/main/js/apps/provisioning/form-view.js
+++ b/server/sonar-web/src/main/js/apps/provisioning/form-view.js
@@ -11,7 +11,7 @@ define([
this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
},
- onClose: function () {
+ onDestroy: function () {
this._super();
this.$('[data-toggle="tooltip"]').tooltip('destroy');
},
diff --git a/server/sonar-web/src/main/js/apps/provisioning/layout.js b/server/sonar-web/src/main/js/apps/provisioning/layout.js
index d0627a1e5b8..31b67e8b774 100644
--- a/server/sonar-web/src/main/js/apps/provisioning/layout.js
+++ b/server/sonar-web/src/main/js/apps/provisioning/layout.js
@@ -2,7 +2,7 @@ define([
'./templates'
], function () {
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
template: Templates['provisioning-layout'],
regions: {
diff --git a/server/sonar-web/src/main/js/apps/provisioning/list-item-view.js b/server/sonar-web/src/main/js/apps/provisioning/list-item-view.js
index 2b03660b698..ead9b009ca3 100644
--- a/server/sonar-web/src/main/js/apps/provisioning/list-item-view.js
+++ b/server/sonar-web/src/main/js/apps/provisioning/list-item-view.js
@@ -22,7 +22,7 @@ define([
this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
},
- onClose: function () {
+ onDestroy: function () {
this.$('[data-toggle="tooltip"]').tooltip('destroy');
},
diff --git a/server/sonar-web/src/main/js/apps/provisioning/list-view.js b/server/sonar-web/src/main/js/apps/provisioning/list-view.js
index 138c36b7619..24878864d30 100644
--- a/server/sonar-web/src/main/js/apps/provisioning/list-view.js
+++ b/server/sonar-web/src/main/js/apps/provisioning/list-view.js
@@ -5,7 +5,7 @@ define([
return Marionette.CollectionView.extend({
tagName: 'ul',
- itemView: ListItemView
+ childView: ListItemView
});
});
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/delete-view.js b/server/sonar-web/src/main/js/apps/quality-gates/delete-view.js
index 793934460ca..d142b883c84 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/delete-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-gates/delete-view.js
@@ -22,7 +22,7 @@ define([
};
return this.model.destroy(options)
.done(function () {
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.enableForm();
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/details-view.js b/server/sonar-web/src/main/js/apps/quality-gates/details-view.js
index df054717447..9af1b38e65e 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/details-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-gates/details-view.js
@@ -5,7 +5,7 @@ define([
'./templates'
], function (Conditions, DetailConditionsView, ProjectsView) {
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
template: Templates['quality-gate-detail'],
regions: {
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/form-view.js b/server/sonar-web/src/main/js/apps/quality-gates/form-view.js
index 5da9dfd05a2..9b61f400bf3 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/form-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-gates/form-view.js
@@ -24,7 +24,7 @@ define([
});
return Backbone.ajax(opts)
.done(function () {
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.enableForm();
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate-condition-view.js b/server/sonar-web/src/main/js/apps/quality-gates/gate-condition-view.js
index 49797716adf..0dfbea0ab17 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/gate-condition-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-gates/gate-condition-view.js
@@ -68,7 +68,7 @@ define([
},
cancelAddCondition: function () {
- this.close();
+ this.destroy();
},
enableUpdate: function () {
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-delete-view.js b/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-delete-view.js
index 83e32ee2455..c8ed23a2883 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-delete-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-delete-view.js
@@ -22,7 +22,7 @@ define([
};
return this.model.destroy(options)
.done(function () {
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.enableForm();
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-view.js b/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-view.js
index 5ec13c6734a..b0c0857cc95 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-gates/gate-conditions-view.js
@@ -7,9 +7,9 @@ define([
return Marionette.CompositeView.extend({
template: Templates['quality-gate-detail-conditions'],
- itemView: ConditionView,
+ childView: ConditionView,
emptyView: ConditionsEmptyView,
- itemViewContainer: '.js-conditions',
+ childViewContainer: '.js-conditions',
ui: {
metricSelect: '#quality-gate-new-condition-metric'
@@ -20,7 +20,7 @@ define([
'change @ui.metricSelect': 'addCondition'
},
- itemViewOptions: function () {
+ childViewOptions: function () {
return {
canEdit: this.options.canEdit,
gate: this.model,
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/gates-view.js b/server/sonar-web/src/main/js/apps/quality-gates/gates-view.js
index 6c05b728d55..cd36c381a39 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/gates-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-gates/gates-view.js
@@ -6,10 +6,10 @@ define([
return Marionette.CompositeView.extend({
className: 'list-group',
template: Templates['quality-gates-gates'],
- itemView: ItemView,
- itemViewContainer: '.js-list',
+ childView: ItemView,
+ childViewContainer: '.js-list',
- itemViewOptions: function (model) {
+ childViewOptions: function (model) {
return {
collectionView: this,
highlighted: model.id === this.highlighted
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/layout.js b/server/sonar-web/src/main/js/apps/quality-gates/layout.js
index e9ae08550c4..791ba30ae83 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/layout.js
+++ b/server/sonar-web/src/main/js/apps/quality-gates/layout.js
@@ -5,7 +5,7 @@ define([
var $ = jQuery;
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
template: Templates['quality-gates-layout'],
regions: {
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/change-profile-parent-view.js b/server/sonar-web/src/main/js/apps/quality-profiles/change-profile-parent-view.js
index e3c30699fe7..a0813a661c9 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/change-profile-parent-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/change-profile-parent-view.js
@@ -60,7 +60,7 @@ define([
}).done(function () {
that.model.collection.fetch();
that.model.trigger('select', that.model);
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
that.enableForm();
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/copy-profile-view.js b/server/sonar-web/src/main/js/apps/quality-profiles/copy-profile-view.js
index 446254c1c4c..307a904e192 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/copy-profile-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/copy-profile-view.js
@@ -52,7 +52,7 @@ define([
}
}).done(function (r) {
that.addProfile(r);
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.enableForm();
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/create-profile-view.js b/server/sonar-web/src/main/js/apps/quality-profiles/create-profile-view.js
index ec5a0923d4b..531edc0f342 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/create-profile-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/create-profile-view.js
@@ -74,7 +74,7 @@ define([
that.showErrors(r.errors, r.warnings);
} else {
that.addProfile(r.profile);
- that.close();
+ that.destroy();
}
});
},
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/delete-profile-view.js b/server/sonar-web/src/main/js/apps/quality-profiles/delete-profile-view.js
index 3b2ccee277a..e58f870c672 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/delete-profile-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/delete-profile-view.js
@@ -28,7 +28,7 @@ define([
template: Templates['quality-profiles-delete-profile'],
modelEvents: {
- 'destroy': 'close'
+ 'destroy': 'destroy'
},
onFormSubmit: function () {
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/layout.js b/server/sonar-web/src/main/js/apps/quality-profiles/layout.js
index 9099a11fa78..7ec62799470 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/layout.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/layout.js
@@ -24,7 +24,7 @@ define([
var $ = jQuery;
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
template: Templates['quality-profiles-layout'],
regions: {
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/profile-details-view.js b/server/sonar-web/src/main/js/apps/quality-profiles/profile-details-view.js
index ede00f14d73..3f9d16f7ea2 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/profile-details-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/profile-details-view.js
@@ -28,7 +28,7 @@ define([
var $ = jQuery;
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
template: Templates['quality-profiles-profile-details'],
regions: {
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/profile-view.js b/server/sonar-web/src/main/js/apps/quality-profiles/profile-view.js
index 03e5352e2d0..34de0a2d0c0 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/profile-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/profile-view.js
@@ -41,7 +41,7 @@ define([
this.$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
},
- onClose: function () {
+ onDestroy: function () {
this.$('[data-toggle="tooltip"]').tooltip('destroy');
},
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/profiles-view.js b/server/sonar-web/src/main/js/apps/quality-profiles/profiles-view.js
index 2a433476dcd..ae40ab623a5 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/profiles-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/profiles-view.js
@@ -26,14 +26,14 @@ define([
className: 'list-group',
template: Templates['quality-profiles-profiles'],
languageTemplate: Templates['quality-profiles-profiles-language'],
- itemView: ProfileView,
- itemViewContainer: '.js-list',
+ childView: ProfileView,
+ childViewContainer: '.js-list',
collectionEvents: {
'filter': 'filterByLanguage'
},
- itemViewOptions: function (model) {
+ childViewOptions: function (model) {
return {
collectionView: this,
highlighted: model.get('key') === this.highlighted
@@ -45,8 +45,8 @@ define([
this.render();
},
- appendHtml: function (compositeView, itemView, index) {
- var $container = this.getItemViewContainer(compositeView),
+ attachHtml: function (compositeView, childView, index) {
+ var $container = this.getChildViewContainer(compositeView),
model = this.collection.at(index);
if (model != null) {
var prev = this.collection.at(index - 1),
@@ -62,7 +62,7 @@ define([
$container.append(this.languageTemplate(model.toJSON()));
}
}
- return $container.append(itemView.el);
+ compositeView._insertAfter(childView);
},
closeChildren: function () {
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/rename-profile-view.js b/server/sonar-web/src/main/js/apps/quality-profiles/rename-profile-view.js
index b61d9d8a794..1091198d747 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/rename-profile-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/rename-profile-view.js
@@ -50,7 +50,7 @@ define([
}
}).done(function () {
that.model.set({ name: name });
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/restore-built-in-profiles-view.js b/server/sonar-web/src/main/js/apps/quality-profiles/restore-built-in-profiles-view.js
index 645197f7b55..bd223c261f3 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/restore-built-in-profiles-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/restore-built-in-profiles-view.js
@@ -58,7 +58,7 @@ define([
}).done(function () {
that.collection.fetch({ reset: true });
that.collection.trigger('destroy');
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
that.enableForm();
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/restore-profile-view.js b/server/sonar-web/src/main/js/apps/quality-profiles/restore-profile-view.js
index fca5b66476f..09b8feeb812 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/restore-profile-view.js
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/restore-profile-view.js
@@ -37,7 +37,7 @@ define([
that.showErrors(r.errors, r.warnings);
} else {
that.addProfile(r.profile);
- that.close();
+ that.destroy();
}
});
},
diff --git a/server/sonar-web/src/main/js/apps/users/change-password-view.js b/server/sonar-web/src/main/js/apps/users/change-password-view.js
index 6187333c9e6..659d3012a79 100644
--- a/server/sonar-web/src/main/js/apps/users/change-password-view.js
+++ b/server/sonar-web/src/main/js/apps/users/change-password-view.js
@@ -26,7 +26,7 @@ define([
400: null
}
}).done(function () {
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.enableForm();
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
diff --git a/server/sonar-web/src/main/js/apps/users/create-view.js b/server/sonar-web/src/main/js/apps/users/create-view.js
index 026f8095056..562e41e8d5d 100644
--- a/server/sonar-web/src/main/js/apps/users/create-view.js
+++ b/server/sonar-web/src/main/js/apps/users/create-view.js
@@ -22,7 +22,7 @@ define([
}
}).done(function () {
that.collection.refresh();
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.enableForm();
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
diff --git a/server/sonar-web/src/main/js/apps/users/deactivate-view.js b/server/sonar-web/src/main/js/apps/users/deactivate-view.js
index 37c71d4a94b..000a350ea57 100644
--- a/server/sonar-web/src/main/js/apps/users/deactivate-view.js
+++ b/server/sonar-web/src/main/js/apps/users/deactivate-view.js
@@ -22,7 +22,7 @@ define([
}
}).done(function () {
collection.total--;
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
});
diff --git a/server/sonar-web/src/main/js/apps/users/form-view.js b/server/sonar-web/src/main/js/apps/users/form-view.js
index f1c7f602d14..50b18c1d237 100644
--- a/server/sonar-web/src/main/js/apps/users/form-view.js
+++ b/server/sonar-web/src/main/js/apps/users/form-view.js
@@ -19,7 +19,7 @@ define([
this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
},
- onClose: function () {
+ onDestroy: function () {
this._super();
this.$('[data-toggle="tooltip"]').tooltip('destroy');
},
diff --git a/server/sonar-web/src/main/js/apps/users/groups-view.js b/server/sonar-web/src/main/js/apps/users/groups-view.js
index 8fb864add9d..09a127fc6fd 100644
--- a/server/sonar-web/src/main/js/apps/users/groups-view.js
+++ b/server/sonar-web/src/main/js/apps/users/groups-view.js
@@ -34,9 +34,9 @@ define([
});
},
- onClose: function () {
+ onDestroy: function () {
this.model.collection.refresh();
- Modal.prototype.onClose.apply(this, arguments);
+ Modal.prototype.onDestroy.apply(this, arguments);
}
});
diff --git a/server/sonar-web/src/main/js/apps/users/layout.js b/server/sonar-web/src/main/js/apps/users/layout.js
index d2b625162e0..9acb054bdad 100644
--- a/server/sonar-web/src/main/js/apps/users/layout.js
+++ b/server/sonar-web/src/main/js/apps/users/layout.js
@@ -2,7 +2,7 @@ define([
'./templates'
], function () {
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
template: Templates['users-layout'],
regions: {
diff --git a/server/sonar-web/src/main/js/apps/users/list-item-view.js b/server/sonar-web/src/main/js/apps/users/list-item-view.js
index 99aa973579b..b11e39e2766 100644
--- a/server/sonar-web/src/main/js/apps/users/list-item-view.js
+++ b/server/sonar-web/src/main/js/apps/users/list-item-view.js
@@ -30,7 +30,7 @@ define([
this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
},
- onClose: function () {
+ onDestroy: function () {
this.$('[data-toggle="tooltip"]').tooltip('destroy');
},
diff --git a/server/sonar-web/src/main/js/apps/users/list-view.js b/server/sonar-web/src/main/js/apps/users/list-view.js
index 138c36b7619..24878864d30 100644
--- a/server/sonar-web/src/main/js/apps/users/list-view.js
+++ b/server/sonar-web/src/main/js/apps/users/list-view.js
@@ -5,7 +5,7 @@ define([
return Marionette.CollectionView.extend({
tagName: 'ul',
- itemView: ListItemView
+ childView: ListItemView
});
});
diff --git a/server/sonar-web/src/main/js/apps/users/update-view.js b/server/sonar-web/src/main/js/apps/users/update-view.js
index 81497a3a75d..b276b79ba42 100644
--- a/server/sonar-web/src/main/js/apps/users/update-view.js
+++ b/server/sonar-web/src/main/js/apps/users/update-view.js
@@ -19,7 +19,7 @@ define([
}
}).done(function () {
that.collection.refresh();
- that.close();
+ that.destroy();
}).fail(function (jqXHR) {
that.enableForm();
that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
diff --git a/server/sonar-web/src/main/js/components/common/modals.js b/server/sonar-web/src/main/js/components/common/modals.js
index 243ddb04948..b4812096177 100644
--- a/server/sonar-web/src/main/js/components/common/modals.js
+++ b/server/sonar-web/src/main/js/components/common/modals.js
@@ -41,7 +41,7 @@ define(function () {
this.keyScope = key.getScope();
key.setScope('modal');
key('escape', 'modal', function () {
- that.close();
+ that.destroy();
return false;
});
this.show();
@@ -58,7 +58,7 @@ define(function () {
}, 0);
},
- onClose: function () {
+ onDestroy: function () {
$('html').removeClass(this.htmlClassName);
this.removeOverlay();
key.deleteScope('modal');
@@ -67,7 +67,7 @@ define(function () {
onCloseClick: function (e) {
e.preventDefault();
- this.close();
+ this.destroy();
},
renderOverlay: function () {
@@ -85,7 +85,7 @@ define(function () {
var that = this;
$('body').on('click.' + EVENT_SCOPE, function () {
$('body').off('click.' + EVENT_SCOPE);
- that.close();
+ that.destroy();
});
}
});
diff --git a/server/sonar-web/src/main/js/components/common/popup.js b/server/sonar-web/src/main/js/components/common/popup.js
index f978b7ab380..a3d15c1bbb8 100644
--- a/server/sonar-web/src/main/js/components/common/popup.js
+++ b/server/sonar-web/src/main/js/components/common/popup.js
@@ -31,20 +31,20 @@ define(function () {
attachCloseEvents: function () {
var that = this;
key('escape', function () {
- that.close();
+ that.destroy();
});
$('body').on('click.bubble-popup', function () {
$('body').off('click.bubble-popup');
- that.close();
+ that.destroy();
});
this.options.triggerEl.on('click.bubble-popup', function (e) {
that.options.triggerEl.off('click.bubble-popup');
e.stopPropagation();
- that.close();
+ that.destroy();
});
},
- onClose: function () {
+ onDestroy: function () {
$('body').off('click.bubble-popup');
this.options.triggerEl.off('click.bubble-popup');
}
diff --git a/server/sonar-web/src/main/js/components/common/selectable-collection-view.js b/server/sonar-web/src/main/js/components/common/selectable-collection-view.js
index b025ba925a5..c7250855946 100644
--- a/server/sonar-web/src/main/js/components/common/selectable-collection-view.js
+++ b/server/sonar-web/src/main/js/components/common/selectable-collection-view.js
@@ -26,7 +26,7 @@ define(function () {
this.listenTo(this.collection, 'reset', this.resetSelectedIndex);
},
- itemViewOptions: function (model, index) {
+ childViewOptions: function (model, index) {
return { index: index };
},
diff --git a/server/sonar-web/src/main/js/components/issue/issue-view.js b/server/sonar-web/src/main/js/components/issue/issue-view.js
index d7c90ddb777..d211ab65eda 100644
--- a/server/sonar-web/src/main/js/components/issue/issue-view.js
+++ b/server/sonar-web/src/main/js/components/issue/issue-view.js
@@ -72,7 +72,7 @@ define([
data: { issue: this.model.get('key') }
}).done(function () {
if (that.popup) {
- that.popup.close();
+ that.popup.destroy();
}
that.popup = new ChangeLogView({
triggerEl: t,
@@ -86,7 +86,7 @@ define([
updateAfterAction: function (fetch) {
if (this.popup) {
- this.popup.close();
+ this.popup.destroy();
}
if (fetch) {
this.resetIssue();
@@ -176,7 +176,7 @@ define([
triggerEl: $('body')
});
view.submit(window.SS.user, window.SS.userName);
- view.close();
+ view.destroy();
},
plan: function (e) {
diff --git a/server/sonar-web/src/main/js/components/issue/manual-issue-view.js b/server/sonar-web/src/main/js/components/issue/manual-issue-view.js
index 1b043544a44..2f819f62f2b 100644
--- a/server/sonar-web/src/main/js/components/issue/manual-issue-view.js
+++ b/server/sonar-web/src/main/js/components/issue/manual-issue-view.js
@@ -37,7 +37,7 @@ define([
}
},
- onClose: function () {
+ onDestroy: function () {
if (key != null && this.key != null) {
key.setScope(this.key);
}
@@ -61,13 +61,13 @@ define([
var that = this;
return issue.fetch().done(function () {
that.trigger('add', issue);
- that.close();
+ that.destroy();
});
},
cancel: function (e) {
e.preventDefault();
- this.close();
+ this.destroy();
},
serializeData: function () {
diff --git a/server/sonar-web/src/main/js/components/issue/views/action-options-view.js b/server/sonar-web/src/main/js/components/issue/views/action-options-view.js
index 402a0cc6ec6..57b5c95d0a9 100644
--- a/server/sonar-web/src/main/js/components/issue/views/action-options-view.js
+++ b/server/sonar-web/src/main/js/components/issue/views/action-options-view.js
@@ -75,7 +75,7 @@ define([
return that.selectActiveOption();
});
key('escape', this.keyScope, function () {
- return that.close();
+ return that.destroy();
});
key('backspace', this.keyScope, function () {
return false;
@@ -96,8 +96,8 @@ define([
key.setScope(this.currentKeyScope);
},
- onClose: function () {
- PopupView.prototype.onClose.apply(this, arguments);
+ onDestroy: function () {
+ PopupView.prototype.onDestroy.apply(this, arguments);
this.unbindShortcuts();
this.$('[data-toggle="tooltip"]').tooltip('destroy');
$('.tooltip').remove();
@@ -105,7 +105,7 @@ define([
selectOption: function (e) {
e.preventDefault();
- this.close();
+ this.destroy();
},
selectActiveOption: function () {
diff --git a/server/sonar-web/src/main/js/components/issue/views/assign-form-view.js b/server/sonar-web/src/main/js/components/issue/views/assign-form-view.js
index f2894e865af..bca6a296365 100644
--- a/server/sonar-web/src/main/js/components/issue/views/assign-form-view.js
+++ b/server/sonar-web/src/main/js/components/issue/views/assign-form-view.js
@@ -98,7 +98,7 @@ define([
this.selectActiveOption();
}
if (e.keyCode === 27) {
- this.close();
+ this.destroy();
}
if ([9, 13, 27, 38, 40].indexOf(e.keyCode) !== -1) {
return false;
diff --git a/server/sonar-web/src/main/js/components/issue/views/comment-form-view.js b/server/sonar-web/src/main/js/components/issue/views/comment-form-view.js
index 10e9c713e09..52cd3917a2c 100644
--- a/server/sonar-web/src/main/js/components/issue/views/comment-form-view.js
+++ b/server/sonar-web/src/main/js/components/issue/views/comment-form-view.js
@@ -41,7 +41,7 @@ define([
onKeydown: function (e) {
if (e.keyCode === 27) {
- this.close();
+ this.destroy();
}
},
diff --git a/server/sonar-web/src/main/js/components/issue/views/issue-popup.js b/server/sonar-web/src/main/js/components/issue/views/issue-popup.js
index 575fb50a52a..9c36ad104b5 100644
--- a/server/sonar-web/src/main/js/components/issue/views/issue-popup.js
+++ b/server/sonar-web/src/main/js/components/issue/views/issue-popup.js
@@ -11,7 +11,7 @@ define([
events: function () {
return {
- 'click .js-issue-form-cancel': 'close'
+ 'click .js-issue-form-cancel': 'destroy'
};
},
@@ -21,8 +21,8 @@ define([
this.options.view.render();
},
- onClose: function () {
- this.options.view.close();
+ onDestroy: function () {
+ this.options.view.destroy();
},
attachCloseEvents: function () {
diff --git a/server/sonar-web/src/main/js/components/issue/views/tags-form-view.js b/server/sonar-web/src/main/js/components/issue/views/tags-form-view.js
index ee04d7103c9..868230ccd88 100644
--- a/server/sonar-web/src/main/js/components/issue/views/tags-form-view.js
+++ b/server/sonar-web/src/main/js/components/issue/views/tags-form-view.js
@@ -142,7 +142,7 @@ define([
this.selectActiveOption();
}
if (e.keyCode === 27) {
- this.close();
+ this.destroy();
}
if ([9, 13, 27, 38, 40].indexOf(e.keyCode) !== -1) {
return false;
diff --git a/server/sonar-web/src/main/js/components/navigator/facets-view.js b/server/sonar-web/src/main/js/components/navigator/facets-view.js
index b137c39e81c..bf881e018fa 100644
--- a/server/sonar-web/src/main/js/components/navigator/facets-view.js
+++ b/server/sonar-web/src/main/js/components/navigator/facets-view.js
@@ -24,13 +24,13 @@ define([
return Marionette.CollectionView.extend({
className: 'search-navigator-facets-list',
- itemViewOptions: function () {
+ childViewOptions: function () {
return {
app: this.options.app
};
},
- getItemView: function () {
+ getChildView: function () {
return BaseFacet;
},
diff --git a/server/sonar-web/src/main/js/components/navigator/filters/filter-bar.js b/server/sonar-web/src/main/js/components/navigator/filters/filter-bar.js
index b87da53c1d6..28ee2ceb836 100644
--- a/server/sonar-web/src/main/js/components/navigator/filters/filter-bar.js
+++ b/server/sonar-web/src/main/js/components/navigator/filters/filter-bar.js
@@ -26,7 +26,7 @@ define(
function (BaseFilters, MoreCriteriaFilters) {
return Marionette.CompositeView.extend({
- itemViewContainer: '.navigator-filters-list',
+ childViewContainer: '.navigator-filters-list',
collectionEvents: {
@@ -34,12 +34,12 @@ define(
},
- getItemView: function (item) {
+ getChildView: function (item) {
return item.get('type') || BaseFilters.BaseFilterView;
},
- itemViewOptions: function () {
+ childViewOptions: function () {
return {
filterBarView: this,
app: this.options.app
@@ -94,7 +94,7 @@ define(
getEnabledFilters: function() {
- return this.$(this.itemViewContainer).children()
+ return this.$(this.childViewContainer).children()
.not('.navigator-filter-disabled')
.not('.navigator-filter-inactive')
.not('.navigator-filter-favorite');
@@ -146,8 +146,8 @@ define(
},
- onAfterItemAdded: function (itemView) {
- if (itemView.model.get('type') === MoreCriteriaFilters.FavoriteFilterView) {
+ onAddChild: function (childView) {
+ if (childView.model.get('type') === MoreCriteriaFilters.FavoriteFilterView) {
jQuery('.navigator-header').addClass('navigator-header-favorite');
}
},
diff --git a/server/sonar-web/src/main/js/components/navigator/workspace-header-view.js b/server/sonar-web/src/main/js/components/navigator/workspace-header-view.js
index 524346fa080..75e34798616 100644
--- a/server/sonar-web/src/main/js/components/navigator/workspace-header-view.js
+++ b/server/sonar-web/src/main/js/components/navigator/workspace-header-view.js
@@ -48,7 +48,7 @@ define(function () {
this.$('[data-toggle="tooltip"]').tooltip('destroy');
},
- onClose: function () {
+ onDestroy: function () {
this.$('[data-toggle="tooltip"]').tooltip('destroy');
},
diff --git a/server/sonar-web/src/main/js/components/navigator/workspace-list-view.js b/server/sonar-web/src/main/js/components/navigator/workspace-list-view.js
index 0296b1589aa..a9c1831c835 100644
--- a/server/sonar-web/src/main/js/components/navigator/workspace-list-view.js
+++ b/server/sonar-web/src/main/js/components/navigator/workspace-list-view.js
@@ -27,7 +27,7 @@ define(function () {
loadMore: '.js-more'
},
- itemViewOptions: function () {
+ childViewOptions: function () {
return {
app: this.options.app
};
@@ -44,7 +44,7 @@ define(function () {
this.bindShortcuts();
},
- onClose: function () {
+ onDestroy: function () {
this.unbindScrollEvents();
this.unbindShortcuts();
},
diff --git a/server/sonar-web/src/main/js/components/source-viewer/main.js b/server/sonar-web/src/main/js/components/source-viewer/main.js
index a007145f354..947d6582af0 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/main.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/main.js
@@ -42,7 +42,7 @@ define([
var $ = jQuery,
HIGHLIGHTED_ROW_CLASS = 'source-line-highlighted';
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
className: 'source-viewer',
template: Templates['source-viewer'],
@@ -106,9 +106,9 @@ define([
this.$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
},
- onClose: function () {
+ onDestroy: function () {
this.issueViews.forEach(function (view) {
- return view.close();
+ return view.destroy();
});
this.issueViews = [];
this.clearTooltips();
diff --git a/server/sonar-web/src/main/js/components/source-viewer/more-actions.js b/server/sonar-web/src/main/js/components/source-viewer/more-actions.js
index c3e7698b8f9..a1b5ff87f99 100644
--- a/server/sonar-web/src/main/js/components/source-viewer/more-actions.js
+++ b/server/sonar-web/src/main/js/components/source-viewer/more-actions.js
@@ -39,7 +39,7 @@ define([
var that = this;
$('body').on('click.component-viewer-more-actions', function () {
$('body').off('click.component-viewer-more-actions');
- that.close();
+ that.destroy();
});
},
diff --git a/server/sonar-web/src/main/js/components/workspace/main.js b/server/sonar-web/src/main/js/components/workspace/main.js
index 5f766ce00fa..7aa7f901aa6 100644
--- a/server/sonar-web/src/main/js/components/workspace/main.js
+++ b/server/sonar-web/src/main/js/components/workspace/main.js
@@ -89,7 +89,7 @@ define([
var that = this;
if (this.viewerView != null) {
this.viewerView.model.trigger('hideViewer');
- this.viewerView.close();
+ this.viewerView.destroy();
}
$('html').addClass('with-workspace');
model.trigger('showViewer');
@@ -112,7 +112,7 @@ define([
closeComponentViewer: function () {
if (this.viewerView != null) {
- this.viewerView.close();
+ this.viewerView.destroy();
$('.with-workspace').removeClass('with-workspace');
}
},
diff --git a/server/sonar-web/src/main/js/components/workspace/views/base-viewer-view.js b/server/sonar-web/src/main/js/components/workspace/views/base-viewer-view.js
index 761b9887184..c917fbc9f65 100644
--- a/server/sonar-web/src/main/js/components/workspace/views/base-viewer-view.js
+++ b/server/sonar-web/src/main/js/components/workspace/views/base-viewer-view.js
@@ -21,11 +21,11 @@ define([
'./viewer-header-view'
], function (HeaderView) {
- return Marionette.Layout.extend({
+ return Marionette.LayoutView.extend({
className: 'workspace-viewer',
modelEvents: {
- 'destroy': 'close'
+ 'destroy': 'destroy'
},
regions: {
diff --git a/server/sonar-web/src/main/js/components/workspace/views/items-view.js b/server/sonar-web/src/main/js/components/workspace/views/items-view.js
index a83eeb0f3e8..36aa5679097 100644
--- a/server/sonar-web/src/main/js/components/workspace/views/items-view.js
+++ b/server/sonar-web/src/main/js/components/workspace/views/items-view.js
@@ -25,10 +25,10 @@ define([
return Marionette.CompositeView.extend({
className: 'workspace-nav',
template: Templates['workspace-items'],
- itemViewContainer: '.workspace-nav-list',
- itemView: ItemView,
+ childViewContainer: '.workspace-nav-list',
+ childView: ItemView,
- itemViewOptions: function () {
+ childViewOptions: function () {
return { collectionView: this };
}
});
diff --git a/server/sonar-web/src/main/js/components/workspace/views/rule-view.js b/server/sonar-web/src/main/js/components/workspace/views/rule-view.js
index 4b4a498244b..b6ca04fee6f 100644
--- a/server/sonar-web/src/main/js/components/workspace/views/rule-view.js
+++ b/server/sonar-web/src/main/js/components/workspace/views/rule-view.js
@@ -26,7 +26,7 @@ define([
template: Templates['workspace-rule'],
modelEvents: {
- 'destroy': 'close',
+ 'destroy': 'destroy',
'change': 'render'
},
@@ -35,12 +35,12 @@ define([
this.$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
},
- onClose: function () {
+ onDestroy: function () {
this.$('[data-toggle="tooltip"]').tooltip('destroy');
},
serializeData: function () {
- return _.extend(Marionette.Layout.prototype.serializeData.apply(this, arguments), {
+ return _.extend(Marionette.LayoutView.prototype.serializeData.apply(this, arguments), {
allTags: _.union(this.model.get('sysTags'), this.model.get('tags'))
});
}
diff --git a/server/sonar-web/src/main/js/components/workspace/views/viewer-header-view.js b/server/sonar-web/src/main/js/components/workspace/views/viewer-header-view.js
index b0705fb724a..6ef5b47f375 100644
--- a/server/sonar-web/src/main/js/components/workspace/views/viewer-header-view.js
+++ b/server/sonar-web/src/main/js/components/workspace/views/viewer-header-view.js
@@ -44,7 +44,7 @@ define([
this.$('.js-normal-size').addClass('hidden');
},
- onClose: function () {
+ onDestroy: function () {
this.$('[data-toggle="tooltip"]').tooltip('destroy');
$('.tooltip').remove();
},
diff --git a/server/sonar-web/src/main/js/libs/third-party/backbone.marionette.js b/server/sonar-web/src/main/js/libs/third-party/backbone.marionette.js
index a7dfbc340fc..d0e801ddcfa 100644
--- a/server/sonar-web/src/main/js/libs/third-party/backbone.marionette.js
+++ b/server/sonar-web/src/main/js/libs/third-party/backbone.marionette.js
@@ -1,14 +1,13 @@
// MarionetteJS (Backbone.Marionette)
// ----------------------------------
-// v1.6.3
+// v2.4.1
//
-// Copyright (c)2014 Derick Bailey, Muted Solutions, LLC.
+// Copyright (c)2015 Derick Bailey, Muted Solutions, LLC.
// Distributed under MIT license
//
// http://marionettejs.com
-
/*!
* Includes BabySitter
* https://github.com/marionettejs/backbone.babysitter/
@@ -17,2537 +16,3804 @@
* https://github.com/marionettejs/backbone.wreqr/
*/
-// Backbone.BabySitter
-// -------------------
-// v0.1.0
-//
-// Copyright (c)2014 Derick Bailey, Muted Solutions, LLC.
-// Distributed under MIT license
-//
-// http://github.com/marionettejs/backbone.babysitter
-
-// Backbone.ChildViewContainer
-// ---------------------------
-//
-// Provide a container to store, retrieve and
-// shut down child views.
-
-Backbone.ChildViewContainer = (function(Backbone, _){
-
- // Container Constructor
- // ---------------------
-
- var Container = function(views){
- this._views = {};
- this._indexByModel = {};
- this._indexByCustom = {};
- this._updateLength();
-
- _.each(views, this.add, this);
- };
-
- // Container Methods
- // -----------------
-
- _.extend(Container.prototype, {
-
- // Add a view to this container. Stores the view
- // by `cid` and makes it searchable by the model
- // cid (and model itself). Optionally specify
- // a custom key to store an retrieve the view.
- add: function(view, customIndex){
- var viewCid = view.cid;
- // store the view
- this._views[viewCid] = view;
-
- // index it by model
- if (view.model){
- this._indexByModel[view.model.cid] = viewCid;
- }
-
- // index by custom
- if (customIndex){
- this._indexByCustom[customIndex] = viewCid;
- }
-
- this._updateLength();
- return this;
- },
-
- // Find a view by the model that was attached to
- // it. Uses the model's `cid` to find it.
- findByModel: function(model){
- return this.findByModelCid(model.cid);
- },
-
- // Find a view by the `cid` of the model that was attached to
- // it. Uses the model's `cid` to find the view `cid` and
- // retrieve the view using it.
- findByModelCid: function(modelCid){
- var viewCid = this._indexByModel[modelCid];
- return this.findByCid(viewCid);
- },
-
- // Find a view by a custom indexer.
- findByCustom: function(index){
- var viewCid = this._indexByCustom[index];
- return this.findByCid(viewCid);
- },
-
- // Find by index. This is not guaranteed to be a
- // stable index.
- findByIndex: function(index){
- return _.values(this._views)[index];
- },
+(function(root, factory) {
- // retrieve a view by its `cid` directly
- findByCid: function(cid){
- return this._views[cid];
- },
-
- // Remove a view
- remove: function(view){
- var viewCid = view.cid;
-
- // delete model index
- if (view.model){
- delete this._indexByModel[view.model.cid];
- }
-
- // delete custom index
- _.any(this._indexByCustom, function(cid, key) {
- if (cid === viewCid) {
- delete this._indexByCustom[key];
- return true;
- }
- }, this);
-
- // remove the view from the container
- delete this._views[viewCid];
-
- // update the length
- this._updateLength();
- return this;
- },
+ /* istanbul ignore next */
+ if (typeof define === 'function' && define.amd) {
+ define(['backbone', 'underscore'], function(Backbone, _) {
+ return (root.Marionette = root.Mn = factory(root, Backbone, _));
+ });
+ } else if (typeof exports !== 'undefined') {
+ var Backbone = require('backbone');
+ var _ = require('underscore');
+ module.exports = factory(root, Backbone, _);
+ } else {
+ root.Marionette = root.Mn = factory(root, root.Backbone, root._);
+ }
- // Call a method on every view in the container,
- // passing parameters to the call method one at a
- // time, like `function.call`.
- call: function(method){
- this.apply(method, _.tail(arguments));
- },
+}(this, function(root, Backbone, _) {
+ 'use strict';
- // Apply a method on every view in the container,
- // passing parameters to the call method one at a
- // time, like `function.apply`.
- apply: function(method, args){
- _.each(this._views, function(view){
- if (_.isFunction(view[method])){
- view[method].apply(view, args || []);
+ /* istanbul ignore next */
+ // Backbone.BabySitter
+ // -------------------
+ // v0.1.6
+ //
+ // Copyright (c)2015 Derick Bailey, Muted Solutions, LLC.
+ // Distributed under MIT license
+ //
+ // http://github.com/marionettejs/backbone.babysitter
+ (function(Backbone, _) {
+ "use strict";
+ var previousChildViewContainer = Backbone.ChildViewContainer;
+ // BabySitter.ChildViewContainer
+ // -----------------------------
+ //
+ // Provide a container to store, retrieve and
+ // shut down child views.
+ Backbone.ChildViewContainer = function(Backbone, _) {
+ // Container Constructor
+ // ---------------------
+ var Container = function(views) {
+ this._views = {};
+ this._indexByModel = {};
+ this._indexByCustom = {};
+ this._updateLength();
+ _.each(views, this.add, this);
+ };
+ // Container Methods
+ // -----------------
+ _.extend(Container.prototype, {
+ // Add a view to this container. Stores the view
+ // by `cid` and makes it searchable by the model
+ // cid (and model itself). Optionally specify
+ // a custom key to store an retrieve the view.
+ add: function(view, customIndex) {
+ var viewCid = view.cid;
+ // store the view
+ this._views[viewCid] = view;
+ // index it by model
+ if (view.model) {
+ this._indexByModel[view.model.cid] = viewCid;
+ }
+ // index by custom
+ if (customIndex) {
+ this._indexByCustom[customIndex] = viewCid;
+ }
+ this._updateLength();
+ return this;
+ },
+ // Find a view by the model that was attached to
+ // it. Uses the model's `cid` to find it.
+ findByModel: function(model) {
+ return this.findByModelCid(model.cid);
+ },
+ // Find a view by the `cid` of the model that was attached to
+ // it. Uses the model's `cid` to find the view `cid` and
+ // retrieve the view using it.
+ findByModelCid: function(modelCid) {
+ var viewCid = this._indexByModel[modelCid];
+ return this.findByCid(viewCid);
+ },
+ // Find a view by a custom indexer.
+ findByCustom: function(index) {
+ var viewCid = this._indexByCustom[index];
+ return this.findByCid(viewCid);
+ },
+ // Find by index. This is not guaranteed to be a
+ // stable index.
+ findByIndex: function(index) {
+ return _.values(this._views)[index];
+ },
+ // retrieve a view by its `cid` directly
+ findByCid: function(cid) {
+ return this._views[cid];
+ },
+ // Remove a view
+ remove: function(view) {
+ var viewCid = view.cid;
+ // delete model index
+ if (view.model) {
+ delete this._indexByModel[view.model.cid];
+ }
+ // delete custom index
+ _.any(this._indexByCustom, function(cid, key) {
+ if (cid === viewCid) {
+ delete this._indexByCustom[key];
+ return true;
+ }
+ }, this);
+ // remove the view from the container
+ delete this._views[viewCid];
+ // update the length
+ this._updateLength();
+ return this;
+ },
+ // Call a method on every view in the container,
+ // passing parameters to the call method one at a
+ // time, like `function.call`.
+ call: function(method) {
+ this.apply(method, _.tail(arguments));
+ },
+ // Apply a method on every view in the container,
+ // passing parameters to the call method one at a
+ // time, like `function.apply`.
+ apply: function(method, args) {
+ _.each(this._views, function(view) {
+ if (_.isFunction(view[method])) {
+ view[method].apply(view, args || []);
+ }
+ });
+ },
+ // Update the `.length` attribute on this container
+ _updateLength: function() {
+ this.length = _.size(this._views);
}
});
- },
-
- // Update the `.length` attribute on this container
- _updateLength: function(){
- this.length = _.size(this._views);
- }
- });
+ // Borrowing this code from Backbone.Collection:
+ // http://backbonejs.org/docs/backbone.html#section-106
+ //
+ // Mix in methods from Underscore, for iteration, and other
+ // collection related features.
+ var methods = [ "forEach", "each", "map", "find", "detect", "filter", "select", "reject", "every", "all", "some", "any", "include", "contains", "invoke", "toArray", "first", "initial", "rest", "last", "without", "isEmpty", "pluck", "reduce" ];
+ _.each(methods, function(method) {
+ Container.prototype[method] = function() {
+ var views = _.values(this._views);
+ var args = [ views ].concat(_.toArray(arguments));
+ return _[method].apply(_, args);
+ };
+ });
+ // return the public API
+ return Container;
+ }(Backbone, _);
+ Backbone.ChildViewContainer.VERSION = "0.1.6";
+ Backbone.ChildViewContainer.noConflict = function() {
+ Backbone.ChildViewContainer = previousChildViewContainer;
+ return this;
+ };
+ return Backbone.ChildViewContainer;
+ })(Backbone, _);
- // Borrowing this code from Backbone.Collection:
- // http://backbonejs.org/docs/backbone.html#section-106
+ /* istanbul ignore next */
+ // Backbone.Wreqr (Backbone.Marionette)
+ // ----------------------------------
+ // v1.3.1
//
- // Mix in methods from Underscore, for iteration, and other
- // collection related features.
- var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
- 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
- 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
- 'last', 'without', 'isEmpty', 'pluck'];
-
- _.each(methods, function(method) {
- Container.prototype[method] = function() {
- var views = _.values(this._views);
- var args = [views].concat(_.toArray(arguments));
- return _[method].apply(_, args);
+ // Copyright (c)2014 Derick Bailey, Muted Solutions, LLC.
+ // Distributed under MIT license
+ //
+ // http://github.com/marionettejs/backbone.wreqr
+ (function(Backbone, _) {
+ "use strict";
+ var previousWreqr = Backbone.Wreqr;
+ var Wreqr = Backbone.Wreqr = {};
+ Backbone.Wreqr.VERSION = "1.3.1";
+ Backbone.Wreqr.noConflict = function() {
+ Backbone.Wreqr = previousWreqr;
+ return this;
};
- });
-
- // return the public API
- return Container;
-})(Backbone, _);
-
-// Backbone.Wreqr (Backbone.Marionette)
-// ----------------------------------
-// v1.0.0
-//
-// Copyright (c)2014 Derick Bailey, Muted Solutions, LLC.
-// Distributed under MIT license
-//
-// http://github.com/marionettejs/backbone.wreqr
-
-
-Backbone.Wreqr = (function(Backbone, Marionette, _){
- "use strict";
- var Wreqr = {};
-
- // Handlers
-// --------
-// A registry of functions to call, given a name
-
-Wreqr.Handlers = (function(Backbone, _){
- "use strict";
-
- // Constructor
- // -----------
-
- var Handlers = function(options){
- this.options = options;
- this._wreqrHandlers = {};
-
- if (_.isFunction(this.initialize)){
- this.initialize(options);
- }
- };
-
- Handlers.extend = Backbone.Model.extend;
-
- // Instance Members
- // ----------------
-
- _.extend(Handlers.prototype, Backbone.Events, {
-
- // Add multiple handlers using an object literal configuration
- setHandlers: function(handlers){
- _.each(handlers, function(handler, name){
- var context = null;
-
- if (_.isObject(handler) && !_.isFunction(handler)){
- context = handler.context;
- handler = handler.callback;
+ // Handlers
+ // --------
+ // A registry of functions to call, given a name
+ Wreqr.Handlers = function(Backbone, _) {
+ "use strict";
+ // Constructor
+ // -----------
+ var Handlers = function(options) {
+ this.options = options;
+ this._wreqrHandlers = {};
+ if (_.isFunction(this.initialize)) {
+ this.initialize(options);
}
-
- this.setHandler(name, handler, context);
- }, this);
- },
-
- // Add a handler for the given name, with an
- // optional context to run the handler within
- setHandler: function(name, handler, context){
- var config = {
- callback: handler,
- context: context
};
-
- this._wreqrHandlers[name] = config;
-
- this.trigger("handler:add", name, handler, context);
- },
-
- // Determine whether or not a handler is registered
- hasHandler: function(name){
- return !! this._wreqrHandlers[name];
- },
-
- // Get the currently registered handler for
- // the specified name. Throws an exception if
- // no handler is found.
- getHandler: function(name){
- var config = this._wreqrHandlers[name];
-
- if (!config){
- throw new Error("Handler not found for '" + name + "'");
- }
-
- return function(){
- var args = Array.prototype.slice.apply(arguments);
- return config.callback.apply(config.context, args);
+ Handlers.extend = Backbone.Model.extend;
+ // Instance Members
+ // ----------------
+ _.extend(Handlers.prototype, Backbone.Events, {
+ // Add multiple handlers using an object literal configuration
+ setHandlers: function(handlers) {
+ _.each(handlers, function(handler, name) {
+ var context = null;
+ if (_.isObject(handler) && !_.isFunction(handler)) {
+ context = handler.context;
+ handler = handler.callback;
+ }
+ this.setHandler(name, handler, context);
+ }, this);
+ },
+ // Add a handler for the given name, with an
+ // optional context to run the handler within
+ setHandler: function(name, handler, context) {
+ var config = {
+ callback: handler,
+ context: context
+ };
+ this._wreqrHandlers[name] = config;
+ this.trigger("handler:add", name, handler, context);
+ },
+ // Determine whether or not a handler is registered
+ hasHandler: function(name) {
+ return !!this._wreqrHandlers[name];
+ },
+ // Get the currently registered handler for
+ // the specified name. Throws an exception if
+ // no handler is found.
+ getHandler: function(name) {
+ var config = this._wreqrHandlers[name];
+ if (!config) {
+ return;
+ }
+ return function() {
+ var args = Array.prototype.slice.apply(arguments);
+ return config.callback.apply(config.context, args);
+ };
+ },
+ // Remove a handler for the specified name
+ removeHandler: function(name) {
+ delete this._wreqrHandlers[name];
+ },
+ // Remove all handlers from this registry
+ removeAllHandlers: function() {
+ this._wreqrHandlers = {};
+ }
+ });
+ return Handlers;
+ }(Backbone, _);
+ // Wreqr.CommandStorage
+ // --------------------
+ //
+ // Store and retrieve commands for execution.
+ Wreqr.CommandStorage = function() {
+ "use strict";
+ // Constructor function
+ var CommandStorage = function(options) {
+ this.options = options;
+ this._commands = {};
+ if (_.isFunction(this.initialize)) {
+ this.initialize(options);
+ }
};
- },
-
- // Remove a handler for the specified name
- removeHandler: function(name){
- delete this._wreqrHandlers[name];
- },
-
- // Remove all handlers from this registry
- removeAllHandlers: function(){
- this._wreqrHandlers = {};
- }
- });
-
- return Handlers;
-})(Backbone, _);
-
- // Wreqr.CommandStorage
-// --------------------
-//
-// Store and retrieve commands for execution.
-Wreqr.CommandStorage = (function(){
- "use strict";
-
- // Constructor function
- var CommandStorage = function(options){
- this.options = options;
- this._commands = {};
-
- if (_.isFunction(this.initialize)){
- this.initialize(options);
- }
- };
-
- // Instance methods
- _.extend(CommandStorage.prototype, Backbone.Events, {
-
- // Get an object literal by command name, that contains
- // the `commandName` and the `instances` of all commands
- // represented as an array of arguments to process
- getCommands: function(commandName){
- var commands = this._commands[commandName];
-
- // we don't have it, so add it
- if (!commands){
-
- // build the configuration
- commands = {
- command: commandName,
- instances: []
- };
-
- // store it
- this._commands[commandName] = commands;
- }
-
- return commands;
- },
-
- // Add a command by name, to the storage and store the
- // args for the command
- addCommand: function(commandName, args){
- var command = this.getCommands(commandName);
- command.instances.push(args);
- },
-
- // Clear all commands for the given `commandName`
- clearCommands: function(commandName){
- var command = this.getCommands(commandName);
- command.instances = [];
- }
- });
-
- return CommandStorage;
-})();
-
- // Wreqr.Commands
-// --------------
-//
-// A simple command pattern implementation. Register a command
-// handler and execute it.
-Wreqr.Commands = (function(Wreqr){
- "use strict";
-
- return Wreqr.Handlers.extend({
- // default storage type
- storageType: Wreqr.CommandStorage,
-
- constructor: function(options){
- this.options = options || {};
-
- this._initializeStorage(this.options);
- this.on("handler:add", this._executeCommands, this);
-
- var args = Array.prototype.slice.call(arguments);
- Wreqr.Handlers.prototype.constructor.apply(this, args);
- },
-
- // Execute a named command with the supplied args
- execute: function(name, args){
- name = arguments[0];
- args = Array.prototype.slice.call(arguments, 1);
-
- if (this.hasHandler(name)){
- this.getHandler(name).apply(this, args);
- } else {
- this.storage.addCommand(name, args);
- }
-
- },
-
- // Internal method to handle bulk execution of stored commands
- _executeCommands: function(name, handler, context){
- var command = this.storage.getCommands(name);
-
- // loop through and execute all the stored command instances
- _.each(command.instances, function(args){
- handler.apply(context, args);
+ // Instance methods
+ _.extend(CommandStorage.prototype, Backbone.Events, {
+ // Get an object literal by command name, that contains
+ // the `commandName` and the `instances` of all commands
+ // represented as an array of arguments to process
+ getCommands: function(commandName) {
+ var commands = this._commands[commandName];
+ // we don't have it, so add it
+ if (!commands) {
+ // build the configuration
+ commands = {
+ command: commandName,
+ instances: []
+ };
+ // store it
+ this._commands[commandName] = commands;
+ }
+ return commands;
+ },
+ // Add a command by name, to the storage and store the
+ // args for the command
+ addCommand: function(commandName, args) {
+ var command = this.getCommands(commandName);
+ command.instances.push(args);
+ },
+ // Clear all commands for the given `commandName`
+ clearCommands: function(commandName) {
+ var command = this.getCommands(commandName);
+ command.instances = [];
+ }
});
+ return CommandStorage;
+ }();
+ // Wreqr.Commands
+ // --------------
+ //
+ // A simple command pattern implementation. Register a command
+ // handler and execute it.
+ Wreqr.Commands = function(Wreqr) {
+ "use strict";
+ return Wreqr.Handlers.extend({
+ // default storage type
+ storageType: Wreqr.CommandStorage,
+ constructor: function(options) {
+ this.options = options || {};
+ this._initializeStorage(this.options);
+ this.on("handler:add", this._executeCommands, this);
+ var args = Array.prototype.slice.call(arguments);
+ Wreqr.Handlers.prototype.constructor.apply(this, args);
+ },
+ // Execute a named command with the supplied args
+ execute: function(name, args) {
+ name = arguments[0];
+ args = Array.prototype.slice.call(arguments, 1);
+ if (this.hasHandler(name)) {
+ this.getHandler(name).apply(this, args);
+ } else {
+ this.storage.addCommand(name, args);
+ }
+ },
+ // Internal method to handle bulk execution of stored commands
+ _executeCommands: function(name, handler, context) {
+ var command = this.storage.getCommands(name);
+ // loop through and execute all the stored command instances
+ _.each(command.instances, function(args) {
+ handler.apply(context, args);
+ });
+ this.storage.clearCommands(name);
+ },
+ // Internal method to initialize storage either from the type's
+ // `storageType` or the instance `options.storageType`.
+ _initializeStorage: function(options) {
+ var storage;
+ var StorageType = options.storageType || this.storageType;
+ if (_.isFunction(StorageType)) {
+ storage = new StorageType();
+ } else {
+ storage = StorageType;
+ }
+ this.storage = storage;
+ }
+ });
+ }(Wreqr);
+ // Wreqr.RequestResponse
+ // ---------------------
+ //
+ // A simple request/response implementation. Register a
+ // request handler, and return a response from it
+ Wreqr.RequestResponse = function(Wreqr) {
+ "use strict";
+ return Wreqr.Handlers.extend({
+ request: function() {
+ var name = arguments[0];
+ var args = Array.prototype.slice.call(arguments, 1);
+ if (this.hasHandler(name)) {
+ return this.getHandler(name).apply(this, args);
+ }
+ }
+ });
+ }(Wreqr);
+ // Event Aggregator
+ // ----------------
+ // A pub-sub object that can be used to decouple various parts
+ // of an application through event-driven architecture.
+ Wreqr.EventAggregator = function(Backbone, _) {
+ "use strict";
+ var EA = function() {};
+ // Copy the `extend` function used by Backbone's classes
+ EA.extend = Backbone.Model.extend;
+ // Copy the basic Backbone.Events on to the event aggregator
+ _.extend(EA.prototype, Backbone.Events);
+ return EA;
+ }(Backbone, _);
+ // Wreqr.Channel
+ // --------------
+ //
+ // An object that wraps the three messaging systems:
+ // EventAggregator, RequestResponse, Commands
+ Wreqr.Channel = function(Wreqr) {
+ "use strict";
+ var Channel = function(channelName) {
+ this.vent = new Backbone.Wreqr.EventAggregator();
+ this.reqres = new Backbone.Wreqr.RequestResponse();
+ this.commands = new Backbone.Wreqr.Commands();
+ this.channelName = channelName;
+ };
+ _.extend(Channel.prototype, {
+ // Remove all handlers from the messaging systems of this channel
+ reset: function() {
+ this.vent.off();
+ this.vent.stopListening();
+ this.reqres.removeAllHandlers();
+ this.commands.removeAllHandlers();
+ return this;
+ },
+ // Connect a hash of events; one for each messaging system
+ connectEvents: function(hash, context) {
+ this._connect("vent", hash, context);
+ return this;
+ },
+ connectCommands: function(hash, context) {
+ this._connect("commands", hash, context);
+ return this;
+ },
+ connectRequests: function(hash, context) {
+ this._connect("reqres", hash, context);
+ return this;
+ },
+ // Attach the handlers to a given message system `type`
+ _connect: function(type, hash, context) {
+ if (!hash) {
+ return;
+ }
+ context = context || this;
+ var method = type === "vent" ? "on" : "setHandler";
+ _.each(hash, function(fn, eventName) {
+ this[type][method](eventName, _.bind(fn, context));
+ }, this);
+ }
+ });
+ return Channel;
+ }(Wreqr);
+ // Wreqr.Radio
+ // --------------
+ //
+ // An object that lets you communicate with many channels.
+ Wreqr.radio = function(Wreqr) {
+ "use strict";
+ var Radio = function() {
+ this._channels = {};
+ this.vent = {};
+ this.commands = {};
+ this.reqres = {};
+ this._proxyMethods();
+ };
+ _.extend(Radio.prototype, {
+ channel: function(channelName) {
+ if (!channelName) {
+ throw new Error("Channel must receive a name");
+ }
+ return this._getChannel(channelName);
+ },
+ _getChannel: function(channelName) {
+ var channel = this._channels[channelName];
+ if (!channel) {
+ channel = new Wreqr.Channel(channelName);
+ this._channels[channelName] = channel;
+ }
+ return channel;
+ },
+ _proxyMethods: function() {
+ _.each([ "vent", "commands", "reqres" ], function(system) {
+ _.each(messageSystems[system], function(method) {
+ this[system][method] = proxyMethod(this, system, method);
+ }, this);
+ }, this);
+ }
+ });
+ var messageSystems = {
+ vent: [ "on", "off", "trigger", "once", "stopListening", "listenTo", "listenToOnce" ],
+ commands: [ "execute", "setHandler", "setHandlers", "removeHandler", "removeAllHandlers" ],
+ reqres: [ "request", "setHandler", "setHandlers", "removeHandler", "removeAllHandlers" ]
+ };
+ var proxyMethod = function(radio, system, method) {
+ return function(channelName) {
+ var messageSystem = radio._getChannel(channelName)[system];
+ var args = Array.prototype.slice.call(arguments, 1);
+ return messageSystem[method].apply(messageSystem, args);
+ };
+ };
+ return new Radio();
+ }(Wreqr);
+ return Backbone.Wreqr;
+ })(Backbone, _);
- this.storage.clearCommands(name);
- },
-
- // Internal method to initialize storage either from the type's
- // `storageType` or the instance `options.storageType`.
- _initializeStorage: function(options){
- var storage;
-
- var StorageType = options.storageType || this.storageType;
- if (_.isFunction(StorageType)){
- storage = new StorageType();
- } else {
- storage = StorageType;
- }
-
- this.storage = storage;
- }
- });
-
-})(Wreqr);
-
- // Wreqr.RequestResponse
-// ---------------------
-//
-// A simple request/response implementation. Register a
-// request handler, and return a response from it
-Wreqr.RequestResponse = (function(Wreqr){
- "use strict";
-
- return Wreqr.Handlers.extend({
- request: function(){
- var name = arguments[0];
- var args = Array.prototype.slice.call(arguments, 1);
-
- return this.getHandler(name).apply(this, args);
- }
- });
-
-})(Wreqr);
-
- // Event Aggregator
-// ----------------
-// A pub-sub object that can be used to decouple various parts
-// of an application through event-driven architecture.
-
-Wreqr.EventAggregator = (function(Backbone, _){
- "use strict";
- var EA = function(){};
-
- // Copy the `extend` function used by Backbone's classes
- EA.extend = Backbone.Model.extend;
-
- // Copy the basic Backbone.Events on to the event aggregator
- _.extend(EA.prototype, Backbone.Events);
-
- return EA;
-})(Backbone, _);
+ var previousMarionette = root.Marionette;
+ var previousMn = root.Mn;
+ var Marionette = Backbone.Marionette = {};
- return Wreqr;
-})(Backbone, Backbone.Marionette, _);
+ Marionette.VERSION = '2.4.1';
-var Marionette = (function(global, Backbone, _){
- "use strict";
+ Marionette.noConflict = function() {
+ root.Marionette = previousMarionette;
+ root.Mn = previousMn;
+ return this;
+ };
- // Define and export the Marionette namespace
- var Marionette = {};
Backbone.Marionette = Marionette;
- // Get the DOM manipulator for later use
- Marionette.$ = Backbone.$;
-
-// Helpers
-// -------
-
-// For slicing `arguments` in functions
-var slice = Array.prototype.slice;
-
-function throwError(message, name) {
- var error = new Error(message);
- error.name = name || 'Error';
- throw error;
-}
-
-// Marionette.extend
-// -----------------
-
-// Borrow the Backbone `extend` method so we can use it as needed
-Marionette.extend = Backbone.Model.extend;
-
-// Marionette.getOption
-// --------------------
-
-// Retrieve an object, function or other value from a target
-// object or its `options`, with `options` taking precedence.
-Marionette.getOption = function(target, optionName){
- if (!target || !optionName){ return; }
- var value;
-
- if (target.options && (optionName in target.options) && (target.options[optionName] !== undefined)){
- value = target.options[optionName];
- } else {
- value = target[optionName];
- }
-
- return value;
-};
+ // Get the Deferred creator for later use
+ Marionette.Deferred = Backbone.$.Deferred;
-// Marionette.normalizeMethods
-// ----------------------
-
-// Pass in a mapping of events => functions or function names
-// and return a mapping of events => functions
-Marionette.normalizeMethods = function(hash) {
- var normalizedHash = {}, method;
- _.each(hash, function(fn, name) {
- method = fn;
- if (!_.isFunction(method)) {
- method = this[method];
- }
- if (!method) {
- return;
- }
- normalizedHash[name] = method;
- }, this);
- return normalizedHash;
-};
-
-// Trigger an event and/or a corresponding method name. Examples:
-//
-// `this.triggerMethod("foo")` will trigger the "foo" event and
-// call the "onFoo" method.
-//
-// `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and
-// call the "onFooBar" method.
-Marionette.triggerMethod = (function(){
-
- // split the event name on the ":"
- var splitter = /(^|:)(\w)/gi;
-
- // take the event section ("section1:section2:section3")
- // and turn it in to uppercase name
- function getEventName(match, prefix, eventName) {
- return eventName.toUpperCase();
- }
-
- // actual triggerMethod implementation
- var triggerMethod = function(event) {
- // get the method name from the event name
- var methodName = 'on' + event.replace(splitter, getEventName);
- var method = this[methodName];
-
- // trigger the event, if a trigger method exists
- if(_.isFunction(this.trigger)) {
- this.trigger.apply(this, arguments);
- }
-
- // call the onMethodName if it exists
- if (_.isFunction(method)) {
- // pass all arguments, except the event name
- return method.apply(this, _.tail(arguments));
+ /* jshint unused: false *//* global console */
+
+ // Helpers
+ // -------
+
+ // Marionette.extend
+ // -----------------
+
+ // Borrow the Backbone `extend` method so we can use it as needed
+ Marionette.extend = Backbone.Model.extend;
+
+ // Marionette.isNodeAttached
+ // -------------------------
+
+ // Determine if `el` is a child of the document
+ Marionette.isNodeAttached = function(el) {
+ return Backbone.$.contains(document.documentElement, el);
+ };
+
+ // Merge `keys` from `options` onto `this`
+ Marionette.mergeOptions = function(options, keys) {
+ if (!options) { return; }
+ _.extend(this, _.pick(options, keys));
+ };
+
+ // Marionette.getOption
+ // --------------------
+
+ // Retrieve an object, function or other value from a target
+ // object or its `options`, with `options` taking precedence.
+ Marionette.getOption = function(target, optionName) {
+ if (!target || !optionName) { return; }
+ if (target.options && (target.options[optionName] !== undefined)) {
+ return target.options[optionName];
+ } else {
+ return target[optionName];
}
};
-
- return triggerMethod;
-})();
-
-// DOMRefresh
-// ----------
-//
-// Monitor a view's state, and after it has been rendered and shown
-// in the DOM, trigger a "dom:refresh" event every time it is
-// re-rendered.
-
-Marionette.MonitorDOMRefresh = (function(documentElement){
- // track when the view has been shown in the DOM,
- // using a Marionette.Region (or by other means of triggering "show")
- function handleShow(view){
- view._isShown = true;
- triggerDOMRefresh(view);
- }
-
- // track when the view has been rendered
- function handleRender(view){
- view._isRendered = true;
- triggerDOMRefresh(view);
- }
-
- // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method
- function triggerDOMRefresh(view){
- if (view._isShown && view._isRendered && isInDOM(view)){
- if (_.isFunction(view.triggerMethod)){
- view.triggerMethod("dom:refresh");
+
+ // Proxy `Marionette.getOption`
+ Marionette.proxyGetOption = function(optionName) {
+ return Marionette.getOption(this, optionName);
+ };
+
+ // Similar to `_.result`, this is a simple helper
+ // If a function is provided we call it with context
+ // otherwise just return the value. If the value is
+ // undefined return a default value
+ Marionette._getValue = function(value, context, params) {
+ if (_.isFunction(value)) {
+ value = params ? value.apply(context, params) : value.call(context);
+ }
+ return value;
+ };
+
+ // Marionette.normalizeMethods
+ // ----------------------
+
+ // Pass in a mapping of events => functions or function names
+ // and return a mapping of events => functions
+ Marionette.normalizeMethods = function(hash) {
+ return _.reduce(hash, function(normalizedHash, method, name) {
+ if (!_.isFunction(method)) {
+ method = this[method];
}
- }
- }
-
- function isInDOM(view) {
- return documentElement.contains(view.el);
- }
-
- // Export public API
- return function(view){
- view.listenTo(view, "show", function(){
- handleShow(view);
- });
-
- view.listenTo(view, "render", function(){
- handleRender(view);
+ if (method) {
+ normalizedHash[name] = method;
+ }
+ return normalizedHash;
+ }, {}, this);
+ };
+
+ // utility method for parsing @ui. syntax strings
+ // into associated selector
+ Marionette.normalizeUIString = function(uiString, ui) {
+ return uiString.replace(/@ui\.[a-zA-Z_$0-9]*/g, function(r) {
+ return ui[r.slice(4)];
});
};
-})(document.documentElement);
-
-
-// Marionette.bindEntityEvents & unbindEntityEvents
-// ---------------------------
-//
-// These methods are used to bind/unbind a backbone "entity" (collection/model)
-// to methods on a target object.
-//
-// The first parameter, `target`, must have a `listenTo` method from the
-// EventBinder object.
-//
-// The second parameter is the entity (Backbone.Model or Backbone.Collection)
-// to bind the events from.
-//
-// The third parameter is a hash of { "event:name": "eventHandler" }
-// configuration. Multiple handlers can be separated by a space. A
-// function can be supplied instead of a string handler name.
-
-(function(Marionette){
- "use strict";
-
- // Bind the event to handlers specified as a string of
- // handler names on the target object
- function bindFromStrings(target, entity, evt, methods){
- var methodNames = methods.split(/\s+/);
-
- _.each(methodNames,function(methodName) {
-
- var method = target[methodName];
- if(!method) {
- throwError("Method '"+ methodName +"' was configured as an event handler, but does not exist.");
+
+ // allows for the use of the @ui. syntax within
+ // a given key for triggers and events
+ // swaps the @ui with the associated selector.
+ // Returns a new, non-mutated, parsed events hash.
+ Marionette.normalizeUIKeys = function(hash, ui) {
+ return _.reduce(hash, function(memo, val, key) {
+ var normalizedKey = Marionette.normalizeUIString(key, ui);
+ memo[normalizedKey] = val;
+ return memo;
+ }, {});
+ };
+
+ // allows for the use of the @ui. syntax within
+ // a given value for regions
+ // swaps the @ui with the associated selector
+ Marionette.normalizeUIValues = function(hash, ui, properties) {
+ _.each(hash, function(val, key) {
+ if (_.isString(val)) {
+ hash[key] = Marionette.normalizeUIString(val, ui);
+ } else if (_.isObject(val) && _.isArray(properties)) {
+ _.extend(val, Marionette.normalizeUIValues(_.pick(val, properties), ui));
+ /* Value is an object, and we got an array of embedded property names to normalize. */
+ _.each(properties, function(property) {
+ var propertyVal = val[property];
+ if (_.isString(propertyVal)) {
+ val[property] = Marionette.normalizeUIString(propertyVal, ui);
+ }
+ });
}
-
- target.listenTo(entity, evt, method);
});
- }
-
- // Bind the event to a supplied callback function
- function bindToFunction(target, entity, evt, method){
- target.listenTo(entity, evt, method);
- }
-
- // Bind the event to handlers specified as a string of
- // handler names on the target object
- function unbindFromStrings(target, entity, evt, methods){
- var methodNames = methods.split(/\s+/);
-
- _.each(methodNames,function(methodName) {
- var method = target[methodName];
- target.stopListening(entity, evt, method);
- });
- }
-
- // Bind the event to a supplied callback function
- function unbindToFunction(target, entity, evt, method){
- target.stopListening(entity, evt, method);
- }
-
-
- // generic looping function
- function iterateEvents(target, entity, bindings, functionCallback, stringCallback){
- if (!entity || !bindings) { return; }
-
- // allow the bindings to be a function
- if (_.isFunction(bindings)){
- bindings = bindings.call(target);
- }
-
- // iterate the bindings and bind them
- _.each(bindings, function(methods, evt){
-
- // allow for a function as the handler,
- // or a list of event names as a string
- if (_.isFunction(methods)){
- functionCallback(target, entity, evt, methods);
- } else {
- stringCallback(target, entity, evt, methods);
- }
-
+ return hash;
+ };
+
+ // Mix in methods from Underscore, for iteration, and other
+ // collection related features.
+ // Borrowing this code from Backbone.Collection:
+ // http://backbonejs.org/docs/backbone.html#section-121
+ Marionette.actAsCollection = function(object, listProperty) {
+ var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
+ 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
+ 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
+ 'last', 'without', 'isEmpty', 'pluck'];
+
+ _.each(methods, function(method) {
+ object[method] = function() {
+ var list = _.values(_.result(this, listProperty));
+ var args = [list].concat(_.toArray(arguments));
+ return _[method].apply(_, args);
+ };
});
- }
-
- // Export Public API
- Marionette.bindEntityEvents = function(target, entity, bindings){
- iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings);
};
-
- Marionette.unbindEntityEvents = function(target, entity, bindings){
- iterateEvents(target, entity, bindings, unbindToFunction, unbindFromStrings);
+
+ var deprecate = Marionette.deprecate = function(message, test) {
+ if (_.isObject(message)) {
+ message = (
+ message.prev + ' is going to be removed in the future. ' +
+ 'Please use ' + message.next + ' instead.' +
+ (message.url ? ' See: ' + message.url : '')
+ );
+ }
+
+ if ((test === undefined || !test) && !deprecate._cache[message]) {
+ deprecate._warn('Deprecation warning: ' + message);
+ deprecate._cache[message] = true;
+ }
};
-
-})(Marionette);
-
-
-// Callbacks
-// ---------
-
-// A simple way of managing a collection of callbacks
-// and executing them at a later point in time, using jQuery's
-// `Deferred` object.
-Marionette.Callbacks = function(){
- this._deferred = Marionette.$.Deferred();
- this._callbacks = [];
-};
-
-_.extend(Marionette.Callbacks.prototype, {
-
- // Add a callback to be executed. Callbacks added here are
- // guaranteed to execute, even if they are added after the
- // `run` method is called.
- add: function(callback, contextOverride){
- this._callbacks.push({cb: callback, ctx: contextOverride});
-
- this._deferred.done(function(context, options){
- if (contextOverride){ context = contextOverride; }
- callback.call(context, options);
- });
- },
-
- // Run all registered callbacks with the context specified.
- // Additional callbacks can be added after this has been run
- // and they will still be executed.
- run: function(options, context){
- this._deferred.resolve(context, options);
- },
-
- // Resets the list of callbacks to be run, allowing the same list
- // to be run multiple times - whenever the `run` method is called.
- reset: function(){
- var callbacks = this._callbacks;
- this._deferred = Marionette.$.Deferred();
- this._callbacks = [];
-
- _.each(callbacks, function(cb){
- this.add(cb.cb, cb.ctx);
- }, this);
- }
-});
-
-
-// Marionette Controller
-// ---------------------
-//
-// A multi-purpose object to use as a controller for
-// modules and routers, and as a mediator for workflow
-// and coordination of other objects, views, and more.
-Marionette.Controller = function(options){
- this.triggerMethod = Marionette.triggerMethod;
- this.options = options || {};
-
- if (_.isFunction(this.initialize)){
- this.initialize(this.options);
- }
-};
-
-Marionette.Controller.extend = Marionette.extend;
-
-// Controller Methods
-// --------------
-
-// Ensure it can trigger events with Backbone.Events
-_.extend(Marionette.Controller.prototype, Backbone.Events, {
- close: function(){
- this.stopListening();
- this.triggerMethod("close");
- this.unbind();
- }
-});
-
-// Region
-// ------
-//
-// Manage the visual regions of your composite application. See
-// http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
-
-Marionette.Region = function(options){
- this.options = options || {};
- this.el = Marionette.getOption(this, "el");
-
- if (!this.el){
- throwError("An 'el' must be specified for a region.", "NoElError");
- }
-
- if (this.initialize){
- var args = Array.prototype.slice.apply(arguments);
- this.initialize.apply(this, args);
- }
-};
-
-
-// Region Type methods
-// -------------------
-
-_.extend(Marionette.Region, {
-
- // Build an instance of a region by passing in a configuration object
- // and a default region type to use if none is specified in the config.
+
+ deprecate._warn = typeof console !== 'undefined' && (console.warn || console.log) || function() {};
+ deprecate._cache = {};
+
+ /* jshint maxstatements: 14, maxcomplexity: 7 */
+
+ // Trigger Method
+ // --------------
+
+ Marionette._triggerMethod = (function() {
+ // split the event name on the ":"
+ var splitter = /(^|:)(\w)/gi;
+
+ // take the event section ("section1:section2:section3")
+ // and turn it in to uppercase name
+ function getEventName(match, prefix, eventName) {
+ return eventName.toUpperCase();
+ }
+
+ return function(context, event, args) {
+ var noEventArg = arguments.length < 3;
+ if (noEventArg) {
+ args = event;
+ event = args[0];
+ }
+
+ // get the method name from the event name
+ var methodName = 'on' + event.replace(splitter, getEventName);
+ var method = context[methodName];
+ var result;
+
+ // call the onMethodName if it exists
+ if (_.isFunction(method)) {
+ // pass all args, except the event name
+ result = method.apply(context, noEventArg ? _.rest(args) : args);
+ }
+
+ // trigger the event, if a trigger method exists
+ if (_.isFunction(context.trigger)) {
+ if (noEventArg + args.length > 1) {
+ context.trigger.apply(context, noEventArg ? args : [event].concat(_.drop(args, 0)));
+ } else {
+ context.trigger(event);
+ }
+ }
+
+ return result;
+ };
+ })();
+
+ // Trigger an event and/or a corresponding method name. Examples:
//
- // The config object should either be a string as a jQuery DOM selector,
- // a Region type directly, or an object literal that specifies both
- // a selector and regionType:
+ // `this.triggerMethod("foo")` will trigger the "foo" event and
+ // call the "onFoo" method.
//
- // ```js
- // {
- // selector: "#foo",
- // regionType: MyCustomRegion
- // }
- // ```
+ // `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and
+ // call the "onFooBar" method.
+ Marionette.triggerMethod = function(event) {
+ return Marionette._triggerMethod(this, arguments);
+ };
+
+ // triggerMethodOn invokes triggerMethod on a specific context
//
- buildRegion: function(regionConfig, defaultRegionType){
- var regionIsString = _.isString(regionConfig);
- var regionSelectorIsString = _.isString(regionConfig.selector);
- var regionTypeIsUndefined = _.isUndefined(regionConfig.regionType);
- var regionIsType = _.isFunction(regionConfig);
-
- if (!regionIsType && !regionIsString && !regionSelectorIsString) {
- throwError("Region must be specified as a Region type, a selector string or an object with selector property");
+ // e.g. `Marionette.triggerMethodOn(view, 'show')`
+ // will trigger a "show" event or invoke onShow the view.
+ Marionette.triggerMethodOn = function(context) {
+ var fnc = _.isFunction(context.triggerMethod) ?
+ context.triggerMethod :
+ Marionette.triggerMethod;
+
+ return fnc.apply(context, _.rest(arguments));
+ };
+
+ // DOM Refresh
+ // -----------
+
+ // Monitor a view's state, and after it has been rendered and shown
+ // in the DOM, trigger a "dom:refresh" event every time it is
+ // re-rendered.
+
+ Marionette.MonitorDOMRefresh = function(view) {
+
+ // track when the view has been shown in the DOM,
+ // using a Marionette.Region (or by other means of triggering "show")
+ function handleShow() {
+ view._isShown = true;
+ triggerDOMRefresh();
}
-
- var selector, RegionType;
-
- // get the selector for the region
-
- if (regionIsString) {
- selector = regionConfig;
+
+ // track when the view has been rendered
+ function handleRender() {
+ view._isRendered = true;
+ triggerDOMRefresh();
}
-
- if (regionConfig.selector) {
- selector = regionConfig.selector;
- delete regionConfig.selector;
+
+ // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method
+ function triggerDOMRefresh() {
+ if (view._isShown && view._isRendered && Marionette.isNodeAttached(view.el)) {
+ if (_.isFunction(view.triggerMethod)) {
+ view.triggerMethod('dom:refresh');
+ }
+ }
}
-
- // get the type for the region
-
- if (regionIsType){
- RegionType = regionConfig;
+
+ view.on({
+ show: handleShow,
+ render: handleRender
+ });
+ };
+
+ /* jshint maxparams: 5 */
+
+ // Bind Entity Events & Unbind Entity Events
+ // -----------------------------------------
+ //
+ // These methods are used to bind/unbind a backbone "entity" (e.g. collection/model)
+ // to methods on a target object.
+ //
+ // The first parameter, `target`, must have the Backbone.Events module mixed in.
+ //
+ // The second parameter is the `entity` (Backbone.Model, Backbone.Collection or
+ // any object that has Backbone.Events mixed in) to bind the events from.
+ //
+ // The third parameter is a hash of { "event:name": "eventHandler" }
+ // configuration. Multiple handlers can be separated by a space. A
+ // function can be supplied instead of a string handler name.
+
+ (function(Marionette) {
+ 'use strict';
+
+ // Bind the event to handlers specified as a string of
+ // handler names on the target object
+ function bindFromStrings(target, entity, evt, methods) {
+ var methodNames = methods.split(/\s+/);
+
+ _.each(methodNames, function(methodName) {
+
+ var method = target[methodName];
+ if (!method) {
+ throw new Marionette.Error('Method "' + methodName +
+ '" was configured as an event handler, but does not exist.');
+ }
+
+ target.listenTo(entity, evt, method);
+ });
}
-
- if (!regionIsType && regionTypeIsUndefined) {
- RegionType = defaultRegionType;
+
+ // Bind the event to a supplied callback function
+ function bindToFunction(target, entity, evt, method) {
+ target.listenTo(entity, evt, method);
}
-
- if (regionConfig.regionType) {
- RegionType = regionConfig.regionType;
- delete regionConfig.regionType;
+
+ // Bind the event to handlers specified as a string of
+ // handler names on the target object
+ function unbindFromStrings(target, entity, evt, methods) {
+ var methodNames = methods.split(/\s+/);
+
+ _.each(methodNames, function(methodName) {
+ var method = target[methodName];
+ target.stopListening(entity, evt, method);
+ });
}
-
- if (regionIsString || regionIsType) {
- regionConfig = {};
+
+ // Bind the event to a supplied callback function
+ function unbindToFunction(target, entity, evt, method) {
+ target.stopListening(entity, evt, method);
}
-
- regionConfig.el = selector;
-
- // build the region instance
- var region = new RegionType(regionConfig);
-
- // override the `getEl` function if we have a parentEl
- // this must be overridden to ensure the selector is found
- // on the first use of the region. if we try to assign the
- // region's `el` to `parentEl.find(selector)` in the object
- // literal to build the region, the element will not be
- // guaranteed to be in the DOM already, and will cause problems
- if (regionConfig.parentEl){
- region.getEl = function(selector) {
- var parentEl = regionConfig.parentEl;
- if (_.isFunction(parentEl)){
- parentEl = parentEl();
+
+ // generic looping function
+ function iterateEvents(target, entity, bindings, functionCallback, stringCallback) {
+ if (!entity || !bindings) { return; }
+
+ // type-check bindings
+ if (!_.isObject(bindings)) {
+ throw new Marionette.Error({
+ message: 'Bindings must be an object or function.',
+ url: 'marionette.functions.html#marionettebindentityevents'
+ });
+ }
+
+ // allow the bindings to be a function
+ bindings = Marionette._getValue(bindings, target);
+
+ // iterate the bindings and bind them
+ _.each(bindings, function(methods, evt) {
+
+ // allow for a function as the handler,
+ // or a list of event names as a string
+ if (_.isFunction(methods)) {
+ functionCallback(target, entity, evt, methods);
+ } else {
+ stringCallback(target, entity, evt, methods);
}
- return parentEl.find(selector);
- };
+
+ });
}
+
+ // Export Public API
+ Marionette.bindEntityEvents = function(target, entity, bindings) {
+ iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings);
+ };
+
+ Marionette.unbindEntityEvents = function(target, entity, bindings) {
+ iterateEvents(target, entity, bindings, unbindToFunction, unbindFromStrings);
+ };
+
+ // Proxy `bindEntityEvents`
+ Marionette.proxyBindEntityEvents = function(entity, bindings) {
+ return Marionette.bindEntityEvents(this, entity, bindings);
+ };
+
+ // Proxy `unbindEntityEvents`
+ Marionette.proxyUnbindEntityEvents = function(entity, bindings) {
+ return Marionette.unbindEntityEvents(this, entity, bindings);
+ };
+ })(Marionette);
+
- return region;
- }
-
-});
-
-// Region Instance Methods
-// -----------------------
-
-_.extend(Marionette.Region.prototype, Backbone.Events, {
-
- // Displays a backbone view instance inside of the region.
- // Handles calling the `render` method for you. Reads content
- // directly from the `el` attribute. Also calls an optional
- // `onShow` and `close` method on your view, just after showing
- // or just before closing the view, respectively.
- show: function(view){
- this.ensureEl();
-
- var isViewClosed = view.isClosed || _.isUndefined(view.$el);
- var isDifferentView = view !== this.currentView;
-
- if (isDifferentView) {
- this.close();
+ // Error
+ // -----
+
+ var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number'];
+
+ Marionette.Error = Marionette.extend.call(Error, {
+ urlRoot: 'http://marionettejs.com/docs/v' + Marionette.VERSION + '/',
+
+ constructor: function(message, options) {
+ if (_.isObject(message)) {
+ options = message;
+ message = options.message;
+ } else if (!options) {
+ options = {};
+ }
+
+ var error = Error.call(this, message);
+ _.extend(this, _.pick(error, errorProps), _.pick(options, errorProps));
+
+ this.captureStackTrace();
+
+ if (options.url) {
+ this.url = this.urlRoot + options.url;
+ }
+ },
+
+ captureStackTrace: function() {
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, Marionette.Error);
+ }
+ },
+
+ toString: function() {
+ return this.name + ': ' + this.message + (this.url ? ' See: ' + this.url : '');
}
-
- view.render();
-
- if (isDifferentView || isViewClosed) {
- this.open(view);
+ });
+
+ Marionette.Error.extend = Marionette.extend;
+
+ // Callbacks
+ // ---------
+
+ // A simple way of managing a collection of callbacks
+ // and executing them at a later point in time, using jQuery's
+ // `Deferred` object.
+ Marionette.Callbacks = function() {
+ this._deferred = Marionette.Deferred();
+ this._callbacks = [];
+ };
+
+ _.extend(Marionette.Callbacks.prototype, {
+
+ // Add a callback to be executed. Callbacks added here are
+ // guaranteed to execute, even if they are added after the
+ // `run` method is called.
+ add: function(callback, contextOverride) {
+ var promise = _.result(this._deferred, 'promise');
+
+ this._callbacks.push({cb: callback, ctx: contextOverride});
+
+ promise.then(function(args) {
+ if (contextOverride) { args.context = contextOverride; }
+ callback.call(args.context, args.options);
+ });
+ },
+
+ // Run all registered callbacks with the context specified.
+ // Additional callbacks can be added after this has been run
+ // and they will still be executed.
+ run: function(options, context) {
+ this._deferred.resolve({
+ options: options,
+ context: context
+ });
+ },
+
+ // Resets the list of callbacks to be run, allowing the same list
+ // to be run multiple times - whenever the `run` method is called.
+ reset: function() {
+ var callbacks = this._callbacks;
+ this._deferred = Marionette.Deferred();
+ this._callbacks = [];
+
+ _.each(callbacks, function(cb) {
+ this.add(cb.cb, cb.ctx);
+ }, this);
}
-
- this.currentView = view;
-
- Marionette.triggerMethod.call(this, "show", view);
- Marionette.triggerMethod.call(view, "show");
- },
-
- ensureEl: function(){
- if (!this.$el || this.$el.length === 0){
+ });
+
+ // Controller
+ // ----------
+
+ // A multi-purpose object to use as a controller for
+ // modules and routers, and as a mediator for workflow
+ // and coordination of other objects, views, and more.
+ Marionette.Controller = function(options) {
+ this.options = options || {};
+
+ if (_.isFunction(this.initialize)) {
+ this.initialize(this.options);
+ }
+ };
+
+ Marionette.Controller.extend = Marionette.extend;
+
+ // Controller Methods
+ // --------------
+
+ // Ensure it can trigger events with Backbone.Events
+ _.extend(Marionette.Controller.prototype, Backbone.Events, {
+ destroy: function() {
+ Marionette._triggerMethod(this, 'before:destroy', arguments);
+ Marionette._triggerMethod(this, 'destroy', arguments);
+
+ this.stopListening();
+ this.off();
+ return this;
+ },
+
+ // import the `triggerMethod` to trigger events with corresponding
+ // methods if the method exists
+ triggerMethod: Marionette.triggerMethod,
+
+ // A handy way to merge options onto the instance
+ mergeOptions: Marionette.mergeOptions,
+
+ // Proxy `getOption` to enable getting options from this or this.options by name.
+ getOption: Marionette.proxyGetOption
+
+ });
+
+ // Object
+ // ------
+
+ // A Base Class that other Classes should descend from.
+ // Object borrows many conventions and utilities from Backbone.
+ Marionette.Object = function(options) {
+ this.options = _.extend({}, _.result(this, 'options'), options);
+
+ this.initialize.apply(this, arguments);
+ };
+
+ Marionette.Object.extend = Marionette.extend;
+
+ // Object Methods
+ // --------------
+
+ // Ensure it can trigger events with Backbone.Events
+ _.extend(Marionette.Object.prototype, Backbone.Events, {
+
+ //this is a noop method intended to be overridden by classes that extend from this base
+ initialize: function() {},
+
+ destroy: function() {
+ this.triggerMethod('before:destroy');
+ this.triggerMethod('destroy');
+ this.stopListening();
+
+ return this;
+ },
+
+ // Import the `triggerMethod` to trigger events with corresponding
+ // methods if the method exists
+ triggerMethod: Marionette.triggerMethod,
+
+ // A handy way to merge options onto the instance
+ mergeOptions: Marionette.mergeOptions,
+
+ // Proxy `getOption` to enable getting options from this or this.options by name.
+ getOption: Marionette.proxyGetOption,
+
+ // Proxy `bindEntityEvents` to enable binding view's events from another entity.
+ bindEntityEvents: Marionette.proxyBindEntityEvents,
+
+ // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
+ unbindEntityEvents: Marionette.proxyUnbindEntityEvents
+ });
+
+ /* jshint maxcomplexity: 16, maxstatements: 45, maxlen: 120 */
+
+ // Region
+ // ------
+
+ // Manage the visual regions of your composite application. See
+ // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
+
+ Marionette.Region = Marionette.Object.extend({
+ constructor: function(options) {
+
+ // set options temporarily so that we can get `el`.
+ // options will be overriden by Object.constructor
+ this.options = options || {};
+ this.el = this.getOption('el');
+
+ // Handle when this.el is passed in as a $ wrapped element.
+ this.el = this.el instanceof Backbone.$ ? this.el[0] : this.el;
+
+ if (!this.el) {
+ throw new Marionette.Error({
+ name: 'NoElError',
+ message: 'An "el" must be specified for a region.'
+ });
+ }
+
this.$el = this.getEl(this.el);
+ Marionette.Object.call(this, options);
+ },
+
+ // Displays a backbone view instance inside of the region.
+ // Handles calling the `render` method for you. Reads content
+ // directly from the `el` attribute. Also calls an optional
+ // `onShow` and `onDestroy` method on your view, just after showing
+ // or just before destroying the view, respectively.
+ // The `preventDestroy` option can be used to prevent a view from
+ // the old view being destroyed on show.
+ // The `forceShow` option can be used to force a view to be
+ // re-rendered if it's already shown in the region.
+ show: function(view, options) {
+ if (!this._ensureElement()) {
+ return;
+ }
+
+ this._ensureViewIsIntact(view);
+
+ var showOptions = options || {};
+ var isDifferentView = view !== this.currentView;
+ var preventDestroy = !!showOptions.preventDestroy;
+ var forceShow = !!showOptions.forceShow;
+
+ // We are only changing the view if there is a current view to change to begin with
+ var isChangingView = !!this.currentView;
+
+ // Only destroy the current view if we don't want to `preventDestroy` and if
+ // the view given in the first argument is different than `currentView`
+ var _shouldDestroyView = isDifferentView && !preventDestroy;
+
+ // Only show the view given in the first argument if it is different than
+ // the current view or if we want to re-show the view. Note that if
+ // `_shouldDestroyView` is true, then `_shouldShowView` is also necessarily true.
+ var _shouldShowView = isDifferentView || forceShow;
+
+ if (isChangingView) {
+ this.triggerMethod('before:swapOut', this.currentView, this, options);
+ }
+
+ if (this.currentView) {
+ delete this.currentView._parent;
+ }
+
+ if (_shouldDestroyView) {
+ this.empty();
+
+ // A `destroy` event is attached to the clean up manually removed views.
+ // We need to detach this event when a new view is going to be shown as it
+ // is no longer relevant.
+ } else if (isChangingView && _shouldShowView) {
+ this.currentView.off('destroy', this.empty, this);
+ }
+
+ if (_shouldShowView) {
+
+ // We need to listen for if a view is destroyed
+ // in a way other than through the region.
+ // If this happens we need to remove the reference
+ // to the currentView since once a view has been destroyed
+ // we can not reuse it.
+ view.once('destroy', this.empty, this);
+ view.render();
+
+ view._parent = this;
+
+ if (isChangingView) {
+ this.triggerMethod('before:swap', view, this, options);
+ }
+
+ this.triggerMethod('before:show', view, this, options);
+ Marionette.triggerMethodOn(view, 'before:show', view, this, options);
+
+ if (isChangingView) {
+ this.triggerMethod('swapOut', this.currentView, this, options);
+ }
+
+ // 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 = this._displayedViews(view);
+ this._triggerAttach(displayedViews, 'before:');
+ }
+
+ this.attachHtml(view);
+ this.currentView = view;
+
+ if (attachedRegion && triggerAttach) {
+ displayedViews = this._displayedViews(view);
+ this._triggerAttach(displayedViews);
+ }
+
+ if (isChangingView) {
+ this.triggerMethod('swap', view, this, options);
+ }
+
+ this.triggerMethod('show', view, this, options);
+ Marionette.triggerMethodOn(view, 'show', view, this, options);
+
+ return this;
+ }
+
+ return this;
+ },
+
+ triggerBeforeAttach: true,
+ triggerAttach: true,
+
+ _triggerAttach: function(views, prefix) {
+ var eventName = (prefix || '') + 'attach';
+ _.each(views, function(view) {
+ Marionette.triggerMethodOn(view, eventName, view, this);
+ }, this);
+ },
+
+ _displayedViews: function(view) {
+ return _.union([view], _.result(view, '_getNestedViews') || []);
+ },
+
+ _ensureElement: function() {
+ if (!_.isObject(this.el)) {
+ this.$el = this.getEl(this.el);
+ this.el = this.$el[0];
+ }
+
+ if (!this.$el || this.$el.length === 0) {
+ if (this.getOption('allowMissingEl')) {
+ return false;
+ } else {
+ throw new Marionette.Error('An "el" ' + this.$el.selector + ' must exist in DOM');
+ }
+ }
+ return true;
+ },
+
+ _ensureViewIsIntact: function(view) {
+ if (!view) {
+ throw new Marionette.Error({
+ name: 'ViewNotValid',
+ message: 'The view passed is undefined and therefore invalid. You must pass a view instance to show.'
+ });
+ }
+
+ if (view.isDestroyed) {
+ throw new Marionette.Error({
+ name: 'ViewDestroyedError',
+ message: 'View (cid: "' + view.cid + '") has already been destroyed and cannot be used.'
+ });
+ }
+ },
+
+ // Override this method to change how the region finds the DOM
+ // element that it manages. Return a jQuery selector object scoped
+ // to a provided parent el or the document if none exists.
+ getEl: function(el) {
+ return Backbone.$(el, Marionette._getValue(this.options.parentEl, this));
+ },
+
+ // Override this method to change how the new view is
+ // appended to the `$el` that the region is managing
+ attachHtml: function(view) {
+ this.$el.contents().detach();
+
+ this.el.appendChild(view.el);
+ },
+
+ // Destroy the current view, if there is one. If there is no
+ // current view, it does nothing and returns immediately.
+ empty: function(options) {
+ var view = this.currentView;
+
+ var preventDestroy = Marionette._getValue(options, 'preventDestroy', this);
+ // If there is no view in the region
+ // we should not remove anything
+ if (!view) { return; }
+
+ view.off('destroy', this.empty, this);
+ this.triggerMethod('before:empty', view);
+ if (!preventDestroy) {
+ this._destroyView();
+ }
+ this.triggerMethod('empty', view);
+
+ // Remove region pointer to the currentView
+ delete this.currentView;
+
+ if (preventDestroy) {
+ this.$el.contents().detach();
+ }
+
+ return this;
+ },
+
+ // call 'destroy' or 'remove', depending on which is found
+ // on the view (if showing a raw Backbone view or a Marionette View)
+ _destroyView: function() {
+ var view = this.currentView;
+
+ if (view.destroy && !view.isDestroyed) {
+ view.destroy();
+ } else if (view.remove) {
+ view.remove();
+
+ // appending isDestroyed to raw Backbone View allows regions
+ // to throw a ViewDestroyedError for this view
+ view.isDestroyed = true;
+ }
+ },
+
+ // Attach an existing view to the region. This
+ // will not call `render` or `onShow` for the new view,
+ // and will not replace the current HTML for the `el`
+ // of the region.
+ attachView: function(view) {
+ this.currentView = view;
+ return this;
+ },
+
+ // Checks whether a view is currently present within
+ // the region. Returns `true` if there is and `false` if
+ // no view is present.
+ hasView: function() {
+ return !!this.currentView;
+ },
+
+ // Reset the region by destroying any existing view and
+ // clearing out the cached `$el`. The next time a view
+ // is shown via this region, the region will re-query the
+ // DOM for the region's `el`.
+ reset: function() {
+ this.empty();
+
+ if (this.$el) {
+ this.el = this.$el.selector;
+ }
+
+ delete this.$el;
+ return this;
}
+
},
-
- // Override this method to change how the region finds the
- // DOM element that it manages. Return a jQuery selector object.
- getEl: function(selector){
- return Marionette.$(selector);
- },
-
- // Override this method to change how the new view is
- // appended to the `$el` that the region is managing
- open: function(view){
- this.$el.empty().append(view.el);
- },
-
- // Close the current view, if there is one. If there is no
- // current view, it does nothing and returns immediately.
- close: function(){
- var view = this.currentView;
- if (!view || view.isClosed){ return; }
-
- // call 'close' or 'remove', depending on which is found
- if (view.close) { view.close(); }
- else if (view.remove) { view.remove(); }
-
- Marionette.triggerMethod.call(this, "close", view);
-
- delete this.currentView;
- },
-
- // Attach an existing view to the region. This
- // will not call `render` or `onShow` for the new view,
- // and will not replace the current HTML for the `el`
- // of the region.
- attachView: function(view){
- this.currentView = view;
- },
-
- // Reset the region by closing any existing view and
- // clearing out the cached `$el`. The next time a view
- // is shown via this region, the region will re-query the
- // DOM for the region's `el`.
- reset: function(){
- this.close();
- delete this.$el;
- }
-});
-
-// Copy the `extend` function used by Backbone's classes
-Marionette.Region.extend = Marionette.extend;
-
-// Marionette.RegionManager
-// ------------------------
-//
-// Manage one or more related `Marionette.Region` objects.
-Marionette.RegionManager = (function(Marionette){
-
- var RegionManager = Marionette.Controller.extend({
- constructor: function(options){
+
+ // Static Methods
+ {
+
+ // Build an instance of a region by passing in a configuration object
+ // and a default region class to use if none is specified in the config.
+ //
+ // The config object should either be a string as a jQuery DOM selector,
+ // a Region class directly, or an object literal that specifies a selector,
+ // a custom regionClass, and any options to be supplied to the region:
+ //
+ // ```js
+ // {
+ // selector: "#foo",
+ // regionClass: MyCustomRegion,
+ // allowMissingEl: false
+ // }
+ // ```
+ //
+ buildRegion: function(regionConfig, DefaultRegionClass) {
+ if (_.isString(regionConfig)) {
+ return this._buildRegionFromSelector(regionConfig, DefaultRegionClass);
+ }
+
+ if (regionConfig.selector || regionConfig.el || regionConfig.regionClass) {
+ return this._buildRegionFromObject(regionConfig, DefaultRegionClass);
+ }
+
+ if (_.isFunction(regionConfig)) {
+ return this._buildRegionFromRegionClass(regionConfig);
+ }
+
+ throw new Marionette.Error({
+ message: 'Improper region configuration type.',
+ url: 'marionette.region.html#region-configuration-types'
+ });
+ },
+
+ // Build the region from a string selector like '#foo-region'
+ _buildRegionFromSelector: function(selector, DefaultRegionClass) {
+ return new DefaultRegionClass({el: selector});
+ },
+
+ // Build the region from a configuration object
+ // ```js
+ // { selector: '#foo', regionClass: FooRegion, allowMissingEl: false }
+ // ```
+ _buildRegionFromObject: function(regionConfig, DefaultRegionClass) {
+ var RegionClass = regionConfig.regionClass || DefaultRegionClass;
+ var options = _.omit(regionConfig, 'selector', 'regionClass');
+
+ if (regionConfig.selector && !options.el) {
+ options.el = regionConfig.selector;
+ }
+
+ return new RegionClass(options);
+ },
+
+ // Build the region directly from a given `RegionClass`
+ _buildRegionFromRegionClass: function(RegionClass) {
+ return new RegionClass();
+ }
+ });
+
+ // Region Manager
+ // --------------
+
+ // Manage one or more related `Marionette.Region` objects.
+ Marionette.RegionManager = Marionette.Controller.extend({
+ constructor: function(options) {
this._regions = {};
- Marionette.Controller.prototype.constructor.call(this, options);
+ this.length = 0;
+
+ Marionette.Controller.call(this, options);
+
+ this.addRegions(this.getOption('regions'));
},
-
- // Add multiple regions using an object literal, where
+
+ // Add multiple regions using an object literal or a
+ // function that returns an object literal, where
// each key becomes the region name, and each value is
// the region definition.
- addRegions: function(regionDefinitions, defaults){
- var regions = {};
-
- _.each(regionDefinitions, function(definition, name){
- if (_.isString(definition)){
- definition = { selector: definition };
+ addRegions: function(regionDefinitions, defaults) {
+ regionDefinitions = Marionette._getValue(regionDefinitions, this, arguments);
+
+ return _.reduce(regionDefinitions, function(regions, definition, name) {
+ if (_.isString(definition)) {
+ definition = {selector: definition};
}
-
- if (definition.selector){
+ if (definition.selector) {
definition = _.defaults({}, definition, defaults);
}
-
- var region = this.addRegion(name, definition);
- regions[name] = region;
- }, this);
-
- return regions;
+
+ regions[name] = this.addRegion(name, definition);
+ return regions;
+ }, {}, this);
},
-
+
// Add an individual region to the region manager,
// and return the region instance
- addRegion: function(name, definition){
+ addRegion: function(name, definition) {
var region;
-
- var isObject = _.isObject(definition);
- var isString = _.isString(definition);
- var hasSelector = !!definition.selector;
-
- if (isString || (isObject && hasSelector)){
- region = Marionette.Region.buildRegion(definition, Marionette.Region);
- } else if (_.isFunction(definition)){
- region = Marionette.Region.buildRegion(definition, Marionette.Region);
- } else {
+
+ if (definition instanceof Marionette.Region) {
region = definition;
+ } else {
+ region = Marionette.Region.buildRegion(definition, Marionette.Region);
}
-
+
+ this.triggerMethod('before:add:region', name, region);
+
+ region._parent = this;
this._store(name, region);
- this.triggerMethod("region:add", name, region);
+
+ this.triggerMethod('add:region', name, region);
return region;
},
-
+
// Get a region by name
- get: function(name){
+ get: function(name) {
return this._regions[name];
},
-
+
+ // Gets all the regions contained within
+ // the `regionManager` instance.
+ getRegions: function() {
+ return _.clone(this._regions);
+ },
+
// Remove a region by name
- removeRegion: function(name){
+ removeRegion: function(name) {
var region = this._regions[name];
this._remove(name, region);
+
+ return region;
},
-
- // Close all regions in the region manager, and
+
+ // Empty all regions in the region manager, and
// remove them
- removeRegions: function(){
- _.each(this._regions, function(region, name){
+ removeRegions: function() {
+ var regions = this.getRegions();
+ _.each(this._regions, function(region, name) {
this._remove(name, region);
}, this);
+
+ return regions;
},
-
- // Close all regions in the region manager, but
+
+ // Empty all regions in the region manager, but
// leave them attached
- closeRegions: function(){
- _.each(this._regions, function(region, name){
- region.close();
- }, this);
+ emptyRegions: function() {
+ var regions = this.getRegions();
+ _.invoke(regions, 'empty');
+ return regions;
},
-
- // Close all regions and shut down the region
+
+ // Destroy all regions and shut down the region
// manager entirely
- close: function(){
+ destroy: function() {
this.removeRegions();
- Marionette.Controller.prototype.close.apply(this, arguments);
+ return Marionette.Controller.prototype.destroy.apply(this, arguments);
},
-
+
// internal method to store regions
- _store: function(name, region){
+ _store: function(name, region) {
+ if (!this._regions[name]) {
+ this.length++;
+ }
+
this._regions[name] = region;
- this._setLength();
},
-
+
// internal method to remove a region
- _remove: function(name, region){
- region.close();
+ _remove: function(name, region) {
+ this.triggerMethod('before:remove:region', name, region);
+ region.empty();
+ region.stopListening();
+
+ delete region._parent;
delete this._regions[name];
- this._setLength();
- this.triggerMethod("region:remove", name, region);
- },
-
- // set the number of regions current held
- _setLength: function(){
- this.length = _.size(this._regions);
+ this.length--;
+ this.triggerMethod('remove:region', name, region);
}
-
});
+
+ Marionette.actAsCollection(Marionette.RegionManager.prototype, '_regions');
+
- // Borrowing this code from Backbone.Collection:
- // http://backbonejs.org/docs/backbone.html#section-106
- //
- // Mix in methods from Underscore, for iteration, and other
- // collection related features.
- var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
- 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
- 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
- 'last', 'without', 'isEmpty', 'pluck'];
-
- _.each(methods, function(method) {
- RegionManager.prototype[method] = function() {
- var regions = _.values(this._regions);
- var args = [regions].concat(_.toArray(arguments));
- return _[method].apply(_, args);
- };
- });
-
- return RegionManager;
-})(Marionette);
-
-
-// Template Cache
-// --------------
-
-// Manage templates stored in `<script>` blocks,
-// caching them for faster access.
-Marionette.TemplateCache = function(templateId){
- this.templateId = templateId;
-};
-
-// TemplateCache object-level methods. Manage the template
-// caches from these method calls instead of creating
-// your own TemplateCache instances
-_.extend(Marionette.TemplateCache, {
- templateCaches: {},
-
- // Get the specified template by id. Either
- // retrieves the cached version, or loads it
- // from the DOM.
- get: function(templateId){
- var cachedTemplate = this.templateCaches[templateId];
-
- if (!cachedTemplate){
- cachedTemplate = new Marionette.TemplateCache(templateId);
- this.templateCaches[templateId] = cachedTemplate;
- }
-
- return cachedTemplate.load();
- },
-
- // Clear templates from the cache. If no arguments
- // are specified, clears all templates:
- // `clear()`
- //
- // If arguments are specified, clears each of the
- // specified templates from the cache:
- // `clear("#t1", "#t2", "...")`
- clear: function(){
- var i;
- var args = slice.call(arguments);
- var length = args.length;
-
- if (length > 0){
- for(i=0; i<length; i++){
- delete this.templateCaches[args[i]];
+ // Template Cache
+ // --------------
+
+ // Manage templates stored in `<script>` blocks,
+ // caching them for faster access.
+ Marionette.TemplateCache = function(templateId) {
+ this.templateId = templateId;
+ };
+
+ // TemplateCache object-level methods. Manage the template
+ // caches from these method calls instead of creating
+ // your own TemplateCache instances
+ _.extend(Marionette.TemplateCache, {
+ templateCaches: {},
+
+ // Get the specified template by id. Either
+ // retrieves the cached version, or loads it
+ // from the DOM.
+ get: function(templateId, options) {
+ var cachedTemplate = this.templateCaches[templateId];
+
+ if (!cachedTemplate) {
+ cachedTemplate = new Marionette.TemplateCache(templateId);
+ this.templateCaches[templateId] = cachedTemplate;
+ }
+
+ return cachedTemplate.load(options);
+ },
+
+ // Clear templates from the cache. If no arguments
+ // are specified, clears all templates:
+ // `clear()`
+ //
+ // If arguments are specified, clears each of the
+ // specified templates from the cache:
+ // `clear("#t1", "#t2", "...")`
+ clear: function() {
+ var i;
+ var args = _.toArray(arguments);
+ var length = args.length;
+
+ if (length > 0) {
+ for (i = 0; i < length; i++) {
+ delete this.templateCaches[args[i]];
+ }
+ } else {
+ this.templateCaches = {};
}
- } else {
- this.templateCaches = {};
}
- }
-});
-
-// TemplateCache instance methods, allowing each
-// template cache object to manage its own state
-// and know whether or not it has been loaded
-_.extend(Marionette.TemplateCache.prototype, {
-
- // Internal method to load the template
- load: function(){
- // Guard clause to prevent loading this template more than once
- if (this.compiledTemplate){
+ });
+
+ // TemplateCache instance methods, allowing each
+ // template cache object to manage its own state
+ // and know whether or not it has been loaded
+ _.extend(Marionette.TemplateCache.prototype, {
+
+ // Internal method to load the template
+ load: function(options) {
+ // Guard clause to prevent loading this template more than once
+ if (this.compiledTemplate) {
+ return this.compiledTemplate;
+ }
+
+ // Load the template and compile it
+ var template = this.loadTemplate(this.templateId, options);
+ this.compiledTemplate = this.compileTemplate(template, options);
+
return this.compiledTemplate;
+ },
+
+ // Load a template from the DOM, by default. Override
+ // this method to provide your own template retrieval
+ // For asynchronous loading with AMD/RequireJS, consider
+ // using a template-loader plugin as described here:
+ // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
+ loadTemplate: function(templateId, options) {
+ var template = Backbone.$(templateId).html();
+
+ if (!template || template.length === 0) {
+ throw new Marionette.Error({
+ name: 'NoTemplateError',
+ message: 'Could not find template: "' + templateId + '"'
+ });
+ }
+
+ return template;
+ },
+
+ // Pre-compile the template before caching it. Override
+ // this method if you do not need to pre-compile a template
+ // (JST / RequireJS for example) or if you want to change
+ // the template engine used (Handebars, etc).
+ compileTemplate: function(rawTemplate, options) {
+ return _.template(rawTemplate, options);
}
-
- // Load the template and compile it
- var template = this.loadTemplate(this.templateId);
- this.compiledTemplate = this.compileTemplate(template);
-
- return this.compiledTemplate;
- },
-
- // Load a template from the DOM, by default. Override
- // this method to provide your own template retrieval
- // For asynchronous loading with AMD/RequireJS, consider
- // using a template-loader plugin as described here:
- // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
- loadTemplate: function(templateId){
- var template = Marionette.$(templateId).html();
-
- if (!template || template.length === 0){
- throwError("Could not find template: '" + templateId + "'", "NoTemplateError");
- }
-
- return template;
- },
-
- // Pre-compile the template before caching it. Override
- // this method if you do not need to pre-compile a template
- // (JST / RequireJS for example) or if you want to change
- // the template engine used (Handebars, etc).
- compileTemplate: function(rawTemplate){
- return _.template(rawTemplate);
- }
-});
-
-
-// Renderer
-// --------
-
-// Render a template with data by passing in the template
-// selector and the data to render.
-Marionette.Renderer = {
-
- // Render a template with data. The `template` parameter is
- // passed to the `TemplateCache` object to retrieve the
- // template function. Override this method to provide your own
- // custom rendering and template handling for all of Marionette.
- render: function(template, data){
-
- if (!template) {
- throwError("Cannot render the template since it's false, null or undefined.", "TemplateNotFoundError");
- }
-
- var templateFunc;
- if (typeof template === "function"){
- templateFunc = template;
- } else {
- templateFunc = Marionette.TemplateCache.get(template);
- }
-
- return templateFunc(data);
- }
-};
-
-
-
-// Marionette.View
-// ---------------
-
-// The core view type that other Marionette views extend from.
-Marionette.View = Backbone.View.extend({
-
- constructor: function(options){
- _.bindAll(this, "render");
-
- // this exposes view options to the view initializer
- // this is a backfill since backbone removed the assignment
- // of this.options
- // at some point however this may be removed
- this.options = _.extend({}, _.result(this, 'options'), _.isFunction(options) ? options.call(this) : options);
-
- // parses out the @ui DSL for events
- this.events = this.normalizeUIKeys(_.result(this, 'events'));
- Backbone.View.prototype.constructor.apply(this, arguments);
-
- Marionette.MonitorDOMRefresh(this);
- this.listenTo(this, "show", this.onShowCalled);
- },
-
- // import the "triggerMethod" to trigger events with corresponding
- // methods if the method exists
- triggerMethod: Marionette.triggerMethod,
-
- // Imports the "normalizeMethods" to transform hashes of
- // events=>function references/names to a hash of events=>function references
- normalizeMethods: Marionette.normalizeMethods,
-
- // Get the template for this view
- // instance. You can set a `template` attribute in the view
- // definition or pass a `template: "whatever"` parameter in
- // to the constructor options.
- getTemplate: function(){
- return Marionette.getOption(this, "template");
- },
-
- // Mix in template helper methods. Looks for a
- // `templateHelpers` attribute, which can either be an
- // object literal, or a function that returns an object
- // literal. All methods and attributes from this object
- // are copies to the object passed in.
- mixinTemplateHelpers: function(target){
- target = target || {};
- var templateHelpers = Marionette.getOption(this, "templateHelpers");
- if (_.isFunction(templateHelpers)){
- templateHelpers = templateHelpers.call(this);
- }
- return _.extend(target, templateHelpers);
- },
-
- // allows for the use of the @ui. syntax within
- // a given key for triggers and events
- // swaps the @ui with the associated selector
- normalizeUIKeys: function(hash) {
- var _this = this;
- if (typeof(hash) === "undefined") {
- return;
+ });
+
+ // Renderer
+ // --------
+
+ // Render a template with data by passing in the template
+ // selector and the data to render.
+ Marionette.Renderer = {
+
+ // Render a template with data. The `template` parameter is
+ // passed to the `TemplateCache` object to retrieve the
+ // template function. Override this method to provide your own
+ // custom rendering and template handling for all of Marionette.
+ render: function(template, data) {
+ if (!template) {
+ throw new Marionette.Error({
+ name: 'TemplateNotFoundError',
+ message: 'Cannot render the template since its false, null or undefined.'
+ });
+ }
+
+ var templateFunc = _.isFunction(template) ? template : Marionette.TemplateCache.get(template);
+
+ return templateFunc(data);
}
+ };
+
- _.each(_.keys(hash), function(v) {
- var pattern = /@ui.[a-zA-Z_$0-9]*/g;
- if (v.match(pattern)) {
- hash[v.replace(pattern, function(r) {
- return _.result(_this, "ui")[r.slice(4)];
- })] = hash[v];
- delete hash[v];
+ /* jshint maxlen: 114, nonew: false */
+ // View
+ // ----
+
+ // The core view class that other Marionette views extend from.
+ Marionette.View = Backbone.View.extend({
+ isDestroyed: false,
+
+ constructor: function(options) {
+ _.bindAll(this, 'render');
+
+ options = Marionette._getValue(options, this);
+
+ // this exposes view options to the view initializer
+ // this is a backfill since backbone removed the assignment
+ // of this.options
+ // at some point however this may be removed
+ this.options = _.extend({}, _.result(this, 'options'), options);
+
+ this._behaviors = Marionette.Behaviors(this);
+
+ Backbone.View.call(this, this.options);
+
+ Marionette.MonitorDOMRefresh(this);
+ },
+
+ // Get the template for this view
+ // instance. You can set a `template` attribute in the view
+ // definition or pass a `template: "whatever"` parameter in
+ // to the constructor options.
+ getTemplate: function() {
+ return this.getOption('template');
+ },
+
+ // Serialize a model by returning its attributes. Clones
+ // the attributes to allow modification.
+ serializeModel: function(model) {
+ return model.toJSON.apply(model, _.rest(arguments));
+ },
+
+ // Mix in template helper methods. Looks for a
+ // `templateHelpers` attribute, which can either be an
+ // object literal, or a function that returns an object
+ // literal. All methods and attributes from this object
+ // are copies to the object passed in.
+ mixinTemplateHelpers: function(target) {
+ target = target || {};
+ var templateHelpers = this.getOption('templateHelpers');
+ templateHelpers = Marionette._getValue(templateHelpers, this);
+ return _.extend(target, templateHelpers);
+ },
+
+ // normalize the keys of passed hash with the views `ui` selectors.
+ // `{"@ui.foo": "bar"}`
+ normalizeUIKeys: function(hash) {
+ var uiBindings = _.result(this, '_uiBindings');
+ return Marionette.normalizeUIKeys(hash, uiBindings || _.result(this, 'ui'));
+ },
+
+ // normalize the values of passed hash with the views `ui` selectors.
+ // `{foo: "@ui.bar"}`
+ normalizeUIValues: function(hash, properties) {
+ var ui = _.result(this, 'ui');
+ var uiBindings = _.result(this, '_uiBindings');
+ return Marionette.normalizeUIValues(hash, uiBindings || ui, properties);
+ },
+
+ // Configure `triggers` to forward DOM events to view
+ // events. `triggers: {"click .foo": "do:foo"}`
+ configureTriggers: function() {
+ if (!this.triggers) { return; }
+
+ // Allow `triggers` to be configured as a function
+ var triggers = this.normalizeUIKeys(_.result(this, 'triggers'));
+
+ // Configure the triggers, prevent default
+ // action and stop propagation of DOM events
+ return _.reduce(triggers, function(events, value, key) {
+ events[key] = this._buildViewTrigger(value);
+ return events;
+ }, {}, this);
+ },
+
+ // Overriding Backbone.View's delegateEvents to handle
+ // the `triggers`, `modelEvents`, and `collectionEvents` configuration
+ delegateEvents: function(events) {
+ this._delegateDOMEvents(events);
+ this.bindEntityEvents(this.model, this.getOption('modelEvents'));
+ this.bindEntityEvents(this.collection, this.getOption('collectionEvents'));
+
+ _.each(this._behaviors, function(behavior) {
+ behavior.bindEntityEvents(this.model, behavior.getOption('modelEvents'));
+ behavior.bindEntityEvents(this.collection, behavior.getOption('collectionEvents'));
+ }, this);
+
+ return this;
+ },
+
+ // internal method to delegate DOM events and triggers
+ _delegateDOMEvents: function(eventsArg) {
+ var events = Marionette._getValue(eventsArg || this.events, this);
+
+ // normalize ui keys
+ events = this.normalizeUIKeys(events);
+ if (_.isUndefined(eventsArg)) {this.events = events;}
+
+ var combinedEvents = {};
+
+ // look up if this view has behavior events
+ var behaviorEvents = _.result(this, 'behaviorEvents') || {};
+ var triggers = this.configureTriggers();
+ var behaviorTriggers = _.result(this, 'behaviorTriggers') || {};
+
+ // behavior events will be overriden by view events and or triggers
+ _.extend(combinedEvents, behaviorEvents, events, triggers, behaviorTriggers);
+
+ Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
+ },
+
+ // Overriding Backbone.View's undelegateEvents to handle unbinding
+ // the `triggers`, `modelEvents`, and `collectionEvents` config
+ undelegateEvents: function() {
+ Backbone.View.prototype.undelegateEvents.apply(this, arguments);
+
+ this.unbindEntityEvents(this.model, this.getOption('modelEvents'));
+ this.unbindEntityEvents(this.collection, this.getOption('collectionEvents'));
+
+ _.each(this._behaviors, function(behavior) {
+ behavior.unbindEntityEvents(this.model, behavior.getOption('modelEvents'));
+ behavior.unbindEntityEvents(this.collection, behavior.getOption('collectionEvents'));
+ }, this);
+
+ return this;
+ },
+
+ // Internal helper method to verify whether the view hasn't been destroyed
+ _ensureViewIsIntact: function() {
+ if (this.isDestroyed) {
+ throw new Marionette.Error({
+ name: 'ViewDestroyedError',
+ message: 'View (cid: "' + this.cid + '") has already been destroyed and cannot be used.'
+ });
}
- });
-
- return hash;
- },
-
- // Configure `triggers` to forward DOM events to view
- // events. `triggers: {"click .foo": "do:foo"}`
- configureTriggers: function(){
- if (!this.triggers) { return; }
-
- var triggerEvents = {};
-
- // Allow `triggers` to be configured as a function
- var triggers = this.normalizeUIKeys(_.result(this, "triggers"));
-
- // Configure the triggers, prevent default
- // action and stop propagation of DOM events
- _.each(triggers, function(value, key){
-
- var hasOptions = _.isObject(value);
- var eventName = hasOptions ? value.event : value;
-
- // build the event handler function for the DOM event
- triggerEvents[key] = function(e){
-
- // stop the event in its tracks
+ },
+
+ // Default `destroy` implementation, for removing a view from the
+ // DOM and unbinding it. Regions will call this method
+ // for you. You can specify an `onDestroy` method in your view to
+ // add custom code that is called after the view is destroyed.
+ destroy: function() {
+ if (this.isDestroyed) { return this; }
+
+ var args = _.toArray(arguments);
+
+ this.triggerMethod.apply(this, ['before:destroy'].concat(args));
+
+ // mark as destroyed before doing the actual destroy, to
+ // prevent infinite loops within "destroy" event handlers
+ // that are trying to destroy other views
+ this.isDestroyed = true;
+ this.triggerMethod.apply(this, ['destroy'].concat(args));
+
+ // unbind UI elements
+ this.unbindUIElements();
+
+ this.isRendered = false;
+
+ // remove the view from the DOM
+ this.remove();
+
+ // Call destroy on each behavior after
+ // destroying the view.
+ // This unbinds event listeners
+ // that behaviors have registered for.
+ _.invoke(this._behaviors, 'destroy', args);
+
+ return this;
+ },
+
+ bindUIElements: function() {
+ this._bindUIElements();
+ _.invoke(this._behaviors, this._bindUIElements);
+ },
+
+ // This method binds the elements specified in the "ui" hash inside the view's code with
+ // the associated jQuery selectors.
+ _bindUIElements: function() {
+ if (!this.ui) { return; }
+
+ // store the ui hash in _uiBindings so they can be reset later
+ // and so re-rendering the view will be able to find the bindings
+ if (!this._uiBindings) {
+ this._uiBindings = this.ui;
+ }
+
+ // get the bindings result, as a function or otherwise
+ var bindings = _.result(this, '_uiBindings');
+
+ // empty the ui so we don't have anything to start with
+ this.ui = {};
+
+ // bind each of the selectors
+ _.each(bindings, function(selector, key) {
+ this.ui[key] = this.$(selector);
+ }, this);
+ },
+
+ // This method unbinds the elements specified in the "ui" hash
+ unbindUIElements: function() {
+ this._unbindUIElements();
+ _.invoke(this._behaviors, this._unbindUIElements);
+ },
+
+ _unbindUIElements: function() {
+ if (!this.ui || !this._uiBindings) { return; }
+
+ // delete all of the existing ui bindings
+ _.each(this.ui, function($el, name) {
+ delete this.ui[name];
+ }, this);
+
+ // reset the ui element to the original bindings configuration
+ this.ui = this._uiBindings;
+ delete this._uiBindings;
+ },
+
+ // Internal method to create an event handler for a given `triggerDef` like
+ // 'click:foo'
+ _buildViewTrigger: function(triggerDef) {
+ var hasOptions = _.isObject(triggerDef);
+
+ var options = _.defaults({}, (hasOptions ? triggerDef : {}), {
+ preventDefault: true,
+ stopPropagation: true
+ });
+
+ var eventName = hasOptions ? options.event : triggerDef;
+
+ return function(e) {
if (e) {
- var prevent = e.preventDefault;
- var stop = e.stopPropagation;
-
- var shouldPrevent = hasOptions ? value.preventDefault : prevent;
- var shouldStop = hasOptions ? value.stopPropagation : stop;
-
- if (shouldPrevent && prevent) { prevent.apply(e); }
- if (shouldStop && stop) { stop.apply(e); }
+ if (e.preventDefault && options.preventDefault) {
+ e.preventDefault();
+ }
+
+ if (e.stopPropagation && options.stopPropagation) {
+ e.stopPropagation();
+ }
}
-
- // build the args for the event
+
var args = {
view: this,
model: this.model,
collection: this.collection
};
-
- // trigger the event
+
this.triggerMethod(eventName, args);
};
-
- }, this);
-
- return triggerEvents;
- },
-
- // Overriding Backbone.View's delegateEvents to handle
- // the `triggers`, `modelEvents`, and `collectionEvents` configuration
- delegateEvents: function(events){
- this._delegateDOMEvents(events);
- Marionette.bindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
- Marionette.bindEntityEvents(this, this.collection, Marionette.getOption(this, "collectionEvents"));
- },
-
- // internal method to delegate DOM events and triggers
- _delegateDOMEvents: function(events){
- events = events || this.events;
- if (_.isFunction(events)){ events = events.call(this); }
-
- var combinedEvents = {};
- var triggers = this.configureTriggers();
- _.extend(combinedEvents, events, triggers);
-
- Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
- },
-
- // Overriding Backbone.View's undelegateEvents to handle unbinding
- // the `triggers`, `modelEvents`, and `collectionEvents` config
- undelegateEvents: function(){
- var args = Array.prototype.slice.call(arguments);
- Backbone.View.prototype.undelegateEvents.apply(this, args);
-
- Marionette.unbindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
- Marionette.unbindEntityEvents(this, this.collection, Marionette.getOption(this, "collectionEvents"));
- },
-
- // Internal method, handles the `show` event.
- onShowCalled: function(){},
-
- // Default `close` implementation, for removing a view from the
- // DOM and unbinding it. Regions will call this method
- // for you. You can specify an `onClose` method in your view to
- // add custom code that is called after the view is closed.
- close: function(){
- if (this.isClosed) { return; }
-
- // allow the close to be stopped by returning `false`
- // from the `onBeforeClose` method
- var shouldClose = this.triggerMethod("before:close");
- if (shouldClose === false){
- return;
- }
-
- // mark as closed before doing the actual close, to
- // prevent infinite loops within "close" event handlers
- // that are trying to close other views
- this.isClosed = true;
- this.triggerMethod("close");
-
- // unbind UI elements
- this.unbindUIElements();
-
- // remove the view from the DOM
- this.remove();
- },
-
- // This method binds the elements specified in the "ui" hash inside the view's code with
- // the associated jQuery selectors.
- bindUIElements: function(){
- if (!this.ui) { return; }
-
- // store the ui hash in _uiBindings so they can be reset later
- // and so re-rendering the view will be able to find the bindings
- if (!this._uiBindings){
- this._uiBindings = this.ui;
- }
-
- // get the bindings result, as a function or otherwise
- var bindings = _.result(this, "_uiBindings");
-
- // empty the ui so we don't have anything to start with
- this.ui = {};
-
- // bind each of the selectors
- _.each(_.keys(bindings), function(key) {
- var selector = bindings[key];
- this.ui[key] = this.$(selector);
- }, this);
- },
-
- // This method unbinds the elements specified in the "ui" hash
- unbindUIElements: function(){
- if (!this.ui || !this._uiBindings){ return; }
-
- // delete all of the existing ui bindings
- _.each(this.ui, function($el, name){
- delete this.ui[name];
- }, this);
-
- // reset the ui element to the original bindings configuration
- this.ui = this._uiBindings;
- delete this._uiBindings;
- }
-});
-
-// Item View
-// ---------
-
-// A single item view implementation that contains code for rendering
-// with underscore.js templates, serializing the view's model or collection,
-// and calling several methods on extended views, such as `onRender`.
-Marionette.ItemView = Marionette.View.extend({
-
- // Setting up the inheritance chain which allows changes to
- // Marionette.View.prototype.constructor which allows overriding
- constructor: function(){
- Marionette.View.prototype.constructor.apply(this, arguments);
- },
-
- // Serialize the model or collection for the view. If a model is
- // found, `.toJSON()` is called. If a collection is found, `.toJSON()`
- // is also called, but is used to populate an `items` array in the
- // resulting data. If both are found, defaults to the model.
- // You can override the `serializeData` method in your own view
- // definition, to provide custom serialization for your view's data.
- serializeData: function(){
- var data = {};
-
- if (this.model) {
- data = this.model.toJSON();
- }
- else if (this.collection) {
- data = { items: this.collection.toJSON() };
- }
-
- return data;
- },
-
- // 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.
- render: function(){
- this.isClosed = false;
-
- this.triggerMethod("before:render", this);
- this.triggerMethod("item:before:render", this);
-
- var data = this.serializeData();
- data = this.mixinTemplateHelpers(data);
-
- var template = this.getTemplate();
- var html = Marionette.Renderer.render(template, data);
-
- this.$el.html(html);
- this.bindUIElements();
-
- this.triggerMethod("render", this);
- this.triggerMethod("item:rendered", this);
-
- return this;
- },
-
- // Override the default close event to add a few
- // more events that are triggered.
- close: function(){
- if (this.isClosed){ return; }
-
- this.triggerMethod('item:before:close');
-
- Marionette.View.prototype.close.apply(this, arguments);
-
- this.triggerMethod('item:closed');
- }
-});
-
-// Collection View
-// ---------------
-
-// A view that iterates over a Backbone.Collection
-// and renders an individual ItemView for each model.
-Marionette.CollectionView = Marionette.View.extend({
- // used as the prefix for item view events
- // that are forwarded through the collectionview
- itemViewEventPrefix: "itemview",
-
- // constructor
- constructor: function(options){
- this._initChildViewStorage();
-
- Marionette.View.prototype.constructor.apply(this, arguments);
-
- this._initialEvents();
- this.initRenderBuffer();
- },
-
- // Instead of inserting elements one by one into the page,
- // it's much more performant to insert elements into a document
- // fragment and then insert that document fragment into the page
- initRenderBuffer: function() {
- this.elBuffer = document.createDocumentFragment();
- this._bufferedChildren = [];
- },
-
- startBuffering: function() {
- this.initRenderBuffer();
- this.isBuffering = true;
- },
-
- endBuffering: function() {
- this.isBuffering = false;
- this.appendBuffer(this, this.elBuffer);
- this._triggerShowBufferedChildren();
- this.initRenderBuffer();
- },
-
- _triggerShowBufferedChildren: function () {
- if (this._isShown) {
- _.each(this._bufferedChildren, function (child) {
- Marionette.triggerMethod.call(child, "show");
+ },
+
+ setElement: function() {
+ var ret = Backbone.View.prototype.setElement.apply(this, arguments);
+
+ // proxy behavior $el to the view's $el.
+ // This is needed because a view's $el proxy
+ // is not set until after setElement is called.
+ _.invoke(this._behaviors, 'proxyViewProperties', this);
+
+ return ret;
+ },
+
+ // import the `triggerMethod` to trigger events with corresponding
+ // methods if the method exists
+ triggerMethod: function() {
+ var ret = Marionette._triggerMethod(this, arguments);
+
+ this._triggerEventOnBehaviors(arguments);
+ this._triggerEventOnParentLayout(arguments[0], _.rest(arguments));
+
+ return ret;
+ },
+
+ _triggerEventOnBehaviors: function(args) {
+ var triggerMethod = Marionette._triggerMethod;
+ var behaviors = this._behaviors;
+ // Use good ol' for as this is a very hot function
+ for (var i = 0, length = behaviors && behaviors.length; i < length; i++) {
+ triggerMethod(behaviors[i], args);
+ }
+ },
+
+ _triggerEventOnParentLayout: function(eventName, args) {
+ var layoutView = this._parentLayoutView();
+ if (!layoutView) {
+ return;
+ }
+
+ // invoke triggerMethod on parent view
+ var eventPrefix = Marionette.getOption(layoutView, 'childViewEventPrefix');
+ var prefixedEventName = eventPrefix + ':' + eventName;
+
+ Marionette._triggerMethod(layoutView, [prefixedEventName, this].concat(args));
+
+ // call the parent view's childEvents handler
+ var childEvents = Marionette.getOption(layoutView, 'childEvents');
+ var normalizedChildEvents = layoutView.normalizeMethods(childEvents);
+
+ if (!!normalizedChildEvents && _.isFunction(normalizedChildEvents[eventName])) {
+ normalizedChildEvents[eventName].apply(layoutView, [this].concat(args));
+ }
+ },
+
+ // This method returns any views that are immediate
+ // children of this view
+ _getImmediateChildren: function() {
+ return [];
+ },
+
+ // Returns an array of every nested view within this view
+ _getNestedViews: function() {
+ var children = this._getImmediateChildren();
+
+ if (!children.length) { return children; }
+
+ return _.reduce(children, function(memo, view) {
+ if (!view._getNestedViews) { return memo; }
+ return memo.concat(view._getNestedViews());
+ }, children);
+ },
+
+ // Internal utility for building an ancestor
+ // view tree list.
+ _getAncestors: function() {
+ var ancestors = [];
+ var parent = this._parent;
+
+ while (parent) {
+ ancestors.push(parent);
+ parent = parent._parent;
+ }
+
+ return ancestors;
+ },
+
+ // Returns the containing parent view.
+ _parentLayoutView: function() {
+ var ancestors = this._getAncestors();
+ return _.find(ancestors, function(parent) {
+ return parent instanceof Marionette.LayoutView;
});
- this._bufferedChildren = [];
- }
- },
-
- // Configured the initial events that the collection view
- // binds to.
- _initialEvents: function(){
- if (this.collection){
- this.listenTo(this.collection, "add", this.addChildView);
- this.listenTo(this.collection, "remove", this.removeItemView);
- this.listenTo(this.collection, "reset", this.render);
- }
- },
-
- // Handle a child item added to the collection
- addChildView: function(item, collection, options){
- this.closeEmptyView();
- var ItemView = this.getItemView(item);
- var index = this.collection.indexOf(item);
- this.addItemView(item, ItemView, index);
- },
-
- // Override from `Marionette.View` to guarantee the `onShow` method
- // of child views is called.
- onShowCalled: function(){
- this.children.each(function(child){
- Marionette.triggerMethod.call(child, "show");
- });
- },
-
- // Internal method to trigger the before render callbacks
- // and events
- triggerBeforeRender: function(){
- this.triggerMethod("before:render", this);
- this.triggerMethod("collection:before:render", this);
- },
-
- // Internal method to trigger the rendered callbacks and
- // events
- triggerRendered: function(){
- this.triggerMethod("render", this);
- this.triggerMethod("collection:rendered", this);
- },
-
- // Render the collection of items. Override this method to
- // provide your own implementation of a render function for
- // the collection view.
- render: function(){
- this.isClosed = false;
- this.triggerBeforeRender();
- this._renderChildren();
- this.triggerRendered();
- return this;
- },
-
- // Internal method. Separated so that CompositeView can have
- // more control over events being triggered, around the rendering
- // process
- _renderChildren: function(){
- this.startBuffering();
-
- this.closeEmptyView();
- this.closeChildren();
-
- if (!this.isEmpty(this.collection)) {
- this.showCollection();
- } else {
- this.showEmptyView();
- }
-
- this.endBuffering();
- },
-
- // Internal method to loop through each item in the
- // collection view and show it
- showCollection: function(){
- var ItemView;
- this.collection.each(function(item, index){
- ItemView = this.getItemView(item);
- this.addItemView(item, ItemView, index);
- }, this);
- },
-
- // Internal method to show an empty view in place of
- // a collection of item views, when the collection is
- // empty
- showEmptyView: function(){
- var EmptyView = this.getEmptyView();
-
- if (EmptyView && !this._showingEmptyView){
- this._showingEmptyView = true;
- var model = new Backbone.Model();
- this.addItemView(model, EmptyView, 0);
- }
- },
-
- // Internal method to close an existing emptyView instance
- // if one exists. Called when a collection view has been
- // rendered empty, and then an item is added to the collection.
- closeEmptyView: function(){
- if (this._showingEmptyView){
- this.closeChildren();
- delete this._showingEmptyView;
- }
- },
-
- // Retrieve the empty view type
- getEmptyView: function(){
- return Marionette.getOption(this, "emptyView");
- },
-
- // Retrieve the itemView type, either from `this.options.itemView`
- // or from the `itemView` in the object definition. The "options"
- // takes precedence.
- getItemView: function(item){
- var itemView = Marionette.getOption(this, "itemView");
-
- if (!itemView){
- throwError("An `itemView` must be specified", "NoItemViewError");
- }
-
- return itemView;
- },
-
- // Render the child item's view and add it to the
- // HTML for the collection view.
- addItemView: function(item, ItemView, index){
- // get the itemViewOptions if any were specified
- var itemViewOptions = Marionette.getOption(this, "itemViewOptions");
- if (_.isFunction(itemViewOptions)){
- itemViewOptions = itemViewOptions.call(this, item, index);
- }
-
- // build the view
- var view = this.buildItemView(item, ItemView, itemViewOptions);
-
- // set up the child view event forwarding
- this.addChildViewEventForwarding(view);
-
- // this view is about to be added
- this.triggerMethod("before:item:added", view);
-
- // Store the child view itself so we can properly
- // remove and/or close it later
- this.children.add(view);
-
- // Render it and show it
- this.renderItemView(view, index);
-
- // call the "show" method if the collection view
- // has already been shown
- if (this._isShown && !this.isBuffering){
- Marionette.triggerMethod.call(view, "show");
- }
-
- // this view was added
- this.triggerMethod("after:item:added", view);
-
- return view;
- },
-
- // Set up the child view event forwarding. Uses an "itemview:"
- // prefix in front of all forwarded events.
- addChildViewEventForwarding: function(view){
- var prefix = Marionette.getOption(this, "itemViewEventPrefix");
-
- // Forward all child item view events through the parent,
- // prepending "itemview:" to the event name
- this.listenTo(view, "all", function(){
- var args = slice.call(arguments);
- var rootEvent = args[0];
- var itemEvents = this.normalizeMethods(this.getItemEvents());
-
- args[0] = prefix + ":" + rootEvent;
- args.splice(1, 0, view);
-
- // call collectionView itemEvent if defined
- if (typeof itemEvents !== "undefined" && _.isFunction(itemEvents[rootEvent])) {
- itemEvents[rootEvent].apply(this, args);
+ },
+
+ // Imports the "normalizeMethods" to transform hashes of
+ // events=>function references/names to a hash of events=>function references
+ normalizeMethods: Marionette.normalizeMethods,
+
+ // A handy way to merge passed-in options onto the instance
+ mergeOptions: Marionette.mergeOptions,
+
+ // Proxy `getOption` to enable getting options from this or this.options by name.
+ getOption: Marionette.proxyGetOption,
+
+ // Proxy `bindEntityEvents` to enable binding view's events from another entity.
+ bindEntityEvents: Marionette.proxyBindEntityEvents,
+
+ // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
+ unbindEntityEvents: Marionette.proxyUnbindEntityEvents
+ });
+
+ // Item View
+ // ---------
+
+ // A single item view implementation that contains code for rendering
+ // with underscore.js templates, serializing the view's model or collection,
+ // and calling several methods on extended views, such as `onRender`.
+ Marionette.ItemView = Marionette.View.extend({
+
+ // Setting up the inheritance chain which allows changes to
+ // Marionette.View.prototype.constructor which allows overriding
+ constructor: function() {
+ Marionette.View.apply(this, arguments);
+ },
+
+ // Serialize the model or collection for the view. If a model is
+ // found, the view's `serializeModel` is called. If a collection is found,
+ // each model in the collection is serialized by calling
+ // the view's `serializeCollection` and put into an `items` array in
+ // the resulting data. If both are found, defaults to the model.
+ // You can override the `serializeData` method in your own view definition,
+ // to provide custom serialization for your view's data.
+ serializeData: function() {
+ if (!this.model && !this.collection) {
+ return {};
}
-
- Marionette.triggerMethod.apply(this, args);
- }, this);
- },
-
- // returns the value of itemEvents depending on if a function
- getItemEvents: function() {
- if (_.isFunction(this.itemEvents)) {
- return this.itemEvents.call(this);
- }
-
- return this.itemEvents;
- },
-
- // render the item view
- renderItemView: function(view, index) {
- view.render();
- this.appendHtml(this, view, index);
- },
-
- // Build an `itemView` for every model in the collection.
- buildItemView: function(item, ItemViewType, itemViewOptions){
- var options = _.extend({model: item}, itemViewOptions);
- return new ItemViewType(options);
- },
-
- // get the child view by item it holds, and remove it
- removeItemView: function(item){
- var view = this.children.findByModel(item);
- this.removeChildView(view);
- this.checkEmpty();
- },
-
- // Remove the child view and close it
- removeChildView: function(view){
-
- // shut down the child view properly,
- // including events that the collection has from it
- if (view){
- this.stopListening(view);
-
- // call 'close' or 'remove', depending on which is found
- if (view.close) { view.close(); }
- else if (view.remove) { view.remove(); }
-
- this.children.remove(view);
- }
-
- this.triggerMethod("item:removed", view);
- },
-
- // helper to check if the collection is empty
- isEmpty: function(collection){
- // check if we're empty now
- return !this.collection || this.collection.length === 0;
- },
-
- // If empty, show the empty view
- checkEmpty: function (){
- if (this.isEmpty(this.collection)){
- this.showEmptyView();
- }
- },
-
- // You might need to override this if you've overridden appendHtml
- appendBuffer: function(collectionView, buffer) {
- collectionView.$el.append(buffer);
- },
-
- // Append the HTML to the collection's `el`.
- // Override this method to do something other
- // than `.append`.
- appendHtml: function(collectionView, itemView, index){
- if (collectionView.isBuffering) {
- // buffering happens on reset events and initial renders
- // in order to reduce the number of inserts into the
- // document, which are expensive.
- collectionView.elBuffer.appendChild(itemView.el);
- collectionView._bufferedChildren.push(itemView);
- }
- else {
- // If we've already rendered the main collection, just
- // append the new items directly into the element.
- collectionView.$el.append(itemView.el);
- }
- },
-
- // Internal method to set up the `children` object for
- // storing all of the child views
- _initChildViewStorage: function(){
- this.children = new Backbone.ChildViewContainer();
- },
-
- // Handle cleanup and other closing needs for
- // the collection of views.
- close: function(){
- if (this.isClosed){ return; }
-
- this.triggerMethod("collection:before:close");
- this.closeChildren();
- this.triggerMethod("collection:closed");
-
- Marionette.View.prototype.close.apply(this, arguments);
- },
-
- // Close the child views that this collection view
- // is holding on to, if any
- closeChildren: function(){
- this.children.each(function(child){
- this.removeChildView(child);
- }, this);
- this.checkEmpty();
- }
-});
-
-
-// Composite View
-// --------------
-
-// Used for rendering a branch-leaf, hierarchical structure.
-// Extends directly from CollectionView and also renders an
-// an item view as `modelView`, for the top leaf
-Marionette.CompositeView = Marionette.CollectionView.extend({
-
- // Setting up the inheritance chain which allows changes to
- // Marionette.CollectionView.prototype.constructor which allows overriding
- constructor: function(){
- Marionette.CollectionView.prototype.constructor.apply(this, arguments);
- },
-
- // Configured the initial events that the composite view
- // binds to. Override this method to prevent the initial
- // events, or to add your own initial events.
- _initialEvents: function(){
-
- // Bind only after composite view is rendered to avoid adding child views
- // to nonexistent itemViewContainer
- this.once('render', function () {
- if (this.collection){
- this.listenTo(this.collection, "add", this.addChildView);
- this.listenTo(this.collection, "remove", this.removeItemView);
- this.listenTo(this.collection, "reset", this._renderChildren);
+
+ var args = [this.model || this.collection];
+ if (arguments.length) {
+ args.push.apply(args, arguments);
}
- });
-
- },
-
- // Retrieve the `itemView` to be used when rendering each of
- // the items in the collection. The default is to return
- // `this.itemView` or Marionette.CompositeView if no `itemView`
- // has been defined
- getItemView: function(item){
- var itemView = Marionette.getOption(this, "itemView") || this.constructor;
-
- if (!itemView){
- throwError("An `itemView` must be specified", "NoItemViewError");
- }
-
- return itemView;
- },
-
- // Serialize the collection for the view.
- // You can override the `serializeData` method in your own view
- // definition, to provide custom serialization for your view's data.
- serializeData: function(){
- var data = {};
-
- if (this.model){
- data = this.model.toJSON();
- }
-
- return data;
- },
-
- // Renders the model once, and the collection once. Calling
- // this again will tell the model's view to re-render itself
- // but the collection will not re-render.
- render: function(){
- this.isRendered = true;
- this.isClosed = false;
- this.resetItemViewContainer();
-
- this.triggerBeforeRender();
- var html = this.renderModel();
- this.$el.html(html);
- // the ui bindings is done here and not at the end of render since they
- // will not be available until after the model is rendered, but should be
- // available before the collection is rendered.
- this.bindUIElements();
- this.triggerMethod("composite:model:rendered");
-
- this._renderChildren();
-
- this.triggerMethod("composite:rendered");
- this.triggerRendered();
- return this;
- },
-
- _renderChildren: function(){
- if (this.isRendered){
- this.triggerMethod("composite:collection:before:render");
- Marionette.CollectionView.prototype._renderChildren.call(this);
- this.triggerMethod("composite:collection:rendered");
- }
- },
-
- // Render an individual model, if we have one, as
- // part of a composite view (branch / leaf). For example:
- // a treeview.
- renderModel: function(){
- var data = {};
- data = this.serializeData();
- data = this.mixinTemplateHelpers(data);
-
- var template = this.getTemplate();
- return Marionette.Renderer.render(template, data);
- },
-
-
- // You might need to override this if you've overridden appendHtml
- appendBuffer: function(compositeView, buffer) {
- var $container = this.getItemViewContainer(compositeView);
- $container.append(buffer);
- },
-
- // Appends the `el` of itemView instances to the specified
- // `itemViewContainer` (a jQuery selector). Override this method to
- // provide custom logic of how the child item view instances have their
- // HTML appended to the composite view instance.
- appendHtml: function(compositeView, itemView, index){
- if (compositeView.isBuffering) {
- compositeView.elBuffer.appendChild(itemView.el);
- compositeView._bufferedChildren.push(itemView);
- }
- else {
- // If we've already rendered the main collection, just
- // append the new items directly into the element.
- var $container = this.getItemViewContainer(compositeView);
- $container.append(itemView.el);
+
+ if (this.model) {
+ return this.serializeModel.apply(this, args);
+ } else {
+ return {
+ items: this.serializeCollection.apply(this, args)
+ };
+ }
+ },
+
+ // Serialize a collection by serializing each of its models.
+ serializeCollection: function(collection) {
+ return collection.toJSON.apply(collection, _.rest(arguments));
+ },
+
+ // 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.
+ render: function() {
+ this._ensureViewIsIntact();
+
+ this.triggerMethod('before:render', this);
+
+ this._renderTemplate();
+ this.isRendered = true;
+ this.bindUIElements();
+
+ this.triggerMethod('render', this);
+
+ return this;
+ },
+
+ // Internal method to render the template with the serialized data
+ // and template helpers via the `Marionette.Renderer` object.
+ // Throws an `UndefinedTemplateError` error if the template is
+ // any falsely value but literal `false`.
+ _renderTemplate: function() {
+ var template = this.getTemplate();
+
+ // Allow template-less item views
+ if (template === false) {
+ return;
+ }
+
+ if (!template) {
+ throw new Marionette.Error({
+ name: 'UndefinedTemplateError',
+ message: 'Cannot render the template since it is null or undefined.'
+ });
+ }
+
+ // Add in entity data and template helpers
+ var data = this.mixinTemplateHelpers(this.serializeData());
+
+ // Render and add to el
+ var html = Marionette.Renderer.render(template, data, this);
+ this.attachElContent(html);
+
+ return this;
+ },
+
+ // Attaches the content of a given view.
+ // This method can be overridden to optimize rendering,
+ // or to render in a non standard way.
+ //
+ // For example, using `innerHTML` instead of `$el.html`
+ //
+ // ```js
+ // attachElContent: function(html) {
+ // this.el.innerHTML = html;
+ // return this;
+ // }
+ // ```
+ attachElContent: function(html) {
+ this.$el.html(html);
+
+ return this;
}
- },
-
-
- // Internal method to ensure an `$itemViewContainer` exists, for the
- // `appendHtml` method to use.
- getItemViewContainer: function(containerView){
- if ("$itemViewContainer" in containerView){
- return containerView.$itemViewContainer;
+ });
+
+ /* jshint maxstatements: 14 */
+
+ // Collection View
+ // ---------------
+
+ // A view that iterates over a Backbone.Collection
+ // and renders an individual child view for each model.
+ Marionette.CollectionView = Marionette.View.extend({
+
+ // used as the prefix for child view events
+ // that are forwarded through the collectionview
+ childViewEventPrefix: 'childview',
+
+ // flag for maintaining the sorted order of the collection
+ sort: true,
+
+ // constructor
+ // option to pass `{sort: false}` to prevent the `CollectionView` from
+ // maintaining the sorted order of the collection.
+ // This will fallback onto appending childView's to the end.
+ //
+ // option to pass `{comparator: compFunction()}` to allow the `CollectionView`
+ // to use a custom sort order for the collection.
+ constructor: function(options) {
+
+ this.once('render', this._initialEvents);
+ this._initChildViewStorage();
+
+ Marionette.View.apply(this, arguments);
+
+ this.on('show', this._onShowCalled);
+
+ this.initRenderBuffer();
+ },
+
+ // Instead of inserting elements one by one into the page,
+ // it's much more performant to insert elements into a document
+ // fragment and then insert that document fragment into the page
+ initRenderBuffer: function() {
+ this._bufferedChildren = [];
+ },
+
+ startBuffering: function() {
+ this.initRenderBuffer();
+ this.isBuffering = true;
+ },
+
+ endBuffering: function() {
+ this.isBuffering = false;
+ this._triggerBeforeShowBufferedChildren();
+
+ this.attachBuffer(this);
+
+ this._triggerShowBufferedChildren();
+ this.initRenderBuffer();
+ },
+
+ _triggerBeforeShowBufferedChildren: function() {
+ if (this._isShown) {
+ _.each(this._bufferedChildren, _.partial(this._triggerMethodOnChild, 'before:show'));
+ }
+ },
+
+ _triggerShowBufferedChildren: function() {
+ if (this._isShown) {
+ _.each(this._bufferedChildren, _.partial(this._triggerMethodOnChild, 'show'));
+
+ this._bufferedChildren = [];
+ }
+ },
+
+ // Internal method for _.each loops to call `Marionette.triggerMethodOn` on
+ // a child view
+ _triggerMethodOnChild: function(event, childView) {
+ Marionette.triggerMethodOn(childView, event);
+ },
+
+ // Configured the initial events that the collection view
+ // binds to.
+ _initialEvents: function() {
+ if (this.collection) {
+ this.listenTo(this.collection, 'add', this._onCollectionAdd);
+ this.listenTo(this.collection, 'remove', this._onCollectionRemove);
+ this.listenTo(this.collection, 'reset', this.render);
+
+ if (this.getOption('sort')) {
+ this.listenTo(this.collection, 'sort', this._sortViews);
+ }
+ }
+ },
+
+ // Handle a child added to the collection
+ _onCollectionAdd: function(child, collection, opts) {
+ var index;
+ if (opts.at !== undefined) {
+ index = opts.at;
+ } else {
+ index = _.indexOf(this._filteredSortedModels(), child);
+ }
+
+ if (this._shouldAddChild(child, index)) {
+ this.destroyEmptyView();
+ var ChildView = this.getChildView(child);
+ this.addChild(child, ChildView, index);
+ }
+ },
+
+ // get the child view by model it holds, and remove it
+ _onCollectionRemove: function(model) {
+ var view = this.children.findByModel(model);
+ this.removeChildView(view);
+ this.checkEmpty();
+ },
+
+ _onShowCalled: function() {
+ this.children.each(_.partial(this._triggerMethodOnChild, 'show'));
+ },
+
+ // Render children views. Override this method to
+ // provide your own implementation of a render function for
+ // the collection view.
+ render: function() {
+ this._ensureViewIsIntact();
+ this.triggerMethod('before:render', this);
+ this._renderChildren();
+ this.isRendered = true;
+ this.triggerMethod('render', this);
+ return this;
+ },
+
+ // Reorder DOM after sorting. When your element's rendering
+ // do not use their index, you can pass reorderOnSort: true
+ // to only reorder the DOM after a sort instead of rendering
+ // all the collectionView
+ reorder: function() {
+ var children = this.children;
+ var models = this._filteredSortedModels();
+ var modelsChanged = _.find(models, function(model) {
+ return !children.findByModel(model);
+ });
+
+ // If the models we're displaying have changed due to filtering
+ // We need to add and/or remove child views
+ // So render as normal
+ if (modelsChanged) {
+ this.render();
+ } else {
+ // get the DOM nodes in the same order as the models
+ var els = _.map(models, function(model) {
+ return children.findByModel(model).el;
+ });
+
+ // since append moves elements that are already in the DOM,
+ // appending the elements will effectively reorder them
+ this.triggerMethod('before:reorder');
+ this._appendReorderedChildren(els);
+ this.triggerMethod('reorder');
+ }
+ },
+
+ // Render view after sorting. Override this method to
+ // change how the view renders after a `sort` on the collection.
+ // An example of this would be to only `renderChildren` in a `CompositeView`
+ // rather than the full view.
+ resortView: function() {
+ if (Marionette.getOption(this, 'reorderOnSort')) {
+ this.reorder();
+ } else {
+ this.render();
+ }
+ },
+
+ // Internal method. This checks for any changes in the order of the collection.
+ // If the index of any view doesn't match, it will render.
+ _sortViews: function() {
+ var models = this._filteredSortedModels();
+
+ // check for any changes in sort order of views
+ var orderChanged = _.find(models, function(item, index) {
+ var view = this.children.findByModel(item);
+ return !view || view._index !== index;
+ }, this);
+
+ if (orderChanged) {
+ this.resortView();
+ }
+ },
+
+ // Internal reference to what index a `emptyView` is.
+ _emptyViewIndex: -1,
+
+ // Internal method. Separated so that CompositeView can append to the childViewContainer
+ // if necessary
+ _appendReorderedChildren: function(children) {
+ this.$el.append(children);
+ },
+
+ // Internal method. Separated so that CompositeView can have
+ // more control over events being triggered, around the rendering
+ // process
+ _renderChildren: function() {
+ this.destroyEmptyView();
+ this.destroyChildren();
+
+ if (this.isEmpty(this.collection)) {
+ this.showEmptyView();
+ } else {
+ this.triggerMethod('before:render:collection', this);
+ this.startBuffering();
+ this.showCollection();
+ this.endBuffering();
+ this.triggerMethod('render:collection', this);
+
+ // If we have shown children and none have passed the filter, show the empty view
+ if (this.children.isEmpty()) {
+ this.showEmptyView();
+ }
+ }
+ },
+
+ // Internal method to loop through collection and show each child view.
+ showCollection: function() {
+ var ChildView;
+
+ var models = this._filteredSortedModels();
+
+ _.each(models, function(child, index) {
+ ChildView = this.getChildView(child);
+ this.addChild(child, ChildView, index);
+ }, this);
+ },
+
+ // Allow the collection to be sorted by a custom view comparator
+ _filteredSortedModels: function() {
+ var models;
+ var viewComparator = this.getViewComparator();
+
+ if (viewComparator) {
+ if (_.isString(viewComparator) || viewComparator.length === 1) {
+ models = this.collection.sortBy(viewComparator, this);
+ } else {
+ models = _.clone(this.collection.models).sort(_.bind(viewComparator, this));
+ }
+ } else {
+ models = this.collection.models;
+ }
+
+ // Filter after sorting in case the filter uses the index
+ if (this.getOption('filter')) {
+ models = _.filter(models, function(model, index) {
+ return this._shouldAddChild(model, index);
+ }, this);
+ }
+
+ return models;
+ },
+
+ // Internal method to show an empty view in place of
+ // a collection of child views, when the collection is empty
+ showEmptyView: function() {
+ var EmptyView = this.getEmptyView();
+
+ if (EmptyView && !this._showingEmptyView) {
+ this.triggerMethod('before:render:empty');
+
+ this._showingEmptyView = true;
+ var model = new Backbone.Model();
+ this.addEmptyView(model, EmptyView);
+
+ this.triggerMethod('render:empty');
+ }
+ },
+
+ // Internal method to destroy an existing emptyView instance
+ // if one exists. Called when a collection view has been
+ // rendered empty, and then a child is added to the collection.
+ destroyEmptyView: function() {
+ if (this._showingEmptyView) {
+ this.triggerMethod('before:remove:empty');
+
+ this.destroyChildren();
+ delete this._showingEmptyView;
+
+ this.triggerMethod('remove:empty');
+ }
+ },
+
+ // Retrieve the empty view class
+ getEmptyView: function() {
+ return this.getOption('emptyView');
+ },
+
+ // Render and show the emptyView. Similar to addChild method
+ // but "add:child" events are not fired, and the event from
+ // emptyView are not forwarded
+ addEmptyView: function(child, EmptyView) {
+
+ // get the emptyViewOptions, falling back to childViewOptions
+ var emptyViewOptions = this.getOption('emptyViewOptions') ||
+ this.getOption('childViewOptions');
+
+ if (_.isFunction(emptyViewOptions)) {
+ emptyViewOptions = emptyViewOptions.call(this, child, this._emptyViewIndex);
+ }
+
+ // build the empty view
+ var view = this.buildChildView(child, EmptyView, emptyViewOptions);
+
+ view._parent = this;
+
+ // Proxy emptyView events
+ this.proxyChildEvents(view);
+
+ // trigger the 'before:show' event on `view` if the collection view
+ // has already been shown
+ if (this._isShown) {
+ Marionette.triggerMethodOn(view, 'before:show');
+ }
+
+ // Store the `emptyView` like a `childView` so we can properly
+ // remove and/or close it later
+ this.children.add(view);
+
+ // Render it and show it
+ this.renderChildView(view, this._emptyViewIndex);
+
+ // call the 'show' method if the collection view
+ // has already been shown
+ if (this._isShown) {
+ Marionette.triggerMethodOn(view, 'show');
+ }
+ },
+
+ // Retrieve the `childView` class, either from `this.options.childView`
+ // or from the `childView` in the object definition. The "options"
+ // takes precedence.
+ // This method receives the model that will be passed to the instance
+ // created from this `childView`. Overriding methods may use the child
+ // to determine what `childView` class to return.
+ getChildView: function(child) {
+ var childView = this.getOption('childView');
+
+ if (!childView) {
+ throw new Marionette.Error({
+ name: 'NoChildViewError',
+ message: 'A "childView" must be specified'
+ });
+ }
+
+ return childView;
+ },
+
+ // Render the child's view and add it to the
+ // HTML for the collection view at a given index.
+ // This will also update the indices of later views in the collection
+ // in order to keep the children in sync with the collection.
+ addChild: function(child, ChildView, index) {
+ var childViewOptions = this.getOption('childViewOptions');
+ childViewOptions = Marionette._getValue(childViewOptions, this, [child, index]);
+
+ var view = this.buildChildView(child, ChildView, childViewOptions);
+
+ // increment indices of views after this one
+ this._updateIndices(view, true, index);
+
+ this._addChildView(view, index);
+
+ view._parent = this;
+
+ return view;
+ },
+
+ // Internal method. This decrements or increments the indices of views after the
+ // added/removed view to keep in sync with the collection.
+ _updateIndices: function(view, increment, index) {
+ if (!this.getOption('sort')) {
+ return;
+ }
+
+ if (increment) {
+ // assign the index to the view
+ view._index = index;
+ }
+
+ // update the indexes of views after this one
+ this.children.each(function(laterView) {
+ if (laterView._index >= view._index) {
+ laterView._index += increment ? 1 : -1;
+ }
+ });
+ },
+
+ // Internal Method. Add the view to children and render it at
+ // the given index.
+ _addChildView: function(view, index) {
+ // set up the child view event forwarding
+ this.proxyChildEvents(view);
+
+ this.triggerMethod('before:add:child', view);
+
+ // trigger the 'before:show' event on `view` if the collection view
+ // has already been shown
+ if (this._isShown && !this.isBuffering) {
+ Marionette.triggerMethodOn(view, 'before:show');
+ }
+
+ // Store the child view itself so we can properly
+ // remove and/or destroy it later
+ this.children.add(view);
+ this.renderChildView(view, index);
+
+ if (this._isShown && !this.isBuffering) {
+ Marionette.triggerMethodOn(view, 'show');
+ }
+
+ this.triggerMethod('add:child', view);
+ },
+
+ // render the child view
+ renderChildView: function(view, index) {
+ view.render();
+ this.attachHtml(this, view, index);
+ return view;
+ },
+
+ // Build a `childView` for a model in the collection.
+ buildChildView: function(child, ChildViewClass, childViewOptions) {
+ var options = _.extend({model: child}, childViewOptions);
+ return new ChildViewClass(options);
+ },
+
+ // Remove the child view and destroy it.
+ // This function also updates the indices of
+ // later views in the collection in order to keep
+ // the children in sync with the collection.
+ removeChildView: function(view) {
+
+ if (view) {
+ this.triggerMethod('before:remove:child', view);
+
+ // call 'destroy' or 'remove', depending on which is found
+ if (view.destroy) {
+ view.destroy();
+ } else if (view.remove) {
+ view.remove();
+ }
+
+ delete view._parent;
+ this.stopListening(view);
+ this.children.remove(view);
+ this.triggerMethod('remove:child', view);
+
+ // decrement the index of views after this one
+ this._updateIndices(view, false);
+ }
+
+ return view;
+ },
+
+ // check if the collection is empty
+ isEmpty: function() {
+ return !this.collection || this.collection.length === 0;
+ },
+
+ // If empty, show the empty view
+ checkEmpty: function() {
+ if (this.isEmpty(this.collection)) {
+ this.showEmptyView();
+ }
+ },
+
+ // You might need to override this if you've overridden attachHtml
+ attachBuffer: function(collectionView) {
+ collectionView.$el.append(this._createBuffer(collectionView));
+ },
+
+ // Create a fragment buffer from the currently buffered children
+ _createBuffer: function(collectionView) {
+ var elBuffer = document.createDocumentFragment();
+ _.each(collectionView._bufferedChildren, function(b) {
+ elBuffer.appendChild(b.el);
+ });
+ return elBuffer;
+ },
+
+ // Append the HTML to the collection's `el`.
+ // Override this method to do something other
+ // than `.append`.
+ attachHtml: function(collectionView, childView, index) {
+ if (collectionView.isBuffering) {
+ // buffering happens on reset events and initial renders
+ // in order to reduce the number of inserts into the
+ // document, which are expensive.
+ collectionView._bufferedChildren.splice(index, 0, childView);
+ } else {
+ // If we've already rendered the main collection, append
+ // the new child into the correct order if we need to. Otherwise
+ // append to the end.
+ if (!collectionView._insertBefore(childView, index)) {
+ collectionView._insertAfter(childView);
+ }
+ }
+ },
+
+ // Internal method. Check whether we need to insert the view into
+ // the correct position.
+ _insertBefore: function(childView, index) {
+ var currentView;
+ var findPosition = this.getOption('sort') && (index < this.children.length - 1);
+ if (findPosition) {
+ // Find the view after this one
+ currentView = this.children.find(function(view) {
+ return view._index === index + 1;
+ });
+ }
+
+ if (currentView) {
+ currentView.$el.before(childView.el);
+ return true;
+ }
+
+ return false;
+ },
+
+ // Internal method. Append a view to the end of the $el
+ _insertAfter: function(childView) {
+ this.$el.append(childView.el);
+ },
+
+ // Internal method to set up the `children` object for
+ // storing all of the child views
+ _initChildViewStorage: function() {
+ this.children = new Backbone.ChildViewContainer();
+ },
+
+ // Handle cleanup and other destroying needs for the collection of views
+ destroy: function() {
+ if (this.isDestroyed) { return this; }
+
+ this.triggerMethod('before:destroy:collection');
+ this.destroyChildren();
+ this.triggerMethod('destroy:collection');
+
+ return Marionette.View.prototype.destroy.apply(this, arguments);
+ },
+
+ // Destroy the child views that this collection view
+ // is holding on to, if any
+ destroyChildren: function() {
+ var childViews = this.children.map(_.identity);
+ this.children.each(this.removeChildView, this);
+ this.checkEmpty();
+ return childViews;
+ },
+
+ // Return true if the given child should be shown
+ // Return false otherwise
+ // The filter will be passed (child, index, collection)
+ // Where
+ // 'child' is the given model
+ // 'index' is the index of that model in the collection
+ // 'collection' is the collection referenced by this CollectionView
+ _shouldAddChild: function(child, index) {
+ var filter = this.getOption('filter');
+ return !_.isFunction(filter) || filter.call(this, child, index, this.collection);
+ },
+
+ // Set up the child view event forwarding. Uses a "childview:"
+ // prefix in front of all forwarded events.
+ proxyChildEvents: function(view) {
+ var prefix = this.getOption('childViewEventPrefix');
+
+ // Forward all child view events through the parent,
+ // prepending "childview:" to the event name
+ this.listenTo(view, 'all', function() {
+ var args = _.toArray(arguments);
+ var rootEvent = args[0];
+ var childEvents = this.normalizeMethods(_.result(this, 'childEvents'));
+
+ args[0] = prefix + ':' + rootEvent;
+ args.splice(1, 0, view);
+
+ // call collectionView childEvent if defined
+ if (typeof childEvents !== 'undefined' && _.isFunction(childEvents[rootEvent])) {
+ childEvents[rootEvent].apply(this, args.slice(1));
+ }
+
+ this.triggerMethod.apply(this, args);
+ });
+ },
+
+ _getImmediateChildren: function() {
+ return _.values(this.children._views);
+ },
+
+ getViewComparator: function() {
+ return this.getOption('viewComparator');
}
-
- var container;
- var itemViewContainer = Marionette.getOption(containerView, "itemViewContainer");
- if (itemViewContainer){
-
- var selector = _.isFunction(itemViewContainer) ? itemViewContainer.call(this) : itemViewContainer;
- container = containerView.$(selector);
- if (container.length <= 0) {
- throwError("The specified `itemViewContainer` was not found: " + containerView.itemViewContainer, "ItemViewContainerMissingError");
+ });
+
+ /* jshint maxstatements: 17, maxlen: 117 */
+
+ // Composite View
+ // --------------
+
+ // Used for rendering a branch-leaf, hierarchical structure.
+ // Extends directly from CollectionView and also renders an
+ // a child view as `modelView`, for the top leaf
+ Marionette.CompositeView = Marionette.CollectionView.extend({
+
+ // Setting up the inheritance chain which allows changes to
+ // Marionette.CollectionView.prototype.constructor which allows overriding
+ // option to pass '{sort: false}' to prevent the CompositeView from
+ // maintaining the sorted order of the collection.
+ // This will fallback onto appending childView's to the end.
+ constructor: function() {
+ Marionette.CollectionView.apply(this, arguments);
+ },
+
+ // Configured the initial events that the composite view
+ // binds to. Override this method to prevent the initial
+ // events, or to add your own initial events.
+ _initialEvents: function() {
+
+ // Bind only after composite view is rendered to avoid adding child views
+ // to nonexistent childViewContainer
+
+ if (this.collection) {
+ this.listenTo(this.collection, 'add', this._onCollectionAdd);
+ this.listenTo(this.collection, 'remove', this._onCollectionRemove);
+ this.listenTo(this.collection, 'reset', this._renderChildren);
+
+ if (this.getOption('sort')) {
+ this.listenTo(this.collection, 'sort', this._sortViews);
+ }
+ }
+ },
+
+ // Retrieve the `childView` to be used when rendering each of
+ // the items in the collection. The default is to return
+ // `this.childView` or Marionette.CompositeView if no `childView`
+ // has been defined
+ getChildView: function(child) {
+ var childView = this.getOption('childView') || this.constructor;
+
+ return childView;
+ },
+
+ // Serialize the model for the view.
+ // You can override the `serializeData` method in your own view
+ // definition, to provide custom serialization for your view's data.
+ serializeData: function() {
+ var data = {};
+
+ if (this.model) {
+ data = _.partial(this.serializeModel, this.model).apply(this, arguments);
+ }
+
+ return data;
+ },
+
+ // Renders the model and the collection.
+ render: function() {
+ this._ensureViewIsIntact();
+ this._isRendering = true;
+ this.resetChildViewContainer();
+
+ this.triggerMethod('before:render', this);
+
+ this._renderTemplate();
+ this._renderChildren();
+
+ this._isRendering = false;
+ this.isRendered = true;
+ this.triggerMethod('render', this);
+ return this;
+ },
+
+ _renderChildren: function() {
+ if (this.isRendered || this._isRendering) {
+ Marionette.CollectionView.prototype._renderChildren.call(this);
+ }
+ },
+
+ // Render the root template that the children
+ // views are appended to
+ _renderTemplate: function() {
+ var data = {};
+ data = this.serializeData();
+ data = this.mixinTemplateHelpers(data);
+
+ this.triggerMethod('before:render:template');
+
+ var template = this.getTemplate();
+ var html = Marionette.Renderer.render(template, data, this);
+ this.attachElContent(html);
+
+ // the ui bindings is done here and not at the end of render since they
+ // will not be available until after the model is rendered, but should be
+ // available before the collection is rendered.
+ this.bindUIElements();
+ this.triggerMethod('render:template');
+ },
+
+ // Attaches the content of the root.
+ // This method can be overridden to optimize rendering,
+ // or to render in a non standard way.
+ //
+ // For example, using `innerHTML` instead of `$el.html`
+ //
+ // ```js
+ // attachElContent: function(html) {
+ // this.el.innerHTML = html;
+ // return this;
+ // }
+ // ```
+ attachElContent: function(html) {
+ this.$el.html(html);
+
+ return this;
+ },
+
+ // You might need to override this if you've overridden attachHtml
+ attachBuffer: function(compositeView) {
+ var $container = this.getChildViewContainer(compositeView);
+ $container.append(this._createBuffer(compositeView));
+ },
+
+ // Internal method. Append a view to the end of the $el.
+ // Overidden from CollectionView to ensure view is appended to
+ // childViewContainer
+ _insertAfter: function(childView) {
+ var $container = this.getChildViewContainer(this, childView);
+ $container.append(childView.el);
+ },
+
+ // Internal method. Append reordered childView'.
+ // Overidden from CollectionView to ensure reordered views
+ // are appended to childViewContainer
+ _appendReorderedChildren: function(children) {
+ var $container = this.getChildViewContainer(this);
+ $container.append(children);
+ },
+
+ // Internal method to ensure an `$childViewContainer` exists, for the
+ // `attachHtml` method to use.
+ getChildViewContainer: function(containerView, childView) {
+ if ('$childViewContainer' in containerView) {
+ return containerView.$childViewContainer;
+ }
+
+ var container;
+ var childViewContainer = Marionette.getOption(containerView, 'childViewContainer');
+ if (childViewContainer) {
+
+ var selector = Marionette._getValue(childViewContainer, containerView);
+
+ if (selector.charAt(0) === '@' && containerView.ui) {
+ container = containerView.ui[selector.substr(4)];
+ } else {
+ container = containerView.$(selector);
+ }
+
+ if (container.length <= 0) {
+ throw new Marionette.Error({
+ name: 'ChildViewContainerMissingError',
+ message: 'The specified "childViewContainer" was not found: ' + containerView.childViewContainer
+ });
+ }
+
+ } else {
+ container = containerView.$el;
+ }
+
+ containerView.$childViewContainer = container;
+ return container;
+ },
+
+ // Internal method to reset the `$childViewContainer` on render
+ resetChildViewContainer: function() {
+ if (this.$childViewContainer) {
+ delete this.$childViewContainer;
}
-
- } else {
- container = containerView.$el;
}
-
- containerView.$itemViewContainer = container;
- return container;
- },
-
- // Internal method to reset the `$itemViewContainer` on render
- resetItemViewContainer: function(){
- if (this.$itemViewContainer){
- delete this.$itemViewContainer;
+ });
+
+ // Layout View
+ // -----------
+
+ // Used for managing application layoutViews, nested layoutViews and
+ // multiple regions within an application or sub-application.
+ //
+ // A specialized view class that renders an area of HTML and then
+ // attaches `Region` instances to the specified `regions`.
+ // Used for composite view management and sub-application areas.
+ Marionette.LayoutView = Marionette.ItemView.extend({
+ regionClass: Marionette.Region,
+
+ options: {
+ destroyImmediate: false
+ },
+
+ // used as the prefix for child view events
+ // that are forwarded through the layoutview
+ childViewEventPrefix: 'childview',
+
+ // Ensure the regions are available when the `initialize` method
+ // is called.
+ constructor: function(options) {
+ options = options || {};
+
+ this._firstRender = true;
+ this._initializeRegions(options);
+
+ Marionette.ItemView.call(this, options);
+ },
+
+ // LayoutView's render will use the existing region objects the
+ // first time it is called. Subsequent calls will destroy the
+ // views that the regions are showing and then reset the `el`
+ // for the regions to the newly rendered DOM elements.
+ render: function() {
+ this._ensureViewIsIntact();
+
+ if (this._firstRender) {
+ // if this is the first render, don't do anything to
+ // reset the regions
+ this._firstRender = false;
+ } else {
+ // If this is not the first render call, then we need to
+ // re-initialize the `el` for each region
+ this._reInitializeRegions();
+ }
+
+ return Marionette.ItemView.prototype.render.apply(this, arguments);
+ },
+
+ // Handle destroying regions, and then destroy the view itself.
+ destroy: function() {
+ if (this.isDestroyed) { return this; }
+ // #2134: remove parent element before destroying the child views, so
+ // removing the child views doesn't retrigger repaints
+ if (this.getOption('destroyImmediate') === true) {
+ this.$el.remove();
+ }
+ this.regionManager.destroy();
+ return Marionette.ItemView.prototype.destroy.apply(this, arguments);
+ },
+
+ showChildView: function(regionName, view) {
+ return this.getRegion(regionName).show(view);
+ },
+
+ getChildView: function(regionName) {
+ return this.getRegion(regionName).currentView;
+ },
+
+ // Add a single region, by name, to the layoutView
+ addRegion: function(name, definition) {
+ var regions = {};
+ regions[name] = definition;
+ return this._buildRegions(regions)[name];
+ },
+
+ // Add multiple regions as a {name: definition, name2: def2} object literal
+ addRegions: function(regions) {
+ this.regions = _.extend({}, this.regions, regions);
+ return this._buildRegions(regions);
+ },
+
+ // Remove a single region from the LayoutView, by name
+ removeRegion: function(name) {
+ delete this.regions[name];
+ return this.regionManager.removeRegion(name);
+ },
+
+ // Provides alternative access to regions
+ // Accepts the region name
+ // getRegion('main')
+ getRegion: function(region) {
+ return this.regionManager.get(region);
+ },
+
+ // Get all regions
+ getRegions: function() {
+ return this.regionManager.getRegions();
+ },
+
+ // internal method to build regions
+ _buildRegions: function(regions) {
+ var defaults = {
+ regionClass: this.getOption('regionClass'),
+ parentEl: _.partial(_.result, this, 'el')
+ };
+
+ return this.regionManager.addRegions(regions, defaults);
+ },
+
+ // Internal method to initialize the regions that have been defined in a
+ // `regions` attribute on this layoutView.
+ _initializeRegions: function(options) {
+ var regions;
+ this._initRegionManager();
+
+ regions = Marionette._getValue(this.regions, this, [options]) || {};
+
+ // Enable users to define `regions` as instance options.
+ var regionOptions = this.getOption.call(options, 'regions');
+
+ // enable region options to be a function
+ regionOptions = Marionette._getValue(regionOptions, this, [options]);
+
+ _.extend(regions, regionOptions);
+
+ // Normalize region selectors hash to allow
+ // a user to use the @ui. syntax.
+ regions = this.normalizeUIValues(regions, ['selector', 'el']);
+
+ this.addRegions(regions);
+ },
+
+ // Internal method to re-initialize all of the regions by updating the `el` that
+ // they point to
+ _reInitializeRegions: function() {
+ this.regionManager.invoke('reset');
+ },
+
+ // Enable easy overriding of the default `RegionManager`
+ // for customized region interactions and business specific
+ // view logic for better control over single regions.
+ getRegionManager: function() {
+ return new Marionette.RegionManager();
+ },
+
+ // Internal method to initialize the region manager
+ // and all regions in it
+ _initRegionManager: function() {
+ this.regionManager = this.getRegionManager();
+ this.regionManager._parent = this;
+
+ this.listenTo(this.regionManager, 'before:add:region', function(name) {
+ this.triggerMethod('before:add:region', name);
+ });
+
+ this.listenTo(this.regionManager, 'add:region', function(name, region) {
+ this[name] = region;
+ this.triggerMethod('add:region', name, region);
+ });
+
+ this.listenTo(this.regionManager, 'before:remove:region', function(name) {
+ this.triggerMethod('before:remove:region', name);
+ });
+
+ this.listenTo(this.regionManager, 'remove:region', function(name, region) {
+ delete this[name];
+ this.triggerMethod('remove:region', name, region);
+ });
+ },
+
+ _getImmediateChildren: function() {
+ return _.chain(this.regionManager.getRegions())
+ .pluck('currentView')
+ .compact()
+ .value();
}
- }
-});
-
-
-// Layout
-// ------
-
-// Used for managing application layouts, nested layouts and
-// multiple regions within an application or sub-application.
-//
-// A specialized view type that renders an area of HTML and then
-// attaches `Region` instances to the specified `regions`.
-// Used for composite view management and sub-application areas.
-Marionette.Layout = Marionette.ItemView.extend({
- regionType: Marionette.Region,
-
- // Ensure the regions are available when the `initialize` method
- // is called.
- constructor: function (options) {
- options = options || {};
-
- this._firstRender = true;
- this._initializeRegions(options);
-
- Marionette.ItemView.prototype.constructor.call(this, options);
- },
-
- // Layout's render will use the existing region objects the
- // first time it is called. Subsequent calls will close the
- // views that the regions are showing and then reset the `el`
- // for the regions to the newly rendered DOM elements.
- render: function(){
+ });
+
- if (this.isClosed){
- // a previously closed layout means we need to
- // completely re-initialize the regions
- this._initializeRegions();
+ // Behavior
+ // --------
+
+ // A Behavior is an isolated set of DOM /
+ // user interactions that can be mixed into any View.
+ // Behaviors allow you to blackbox View specific interactions
+ // into portable logical chunks, keeping your views simple and your code DRY.
+
+ Marionette.Behavior = Marionette.Object.extend({
+ constructor: function(options, view) {
+ // Setup reference to the view.
+ // this comes in handle when a behavior
+ // wants to directly talk up the chain
+ // to the view.
+ this.view = view;
+ this.defaults = _.result(this, 'defaults') || {};
+ this.options = _.extend({}, this.defaults, options);
+ // Construct an internal UI hash using
+ // the views UI hash and then the behaviors 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.
+ this.ui = _.extend({}, _.result(view, 'ui'), _.result(this, 'ui'));
+
+ Marionette.Object.apply(this, arguments);
+ },
+
+ // proxy behavior $ method to the view
+ // this is useful for doing jquery DOM lookups
+ // scoped to behaviors view.
+ $: function() {
+ return this.view.$.apply(this.view, arguments);
+ },
+
+ // Stops the behavior from listening to events.
+ // Overrides Object#destroy to prevent additional events from being triggered.
+ destroy: function() {
+ this.stopListening();
+
+ return this;
+ },
+
+ proxyViewProperties: function(view) {
+ this.$el = view.$el;
+ this.el = view.el;
}
- if (this._firstRender) {
- // if this is the first render, don't do anything to
- // reset the regions
- this._firstRender = false;
- } else if (!this.isClosed){
- // If this is not the first render call, then we need to
- // re-initializing the `el` for each region
- this._reInitializeRegions();
+ });
+
+ /* jshint maxlen: 143 */
+ // Behaviors
+ // ---------
+
+ // Behaviors is a utility class that takes care of
+ // gluing your behavior instances to their given View.
+ // The most important part of this class is that you
+ // **MUST** override the class level behaviorsLookup
+ // method for things to work properly.
+
+ Marionette.Behaviors = (function(Marionette, _) {
+ // Borrow event splitter from Backbone
+ var delegateEventSplitter = /^(\S+)\s*(.*)$/;
+
+ function Behaviors(view, behaviors) {
+
+ if (!_.isObject(view.behaviors)) {
+ return {};
+ }
+
+ // Behaviors defined on a view can be a flat object literal
+ // or it can be a function that returns an object.
+ behaviors = Behaviors.parseBehaviors(view, behaviors || _.result(view, 'behaviors'));
+
+ // Wraps several of the view's methods
+ // calling the methods first on each behavior
+ // and then eventually calling the method on the view.
+ Behaviors.wrap(view, behaviors, _.keys(methods));
+ return behaviors;
}
-
- return Marionette.ItemView.prototype.render.apply(this, arguments);
- },
-
- // Handle closing regions, and then close the view itself.
- close: function () {
- if (this.isClosed){ return; }
- this.regionManager.close();
- Marionette.ItemView.prototype.close.apply(this, arguments);
- },
-
- // Add a single region, by name, to the layout
- addRegion: function(name, definition){
- var regions = {};
- regions[name] = definition;
- return this._buildRegions(regions)[name];
- },
-
- // Add multiple regions as a {name: definition, name2: def2} object literal
- addRegions: function(regions){
- this.regions = _.extend({}, this.regions, regions);
- return this._buildRegions(regions);
- },
-
- // Remove a single region from the Layout, by name
- removeRegion: function(name){
- delete this.regions[name];
- return this.regionManager.removeRegion(name);
- },
-
- // internal method to build regions
- _buildRegions: function(regions){
- var that = this;
-
- var defaults = {
- regionType: Marionette.getOption(this, "regionType"),
- parentEl: function(){ return that.$el; }
+
+ var methods = {
+ behaviorTriggers: function(behaviorTriggers, behaviors) {
+ var triggerBuilder = new BehaviorTriggersBuilder(this, behaviors);
+ return triggerBuilder.buildBehaviorTriggers();
+ },
+
+ behaviorEvents: function(behaviorEvents, behaviors) {
+ var _behaviorsEvents = {};
+
+ _.each(behaviors, function(b, i) {
+ var _events = {};
+ var behaviorEvents = _.clone(_.result(b, 'events')) || {};
+
+ // Normalize behavior events hash to allow
+ // a user to use the @ui. syntax.
+ behaviorEvents = Marionette.normalizeUIKeys(behaviorEvents, getBehaviorsUI(b));
+
+ var j = 0;
+ _.each(behaviorEvents, function(behaviour, key) {
+ var match = key.match(delegateEventSplitter);
+
+ // Set event name to be namespaced using the view cid,
+ // the behavior index, and the behavior event index
+ // to generate a non colliding event namespace
+ // http://api.jquery.com/event.namespace/
+ var eventName = match[1] + '.' + [this.cid, i, j++, ' '].join('');
+ var selector = match[2];
+
+ var eventKey = eventName + selector;
+ var handler = _.isFunction(behaviour) ? behaviour : b[behaviour];
+
+ _events[eventKey] = _.bind(handler, b);
+ }, this);
+
+ _behaviorsEvents = _.extend(_behaviorsEvents, _events);
+ }, this);
+
+ return _behaviorsEvents;
+ }
};
-
- return this.regionManager.addRegions(regions, defaults);
- },
-
- // Internal method to initialize the regions that have been defined in a
- // `regions` attribute on this layout.
- _initializeRegions: function (options) {
- var regions;
- this._initRegionManager();
-
- if (_.isFunction(this.regions)) {
- regions = this.regions(options);
- } else {
- regions = this.regions || {};
- }
-
- this.addRegions(regions);
- },
-
- // Internal method to re-initialize all of the regions by updating the `el` that
- // they point to
- _reInitializeRegions: function(){
- this.regionManager.closeRegions();
- this.regionManager.each(function(region){
- region.reset();
- });
- },
-
- // Internal method to initialize the region manager
- // and all regions in it
- _initRegionManager: function(){
- this.regionManager = new Marionette.RegionManager();
-
- this.listenTo(this.regionManager, "region:add", function(name, region){
- this[name] = region;
- this.trigger("region:add", name, region);
- });
-
- this.listenTo(this.regionManager, "region:remove", function(name, region){
- delete this[name];
- this.trigger("region:remove", name, region);
+
+ _.extend(Behaviors, {
+
+ // Placeholder method to be extended by the user.
+ // The method should define the object that stores the behaviors.
+ // i.e.
+ //
+ // ```js
+ // Marionette.Behaviors.behaviorsLookup: function() {
+ // return App.Behaviors
+ // }
+ // ```
+ behaviorsLookup: function() {
+ throw new Marionette.Error({
+ message: 'You must define where your behaviors are stored.',
+ url: 'marionette.behaviors.html#behaviorslookup'
+ });
+ },
+
+ // Takes care of getting the behavior class
+ // given options and a key.
+ // If a user passes in options.behaviorClass
+ // default to using that. Otherwise delegate
+ // the lookup to the users `behaviorsLookup` implementation.
+ getBehaviorClass: function(options, key) {
+ if (options.behaviorClass) {
+ return options.behaviorClass;
+ }
+
+ // Get behavior class can be either a flat object or a method
+ return Marionette._getValue(Behaviors.behaviorsLookup, this, [options, key])[key];
+ },
+
+ // Iterate over the behaviors object, for each behavior
+ // instantiate it and get its grouped behaviors.
+ parseBehaviors: function(view, behaviors) {
+ return _.chain(behaviors).map(function(options, key) {
+ var BehaviorClass = Behaviors.getBehaviorClass(options, key);
+
+ var behavior = new BehaviorClass(options, view);
+ var nestedBehaviors = Behaviors.parseBehaviors(view, _.result(behavior, 'behaviors'));
+
+ return [behavior].concat(nestedBehaviors);
+ }).flatten().value();
+ },
+
+ // Wrap view internal methods so that they delegate to behaviors. For example,
+ // `onDestroy` should trigger destroy on all of the behaviors and then destroy itself.
+ // i.e.
+ //
+ // `view.delegateEvents = _.partial(methods.delegateEvents, view.delegateEvents, behaviors);`
+ wrap: function(view, behaviors, methodNames) {
+ _.each(methodNames, function(methodName) {
+ view[methodName] = _.partial(methods[methodName], view[methodName], behaviors);
+ });
+ }
});
- }
-});
-
-
-// AppRouter
-// ---------
-
-// Reduce the boilerplate code of handling route events
-// and then calling a single method on another object.
-// Have your routers configured to call the method on
-// your object, directly.
-//
-// Configure an AppRouter with `appRoutes`.
-//
-// App routers can only take one `controller` object.
-// It is recommended that you divide your controller
-// objects in to smaller pieces of related functionality
-// and have multiple routers / controllers, instead of
-// just one giant router and controller.
-//
-// You can also add standard routes to an AppRouter.
-
-Marionette.AppRouter = Backbone.Router.extend({
-
- constructor: function(options){
- Backbone.Router.prototype.constructor.apply(this, arguments);
-
- this.options = options || {};
-
- var appRoutes = Marionette.getOption(this, "appRoutes");
- var controller = this._getController();
- this.processAppRoutes(controller, appRoutes);
- },
-
- // Similar to route method on a Backbone Router but
- // method is called on the controller
- appRoute: function(route, methodName) {
- var controller = this._getController();
- this._addAppRoute(controller, route, methodName);
- },
-
- // Internal method to process the `appRoutes` for the
- // router, and turn them in to routes that trigger the
- // specified method on the specified `controller`.
- processAppRoutes: function(controller, appRoutes) {
- if (!appRoutes){ return; }
-
- var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes
-
- _.each(routeNames, function(route) {
- this._addAppRoute(controller, route, appRoutes[route]);
- }, this);
- },
-
- _getController: function(){
- return Marionette.getOption(this, "controller");
- },
-
- _addAppRoute: function(controller, route, methodName){
- var method = controller[methodName];
-
- if (!method) {
- throwError("Method '" + methodName + "' was not found on the controller");
+
+ // Class to build handlers for `triggers` on behaviors
+ // for views
+ function BehaviorTriggersBuilder(view, behaviors) {
+ this._view = view;
+ this._behaviors = behaviors;
+ this._triggers = {};
}
-
- this.route(route, methodName, _.bind(method, controller));
- }
-});
-
-
-// Application
-// -----------
-
-// Contain and manage the composite application as a whole.
-// Stores and starts up `Region` objects, includes an
-// event aggregator as `app.vent`
-Marionette.Application = function(options){
- this._initRegionManager();
- this._initCallbacks = new Marionette.Callbacks();
- this.vent = new Backbone.Wreqr.EventAggregator();
- this.commands = new Backbone.Wreqr.Commands();
- this.reqres = new Backbone.Wreqr.RequestResponse();
- this.submodules = {};
-
- _.extend(this, options);
-
- this.triggerMethod = Marionette.triggerMethod;
-};
-
-_.extend(Marionette.Application.prototype, Backbone.Events, {
- // Command execution, facilitated by Backbone.Wreqr.Commands
- execute: function(){
- this.commands.execute.apply(this.commands, arguments);
- },
-
- // Request/response, facilitated by Backbone.Wreqr.RequestResponse
- request: function(){
- return this.reqres.request.apply(this.reqres, arguments);
- },
-
- // Add an initializer that is either run at when the `start`
- // method is called, or run immediately if added after `start`
- // has already been called.
- addInitializer: function(initializer){
- this._initCallbacks.add(initializer);
- },
-
- // kick off all of the application's processes.
- // initializes all of the regions that have been added
- // to the app, and runs all of the initializer functions
- start: function(options){
- this.triggerMethod("initialize:before", options);
- this._initCallbacks.run(options, this);
- this.triggerMethod("initialize:after", options);
-
- this.triggerMethod("start", options);
- },
-
- // Add regions to your app.
- // Accepts a hash of named strings or Region objects
- // addRegions({something: "#someRegion"})
- // addRegions({something: Region.extend({el: "#someRegion"}) });
- addRegions: function(regions){
- return this._regionManager.addRegions(regions);
- },
-
- // Close all regions in the app, without removing them
- closeRegions: function(){
- this._regionManager.closeRegions();
- },
-
- // Removes a region from your app, by name
- // Accepts the regions name
- // removeRegion('myRegion')
- removeRegion: function(region) {
- this._regionManager.removeRegion(region);
- },
-
- // Provides alternative access to regions
- // Accepts the region name
- // getRegion('main')
- getRegion: function(region) {
- return this._regionManager.get(region);
- },
-
- // Create a module, attached to the application
- module: function(moduleNames, moduleDefinition){
-
- // Overwrite the module class if the user specifies one
- var ModuleClass = Marionette.Module.getClass(moduleDefinition);
-
- // slice the args, and add this application object as the
- // first argument of the array
- var args = slice.call(arguments);
- args.unshift(this);
-
- // see the Marionette.Module object for more information
- return ModuleClass.create.apply(ModuleClass, args);
- },
-
- // Internal method to set up the region manager
- _initRegionManager: function(){
- this._regionManager = new Marionette.RegionManager();
-
- this.listenTo(this._regionManager, "region:add", function(name, region){
- this[name] = region;
- });
-
- this.listenTo(this._regionManager, "region:remove", function(name, region){
- delete this[name];
- });
- }
-});
-
-// Copy the `extend` function used by Backbone's classes
-Marionette.Application.extend = Marionette.extend;
-
-// Module
-// ------
-
-// A simple module system, used to create privacy and encapsulation in
-// Marionette applications
-Marionette.Module = function(moduleName, app, options){
- this.moduleName = moduleName;
- this.options = _.extend({}, this.options, options);
- this.initialize = options.initialize || this.initialize;
-
- // store sub-modules
- this.submodules = {};
-
- this._setupInitializersAndFinalizers();
-
- // store the configuration for this module
- this.app = app;
- this.startWithParent = true;
-
- this.triggerMethod = Marionette.triggerMethod;
-
- if (_.isFunction(this.initialize)){
- this.initialize(this.options, moduleName, app);
- }
-};
-
-Marionette.Module.extend = Marionette.extend;
-
-// Extend the Module prototype with events / listenTo, so that the module
-// can be used as an event aggregator or pub/sub.
-_.extend(Marionette.Module.prototype, Backbone.Events, {
-
- // Initialize is an empty function by default. Override it with your own
- // initialization logic when extending Marionette.Module.
- initialize: function(){},
-
- // Initializer for a specific module. Initializers are run when the
- // module's `start` method is called.
- addInitializer: function(callback){
- this._initializerCallbacks.add(callback);
- },
-
- // Finalizers are run when a module is stopped. They are used to teardown
- // and finalize any variables, references, events and other code that the
- // module had set up.
- addFinalizer: function(callback){
- this._finalizerCallbacks.add(callback);
- },
-
- // Start the module, and run all of its initializers
- start: function(options){
- // Prevent re-starting a module that is already started
- if (this._isInitialized){ return; }
-
- // start the sub-modules (depth-first hierarchy)
- _.each(this.submodules, function(mod){
- // check to see if we should start the sub-module with this parent
- if (mod.startWithParent){
- mod.start(options);
+
+ _.extend(BehaviorTriggersBuilder.prototype, {
+ // Main method to build the triggers hash with event keys and handlers
+ buildBehaviorTriggers: function() {
+ _.each(this._behaviors, this._buildTriggerHandlersForBehavior, this);
+ return this._triggers;
+ },
+
+ // Internal method to build all trigger handlers for a given behavior
+ _buildTriggerHandlersForBehavior: function(behavior, i) {
+ var triggersHash = _.clone(_.result(behavior, 'triggers')) || {};
+
+ triggersHash = Marionette.normalizeUIKeys(triggersHash, getBehaviorsUI(behavior));
+
+ _.each(triggersHash, _.bind(this._setHandlerForBehavior, this, behavior, i));
+ },
+
+ // Internal method to create and assign the trigger handler for a given
+ // behavior
+ _setHandlerForBehavior: function(behavior, i, eventName, trigger) {
+ // Unique identifier for the `this._triggers` hash
+ var triggerKey = trigger.replace(/^\S+/, function(triggerName) {
+ return triggerName + '.' + 'behaviortriggers' + i;
+ });
+
+ this._triggers[triggerKey] = this._view._buildViewTrigger(eventName);
}
});
-
- // run the callbacks to "start" the current module
- this.triggerMethod("before:start", options);
-
- this._initializerCallbacks.run(options, this);
- this._isInitialized = true;
-
- this.triggerMethod("start", options);
- },
-
- // Stop this module by running its finalizers and then stop all of
- // the sub-modules for this module
- stop: function(){
- // if we are not initialized, don't bother finalizing
- if (!this._isInitialized){ return; }
- this._isInitialized = false;
-
- Marionette.triggerMethod.call(this, "before:stop");
-
- // stop the sub-modules; depth-first, to make sure the
- // sub-modules are stopped / finalized before parents
- _.each(this.submodules, function(mod){ mod.stop(); });
-
- // run the finalizers
- this._finalizerCallbacks.run(undefined,this);
-
- // reset the initializers and finalizers
- this._initializerCallbacks.reset();
- this._finalizerCallbacks.reset();
-
- Marionette.triggerMethod.call(this, "stop");
- },
-
- // Configure the module with a definition function and any custom args
- // that are to be passed in to the definition function
- addDefinition: function(moduleDefinition, customArgs){
- this._runModuleDefinition(moduleDefinition, customArgs);
- },
-
- // Internal method: run the module definition function with the correct
- // arguments
- _runModuleDefinition: function(definition, customArgs){
- if (!definition){ return; }
-
- // build the correct list of arguments for the module definition
- var args = _.flatten([
- this,
- this.app,
- Backbone,
- Marionette,
- Marionette.$, _,
- customArgs
- ]);
-
- definition.apply(this, args);
- },
-
- // Internal method: set up new copies of initializers and finalizers.
- // Calling this method will wipe out all existing initializers and
- // finalizers.
- _setupInitializersAndFinalizers: function(){
- this._initializerCallbacks = new Marionette.Callbacks();
- this._finalizerCallbacks = new Marionette.Callbacks();
- }
-});
-
-// Type methods to create modules
-_.extend(Marionette.Module, {
-
- // Create a module, hanging off the app parameter as the parent object.
- create: function(app, moduleNames, moduleDefinition){
- var module = app;
-
- // get the custom args passed in after the module definition and
- // get rid of the module name and definition function
- var customArgs = slice.call(arguments);
- customArgs.splice(0, 3);
-
- // split the module names and get the length
- moduleNames = moduleNames.split(".");
- var length = moduleNames.length;
-
- // store the module definition for the last module in the chain
- var moduleDefinitions = [];
- moduleDefinitions[length-1] = moduleDefinition;
-
- // Loop through all the parts of the module definition
- _.each(moduleNames, function(moduleName, i){
- var parentModule = module;
- module = this._getModule(parentModule, moduleName, app, moduleDefinition);
- this._addModuleDefinition(parentModule, module, moduleDefinitions[i], customArgs);
- }, this);
-
- // Return the last module in the definition chain
- return module;
- },
-
- _getModule: function(parentModule, moduleName, app, def, args){
- var options = _.extend({}, def);
- var ModuleClass = this.getClass(def);
-
- // Get an existing module of this name if we have one
- var module = parentModule[moduleName];
-
- if (!module){
- // Create a new module if we don't have one
- module = new ModuleClass(moduleName, app, options);
- parentModule[moduleName] = module;
- // store the module on the parent
- parentModule.submodules[moduleName] = module;
- }
-
- return module;
- },
-
- getClass: function(moduleDefinition) {
- var ModuleClass = Marionette.Module;
-
- if (!moduleDefinition) {
- return ModuleClass;
- }
-
- if (moduleDefinition.prototype instanceof ModuleClass) {
- return moduleDefinition;
+
+ function getBehaviorsUI(behavior) {
+ return behavior._uiBindings || behavior.ui;
}
+
+ return Behaviors;
+
+ })(Marionette, _);
+
- return moduleDefinition.moduleClass || ModuleClass;
- },
-
- _addModuleDefinition: function(parentModule, module, def, args){
- var fn;
- var startWithParent;
-
- if (_.isFunction(def)){
- // if a function is supplied for the module definition
- fn = def;
- startWithParent = true;
-
- } else if (_.isObject(def)){
- // if an object is supplied
- fn = def.define;
- startWithParent = !_.isUndefined(def.startWithParent) ? def.startWithParent : true;
-
- } else {
- // if nothing is supplied
- startWithParent = true;
+ // App Router
+ // ----------
+
+ // Reduce the boilerplate code of handling route events
+ // and then calling a single method on another object.
+ // Have your routers configured to call the method on
+ // your object, directly.
+ //
+ // Configure an AppRouter with `appRoutes`.
+ //
+ // App routers can only take one `controller` object.
+ // It is recommended that you divide your controller
+ // objects in to smaller pieces of related functionality
+ // and have multiple routers / controllers, instead of
+ // just one giant router and controller.
+ //
+ // You can also add standard routes to an AppRouter.
+
+ Marionette.AppRouter = Backbone.Router.extend({
+
+ constructor: function(options) {
+ this.options = options || {};
+
+ Backbone.Router.apply(this, arguments);
+
+ var appRoutes = this.getOption('appRoutes');
+ var controller = this._getController();
+ this.processAppRoutes(controller, appRoutes);
+ this.on('route', this._processOnRoute, this);
+ },
+
+ // Similar to route method on a Backbone Router but
+ // method is called on the controller
+ appRoute: function(route, methodName) {
+ var controller = this._getController();
+ this._addAppRoute(controller, route, methodName);
+ },
+
+ // process the route event and trigger the onRoute
+ // method call, if it exists
+ _processOnRoute: function(routeName, routeArgs) {
+ // make sure an onRoute before trying to call it
+ if (_.isFunction(this.onRoute)) {
+ // find the path that matches the current route
+ var routePath = _.invert(this.getOption('appRoutes'))[routeName];
+ this.onRoute(routeName, routePath, routeArgs);
+ }
+ },
+
+ // Internal method to process the `appRoutes` for the
+ // router, and turn them in to routes that trigger the
+ // specified method on the specified `controller`.
+ processAppRoutes: function(controller, appRoutes) {
+ if (!appRoutes) { return; }
+
+ var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes
+
+ _.each(routeNames, function(route) {
+ this._addAppRoute(controller, route, appRoutes[route]);
+ }, this);
+ },
+
+ _getController: function() {
+ return this.getOption('controller');
+ },
+
+ _addAppRoute: function(controller, route, methodName) {
+ var method = controller[methodName];
+
+ if (!method) {
+ throw new Marionette.Error('Method "' + methodName + '" was not found on the controller');
+ }
+
+ this.route(route, methodName, _.bind(method, controller));
+ },
+
+ mergeOptions: Marionette.mergeOptions,
+
+ // Proxy `getOption` to enable getting options from this or this.options by name.
+ getOption: Marionette.proxyGetOption,
+
+ triggerMethod: Marionette.triggerMethod,
+
+ bindEntityEvents: Marionette.proxyBindEntityEvents,
+
+ unbindEntityEvents: Marionette.proxyUnbindEntityEvents
+ });
+
+ // Application
+ // -----------
+
+ // Contain and manage the composite application as a whole.
+ // Stores and starts up `Region` objects, includes an
+ // event aggregator as `app.vent`
+ Marionette.Application = Marionette.Object.extend({
+ constructor: function(options) {
+ this._initializeRegions(options);
+ this._initCallbacks = new Marionette.Callbacks();
+ this.submodules = {};
+ _.extend(this, options);
+ this._initChannel();
+ Marionette.Object.call(this, options);
+ },
+
+ // Command execution, facilitated by Backbone.Wreqr.Commands
+ execute: function() {
+ this.commands.execute.apply(this.commands, arguments);
+ },
+
+ // Request/response, facilitated by Backbone.Wreqr.RequestResponse
+ request: function() {
+ return this.reqres.request.apply(this.reqres, arguments);
+ },
+
+ // Add an initializer that is either run at when the `start`
+ // method is called, or run immediately if added after `start`
+ // has already been called.
+ addInitializer: function(initializer) {
+ this._initCallbacks.add(initializer);
+ },
+
+ // kick off all of the application's processes.
+ // initializes all of the regions that have been added
+ // to the app, and runs all of the initializer functions
+ start: function(options) {
+ this.triggerMethod('before:start', options);
+ this._initCallbacks.run(options, this);
+ this.triggerMethod('start', options);
+ },
+
+ // Add regions to your app.
+ // Accepts a hash of named strings or Region objects
+ // addRegions({something: "#someRegion"})
+ // addRegions({something: Region.extend({el: "#someRegion"}) });
+ addRegions: function(regions) {
+ return this._regionManager.addRegions(regions);
+ },
+
+ // Empty all regions in the app, without removing them
+ emptyRegions: function() {
+ return this._regionManager.emptyRegions();
+ },
+
+ // Removes a region from your app, by name
+ // Accepts the regions name
+ // removeRegion('myRegion')
+ removeRegion: function(region) {
+ return this._regionManager.removeRegion(region);
+ },
+
+ // Provides alternative access to regions
+ // Accepts the region name
+ // getRegion('main')
+ getRegion: function(region) {
+ return this._regionManager.get(region);
+ },
+
+ // Get all the regions from the region manager
+ getRegions: function() {
+ return this._regionManager.getRegions();
+ },
+
+ // Create a module, attached to the application
+ module: function(moduleNames, moduleDefinition) {
+
+ // Overwrite the module class if the user specifies one
+ var ModuleClass = Marionette.Module.getClass(moduleDefinition);
+
+ var args = _.toArray(arguments);
+ args.unshift(this);
+
+ // see the Marionette.Module object for more information
+ return ModuleClass.create.apply(ModuleClass, args);
+ },
+
+ // Enable easy overriding of the default `RegionManager`
+ // for customized region interactions and business-specific
+ // view logic for better control over single regions.
+ getRegionManager: function() {
+ return new Marionette.RegionManager();
+ },
+
+ // Internal method to initialize the regions that have been defined in a
+ // `regions` attribute on the application instance
+ _initializeRegions: function(options) {
+ var regions = _.isFunction(this.regions) ? this.regions(options) : this.regions || {};
+
+ this._initRegionManager();
+
+ // Enable users to define `regions` in instance options.
+ var optionRegions = Marionette.getOption(options, 'regions');
+
+ // Enable region options to be a function
+ if (_.isFunction(optionRegions)) {
+ optionRegions = optionRegions.call(this, options);
+ }
+
+ // Overwrite current regions with those passed in options
+ _.extend(regions, optionRegions);
+
+ this.addRegions(regions);
+
+ return this;
+ },
+
+ // Internal method to set up the region manager
+ _initRegionManager: function() {
+ this._regionManager = this.getRegionManager();
+ this._regionManager._parent = this;
+
+ this.listenTo(this._regionManager, 'before:add:region', function() {
+ Marionette._triggerMethod(this, 'before:add:region', arguments);
+ });
+
+ this.listenTo(this._regionManager, 'add:region', function(name, region) {
+ this[name] = region;
+ Marionette._triggerMethod(this, 'add:region', arguments);
+ });
+
+ this.listenTo(this._regionManager, 'before:remove:region', function() {
+ Marionette._triggerMethod(this, 'before:remove:region', arguments);
+ });
+
+ this.listenTo(this._regionManager, 'remove:region', function(name) {
+ delete this[name];
+ Marionette._triggerMethod(this, 'remove:region', arguments);
+ });
+ },
+
+ // Internal method to setup the Wreqr.radio channel
+ _initChannel: function() {
+ this.channelName = _.result(this, 'channelName') || 'global';
+ this.channel = _.result(this, 'channel') || Backbone.Wreqr.radio.channel(this.channelName);
+ this.vent = _.result(this, 'vent') || this.channel.vent;
+ this.commands = _.result(this, 'commands') || this.channel.commands;
+ this.reqres = _.result(this, 'reqres') || this.channel.reqres;
}
-
- // add module definition if needed
- if (fn){
- module.addDefinition(fn, args);
+ });
+
+ /* jshint maxparams: 9 */
+
+ // Module
+ // ------
+
+ // A simple module system, used to create privacy and encapsulation in
+ // Marionette applications
+ Marionette.Module = function(moduleName, app, options) {
+ this.moduleName = moduleName;
+ this.options = _.extend({}, this.options, options);
+ // Allow for a user to overide the initialize
+ // for a given module instance.
+ this.initialize = options.initialize || this.initialize;
+
+ // Set up an internal store for sub-modules.
+ this.submodules = {};
+
+ this._setupInitializersAndFinalizers();
+
+ // Set an internal reference to the app
+ // within a module.
+ this.app = app;
+
+ if (_.isFunction(this.initialize)) {
+ this.initialize(moduleName, app, this.options);
}
-
- // `and` the two together, ensuring a single `false` will prevent it
- // from starting with the parent
- module.startWithParent = module.startWithParent && startWithParent;
-
- // setup auto-start if needed
- if (module.startWithParent && !module.startWithParentIsConfigured){
-
- // only configure this once
+ };
+
+ Marionette.Module.extend = Marionette.extend;
+
+ // Extend the Module prototype with events / listenTo, so that the module
+ // can be used as an event aggregator or pub/sub.
+ _.extend(Marionette.Module.prototype, Backbone.Events, {
+
+ // By default modules start with their parents.
+ startWithParent: true,
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic when extending Marionette.Module.
+ initialize: function() {},
+
+ // Initializer for a specific module. Initializers are run when the
+ // module's `start` method is called.
+ addInitializer: function(callback) {
+ this._initializerCallbacks.add(callback);
+ },
+
+ // Finalizers are run when a module is stopped. They are used to teardown
+ // and finalize any variables, references, events and other code that the
+ // module had set up.
+ addFinalizer: function(callback) {
+ this._finalizerCallbacks.add(callback);
+ },
+
+ // Start the module, and run all of its initializers
+ start: function(options) {
+ // Prevent re-starting a module that is already started
+ if (this._isInitialized) { return; }
+
+ // start the sub-modules (depth-first hierarchy)
+ _.each(this.submodules, function(mod) {
+ // check to see if we should start the sub-module with this parent
+ if (mod.startWithParent) {
+ mod.start(options);
+ }
+ });
+
+ // run the callbacks to "start" the current module
+ this.triggerMethod('before:start', options);
+
+ this._initializerCallbacks.run(options, this);
+ this._isInitialized = true;
+
+ this.triggerMethod('start', options);
+ },
+
+ // Stop this module by running its finalizers and then stop all of
+ // the sub-modules for this module
+ stop: function() {
+ // if we are not initialized, don't bother finalizing
+ if (!this._isInitialized) { return; }
+ this._isInitialized = false;
+
+ this.triggerMethod('before:stop');
+
+ // stop the sub-modules; depth-first, to make sure the
+ // sub-modules are stopped / finalized before parents
+ _.invoke(this.submodules, 'stop');
+
+ // run the finalizers
+ this._finalizerCallbacks.run(undefined, this);
+
+ // reset the initializers and finalizers
+ this._initializerCallbacks.reset();
+ this._finalizerCallbacks.reset();
+
+ this.triggerMethod('stop');
+ },
+
+ // Configure the module with a definition function and any custom args
+ // that are to be passed in to the definition function
+ addDefinition: function(moduleDefinition, customArgs) {
+ this._runModuleDefinition(moduleDefinition, customArgs);
+ },
+
+ // Internal method: run the module definition function with the correct
+ // arguments
+ _runModuleDefinition: function(definition, customArgs) {
+ // If there is no definition short circut the method.
+ if (!definition) { return; }
+
+ // build the correct list of arguments for the module definition
+ var args = _.flatten([
+ this,
+ this.app,
+ Backbone,
+ Marionette,
+ Backbone.$, _,
+ customArgs
+ ]);
+
+ definition.apply(this, args);
+ },
+
+ // Internal method: set up new copies of initializers and finalizers.
+ // Calling this method will wipe out all existing initializers and
+ // finalizers.
+ _setupInitializersAndFinalizers: function() {
+ this._initializerCallbacks = new Marionette.Callbacks();
+ this._finalizerCallbacks = new Marionette.Callbacks();
+ },
+
+ // import the `triggerMethod` to trigger events with corresponding
+ // methods if the method exists
+ triggerMethod: Marionette.triggerMethod
+ });
+
+ // Class methods to create modules
+ _.extend(Marionette.Module, {
+
+ // Create a module, hanging off the app parameter as the parent object.
+ create: function(app, moduleNames, moduleDefinition) {
+ var module = app;
+
+ // get the custom args passed in after the module definition and
+ // get rid of the module name and definition function
+ var customArgs = _.drop(arguments, 3);
+
+ // Split the module names and get the number of submodules.
+ // i.e. an example module name of `Doge.Wow.Amaze` would
+ // then have the potential for 3 module definitions.
+ moduleNames = moduleNames.split('.');
+ var length = moduleNames.length;
+
+ // store the module definition for the last module in the chain
+ var moduleDefinitions = [];
+ moduleDefinitions[length - 1] = moduleDefinition;
+
+ // Loop through all the parts of the module definition
+ _.each(moduleNames, function(moduleName, i) {
+ var parentModule = module;
+ module = this._getModule(parentModule, moduleName, app, moduleDefinition);
+ this._addModuleDefinition(parentModule, module, moduleDefinitions[i], customArgs);
+ }, this);
+
+ // Return the last module in the definition chain
+ return module;
+ },
+
+ _getModule: function(parentModule, moduleName, app, def, args) {
+ var options = _.extend({}, def);
+ var ModuleClass = this.getClass(def);
+
+ // Get an existing module of this name if we have one
+ var module = parentModule[moduleName];
+
+ if (!module) {
+ // Create a new module if we don't have one
+ module = new ModuleClass(moduleName, app, options);
+ parentModule[moduleName] = module;
+ // store the module on the parent
+ parentModule.submodules[moduleName] = module;
+ }
+
+ return module;
+ },
+
+ // ## Module Classes
+ //
+ // Module classes can be used as an alternative to the define pattern.
+ // The extend function of a Module is identical to the extend functions
+ // on other Backbone and Marionette classes.
+ // This allows module lifecyle events like `onStart` and `onStop` to be called directly.
+ getClass: function(moduleDefinition) {
+ var ModuleClass = Marionette.Module;
+
+ if (!moduleDefinition) {
+ return ModuleClass;
+ }
+
+ // If all of the module's functionality is defined inside its class,
+ // then the class can be passed in directly. `MyApp.module("Foo", FooModule)`.
+ if (moduleDefinition.prototype instanceof ModuleClass) {
+ return moduleDefinition;
+ }
+
+ return moduleDefinition.moduleClass || ModuleClass;
+ },
+
+ // Add the module definition and add a startWithParent initializer function.
+ // This is complicated because module definitions are heavily overloaded
+ // and support an anonymous function, module class, or options object
+ _addModuleDefinition: function(parentModule, module, def, args) {
+ var fn = this._getDefine(def);
+ var startWithParent = this._getStartWithParent(def, module);
+
+ if (fn) {
+ module.addDefinition(fn, args);
+ }
+
+ this._addStartWithParent(parentModule, module, startWithParent);
+ },
+
+ _getStartWithParent: function(def, module) {
+ var swp;
+
+ if (_.isFunction(def) && (def.prototype instanceof Marionette.Module)) {
+ swp = module.constructor.prototype.startWithParent;
+ return _.isUndefined(swp) ? true : swp;
+ }
+
+ if (_.isObject(def)) {
+ swp = def.startWithParent;
+ return _.isUndefined(swp) ? true : swp;
+ }
+
+ return true;
+ },
+
+ _getDefine: function(def) {
+ if (_.isFunction(def) && !(def.prototype instanceof Marionette.Module)) {
+ return def;
+ }
+
+ if (_.isObject(def)) {
+ return def.define;
+ }
+
+ return null;
+ },
+
+ _addStartWithParent: function(parentModule, module, startWithParent) {
+ module.startWithParent = module.startWithParent && startWithParent;
+
+ if (!module.startWithParent || !!module.startWithParentIsConfigured) {
+ return;
+ }
+
module.startWithParentIsConfigured = true;
-
- // add the module initializer config
- parentModule.addInitializer(function(options){
- if (module.startWithParent){
+
+ parentModule.addInitializer(function(options) {
+ if (module.startWithParent) {
module.start(options);
}
});
-
}
-
- }
-});
-
-
+ });
+
return Marionette;
-})(this, Backbone, _);
+}));