在同一页面中容纳多个 Backbone 视图、模型和集合
Accommodate multiple Backbone views, models and collections in the same page
我很难在同一页上显示两个 models/collections。
<body>
<div id="mainContainer">
<div id="contentContainer"></div>
</div>
<div id="mainContainer2">
<div id="contentContainer2"></div>
</div>
<script id="list_container_tpl" type="text/template">
<div class="grid_5 listContainer">
<div class="box">
<h2 class="box_head grad_colour">Your tasks</h2>
<div class="sorting">Show: <select id="taskSorting"><option value="0">All Current</option><option value="1">Completed</option></select>
<input class="search round_all" id="searchTask" type="text" value="">
</div>
<div class="block">
<ul id="taskList" class="list"></ul>
</div>
</div>
</div>
</script>
<script id="list2_container_tpl" type="text/template">
<div class="grid_5 mylistContainer">
<div class="box">
<h2 class="box_head grad_colour">Your facets</h2>
<div class="sorting">
%{--Show: <select id="taskSorting"><option value="0">All Current</option><option value="1">Completed</option></select>--}%
<input class="search round_all" id="searchFacet" type="text" value="">
</div>
<div class="block">
<ul id="facetList" class="list"></ul>
</div>
</div>
</div>
</script>
<script id="task_item_tpl" type="text/template">
<li class="task">
<h4 class="name searchItem">{{ name }}</h4>
</li>
</script>
<script id="facet_item_tpl" type="text/template">
<li class="facet">
<h5 class="label searchItem">{{ label }}</h5>
</li>
</script>
<script>
var myapp = {
model: {},
view: {},
collection: {},
router: {}
};
var facetsSearch = {
model: {},
view: {},
collection: {},
router: {}
};
</script>
<script src="underscore-min.js"></script>
<script src="handlebars.min.js"></script>
<script src="backbone-min.js"></script>
<script>
/* avoid */
_.templateSettings = {
interpolate: /\{\{(.+?)\}\}/g
};
</script>
<script>
// model.tasks.js
myapp.model.Tasks = Backbone.Model.extend({
default:{
completed: 0,
name: ""
},
//url:"/js/libs/fixtures/task.json"
});
var tasks1 = new myapp.model.Tasks({
completed: 0,
name: "Clear dishes"
}
);
var tasks2 = new myapp.model.Tasks({
completed: 1,
name: "Get out the trash"
}
);
var tasks3 = new myapp.model.Tasks({
completed: 0,
name: "Do the laundry"
}
);
var tasks4 = new myapp.model.Tasks({
completed: 1,
name: "Vacuuming the carpet"
}
);
// collection.tasks.js
myapp.collection.Tasks = Backbone.Collection.extend({
currentStatus : function(status){
return _(this.filter(function(data) {
return data.get("completed") == status;
}));
},
search : function(letters){
if (letters == "") return this;
var pattern = new RegExp(letters,"gi");
return _(this.filter(function(data) {
return pattern.test(data.get("name"));
}));
}
});
myapp.collection.tasks = new myapp.collection.Tasks([tasks1, tasks2, tasks3, tasks4]);
// route.tasks.js
myapp.router.Tasks = Backbone.Router.extend({
routes: {
"": "list",
},
list: function(){
this.listContainerView = new myapp.view.TasksContainer({
collection: myapp.collection.tasks
});
$("#contentContainer").append(this.listContainerView.render().el);
this.listContainerView.sorts()
}
});
myapp.router.tasks = new myapp.router.Tasks;
<!-- render views -->
myapp.view.TasksContainer = Backbone.View.extend({
events: {
"keyup #searchTask" : "search",
"change #taskSorting" : "sorts"
},
render: function(data) {
$(this.el).html(this.template);
return this;
},
renderList : function(tasks){
$("#taskList").html("");
tasks.each(function(task){
var view = new myapp.view.TasksItem({
model: task,
collection: this.collection
});
$("#taskList").append(view.render().el);
});
return this;
},
initialize : function(){
this.template = _.template($("#list_container_tpl").html());
this.collection.bind("reset", this.render, this);
},
search: function(e){
var letters = $("#searchTask").val();
this.renderList(this.collection.search(letters));
},
sorts: function(e){
var status = $("#taskSorting").find("option:selected").val();
if (status == "") status = 0;
this.renderList(this.collection.currentStatus(status));
}
});
myapp.view.TasksItem = Backbone.View.extend({
events: {},
render: function(data) {
$(this.el).html(this.template(this.model.toJSON()));
console.log(this.model.toJSON(), "became", this.template(this.model.toJSON()));
return this;
},
initialize : function(){
this.template = _.template($("#task_item_tpl").html());
}
});
</script>
<script>
// model.facets.js
facetsSearch.model.Facets = Backbone.Model.extend({
default: {
id: 0,
label: "",
facetValues: []
}
});
var facet1 = new facetsSearch.model.Facets({
id: 1,
label: "Organism",
facetValues: ["Orga1", "Orga2"]
});
var facet2 = new facetsSearch.model.Facets({
id: 2,
label: "Omics",
facetValues: ["Omics1", "Omics2"]
});
var facet3 = new facetsSearch.model.Facets({
id: 3,
label: "Publication Date",
facetValues: ["2016-11-01", "2016-11-02"]
});
// collection.facets.js
facetsSearch.collection.Facets = Backbone.Collection.extend({
search : function(letters){
if (letters == "") return this;
/**
* the g modifier is used to perform a global match (find all matches rather than stopping after the first match).
* Tip: To perform a global, case-insensitive search, use this modifier together with the "i" modifier.
*/
var pattern = new RegExp(letters, "gi");
return _(this.filter(function(data) {
return pattern.test(data.get("label"));
}));
}
});
facetsSearch.collection.facets = new facetsSearch.collection.Facets([facet1, facet2, facet3]);
// route.facets.js
facetsSearch.router.Facets = Backbone.Router.extend({
routes: {
"": "list",
},
list: function(){
this.mylistContainerView = new facetsSearch.view.FacetsContainer({
collection: facetsSearch.collection.facets
});
console.log("Facet collection: ", facetsSearch.collection.facets);
$("#contentContainer2").append(this.mylistContainerView.render().el);
this.mylistContainerView.sorts()
}
});
facetsSearch.router.Facets = new facetsSearch.router.Facets;
facetsSearch.view.FacetsContainer = Backbone.View.extend({
events: {
"keyup #searchFacet" : "search",
"change #facetSorting": "sorts"
},
render: function(data) {
$(this.el).html(this.template);
return this;
},
renderList : function(facets){
$("#facetList").html("");
facets.each(function(facet){
var view2 = new facetsSearch.view.FacetsItem({
model: facet,
collection: this.collection
});
$("#facetList").append(view2.render().el);
});
return this;
},
initialize : function(){
this.template = _.template($("#list2_container_tpl").html());
this.collection.bind("reset", this.render, this);
},
search: function(e){
var letters = $("#searchFacet").val();
this.renderList(this.collection.search(letters));
},
sorts: function(e){
/*var status = $("#taskSorting").find("option:selected").val();
if (status == "") status = 0;
this.renderList(this.collection.currentStatus(status));*/
}
});
facetsSearch.view.FacetsItem = Backbone.View.extend({
events: {},
render: function(data) {
$(this.el).html(this.template(this.model.toJSON()));
console.log(this.model.toJSON(), "became", this.template(this.model.toJSON()));
return this;
},
initialize : function(){
this.template = _.template($("#facet_item_tpl").html());
}
});
</script>
<script>
Backbone.history.start();
</script>
</body>
问题
在您的方面上方显示任务。我创建了两串代码来呈现 Tasks 和 Facets 但分别修改了变量名称。不幸的是,前者无法显示。
您制作了 2 个路由器,它们的路由都是空的。每个路由都在 Backbone.history
中注册,因此当 facets 路由器初始化时,它的路由会覆盖任务路由器路由。
如何拥有多个路由器?
对于您的应用程序范围,您应该首先制作一个路由器,并在 parent 视图中处理包含 2 个列表的页面。为该页面制作一种 Layout
视图,它将处理 2 个列表:
var Layout = Backbone.View.extend({
template: _.template($('#layout-template').html()),
// keep the selector strings in a simple object
selectors: {
tasks: '.task-container',
facets: '.facet-container',
},
initialize: function() {
this.view = {
tasks: new TaskList(),
facets: new FacetList()
};
},
render: function() {
this.$el.html(this.template());
var views = this.views,
selectors = this.selectors;
this.$(selectors.tasks).append(views.tasks.render().el);
this.$(selectors.facets).append(views.facets.render().el);
return this;
}
});
那么,只有一台路由器:
var Router = Backbone.Router.extend({
routes: {
"": "list",
},
list: function() {
this.listContainerView = new Layout();
$("body").html(this.listContainerView.render().el);
}
});
这不适用于您的代码 as-is,您必须自己将这些概念整合到您的应用程序中。
否则,如果你真的想要多个路由器,你必须明白它们不能共享一条路由,并且任何时刻只能触发一条路由。
当您有多个路由器时,每个路由器管理一个模块的路由。
var TaskRouter = Backbone.Router.extend({
routes: {
'tasks': 'taskList',
'tasks/:id': 'taskDetails'
}
// ...snip...
});
var FacetsRouter = Backbone.Router.extend({
routes: {
'facets': 'facetList',
'facets/:id': 'facetDetails'
}
// ...snip...
});
其他改进
编译模板一次
在扩展视图时编译一次模板比每次初始化新视图更有效。
myapp.view.TasksContainer = Backbone.View.extend({
// gets compiled once
template: _.template($("#list_container_tpl").html()),
initialize: function() {
// not here, as it gets compiled for each view
// this.template = _.template($("#list_container_tpl").html())
},
});
避免全局 jQuery 函数
- 避免
$(this.el)
转而使用 this.$el
。
- 避免使用
$('#divInTemplate')
,而使用 this.$('.divInTemplate')
。这是 a shortcut to this.$el.find
.
有关其他信息,请参阅 What is the difference between $el and el。
缓存 jQuery objects
每当您想 select 视图元素的 child 时,执行一次并将结果放入变量中。
render: function(data) {
this.$el.html(this.template);
// I like to namespace them inside an object.
this.elements = {
$list: this.$('.task-list'),
$search: this.$('.task-sorting')
};
// then werever you want to use them
this.elements.$list.toggleClass('active');
return this;
},
使用listenTo
避免使用 bind
/unbind
和 on
/off
/once
(别名)而使用 listenTo
/stopListening
/listenToOnce
.
listenTo
is an improved version of bind
解决内存泄漏问题。
this.collection.bind("reset", this.render, this);
// becomes
this.listenTo(this.collection, "reset", this.render);
将 objects 的数组传递给 collection
myapp.collection.Tasks = Backbone.Collection.extend({
model: myapp.model.Tasks
// ...snip...
});
myapp.collection.tasks = new myapp.collection.Tasks([{
completed: 0,
name: "Clear dishes"
}, {
completed: 1,
name: "Get out the trash"
}, {
completed: 0,
name: "Do the laundry"
}, {
completed: 1,
name: "Vacuuming the carpet"
}]);
这就够了,Backbone collection 处理剩下的事情。
正如 Emile 在他的详细 [=11=] 中提到的,您的问题是您正在使用相同的路由初始化多个路由器。
既然你似乎是从 Backbone 开始的,我会给你一个比创建复杂的布局(父)视图更简单的答案:
只需为特定路由设置一个处理程序,并在其中初始化您的两个视图。
它看起来像:
myapp.router = Backbone.Router.extend({
routes: {
"": "list",
},
list: function() {
this.listContainerView = new myapp.view.TasksContainer({
collection: myapp.collection.tasks
});
$("#contentContainer").append(this.listContainerView.render().el);
this.listContainerView.sorts(); //this can be done inside the view
this.mylistContainerView = new facetsSearch.view.FacetsContainer({
collection: facetsSearch.collection.facets
});
$("#contentContainer2").append(this.mylistContainerView.render().el);
this.mylistContainerView.sorts(); //this can be done inside the view
}
});
您只需在同一路由中初始化 2 个视图。
我很难在同一页上显示两个 models/collections。
<body>
<div id="mainContainer">
<div id="contentContainer"></div>
</div>
<div id="mainContainer2">
<div id="contentContainer2"></div>
</div>
<script id="list_container_tpl" type="text/template">
<div class="grid_5 listContainer">
<div class="box">
<h2 class="box_head grad_colour">Your tasks</h2>
<div class="sorting">Show: <select id="taskSorting"><option value="0">All Current</option><option value="1">Completed</option></select>
<input class="search round_all" id="searchTask" type="text" value="">
</div>
<div class="block">
<ul id="taskList" class="list"></ul>
</div>
</div>
</div>
</script>
<script id="list2_container_tpl" type="text/template">
<div class="grid_5 mylistContainer">
<div class="box">
<h2 class="box_head grad_colour">Your facets</h2>
<div class="sorting">
%{--Show: <select id="taskSorting"><option value="0">All Current</option><option value="1">Completed</option></select>--}%
<input class="search round_all" id="searchFacet" type="text" value="">
</div>
<div class="block">
<ul id="facetList" class="list"></ul>
</div>
</div>
</div>
</script>
<script id="task_item_tpl" type="text/template">
<li class="task">
<h4 class="name searchItem">{{ name }}</h4>
</li>
</script>
<script id="facet_item_tpl" type="text/template">
<li class="facet">
<h5 class="label searchItem">{{ label }}</h5>
</li>
</script>
<script>
var myapp = {
model: {},
view: {},
collection: {},
router: {}
};
var facetsSearch = {
model: {},
view: {},
collection: {},
router: {}
};
</script>
<script src="underscore-min.js"></script>
<script src="handlebars.min.js"></script>
<script src="backbone-min.js"></script>
<script>
/* avoid */
_.templateSettings = {
interpolate: /\{\{(.+?)\}\}/g
};
</script>
<script>
// model.tasks.js
myapp.model.Tasks = Backbone.Model.extend({
default:{
completed: 0,
name: ""
},
//url:"/js/libs/fixtures/task.json"
});
var tasks1 = new myapp.model.Tasks({
completed: 0,
name: "Clear dishes"
}
);
var tasks2 = new myapp.model.Tasks({
completed: 1,
name: "Get out the trash"
}
);
var tasks3 = new myapp.model.Tasks({
completed: 0,
name: "Do the laundry"
}
);
var tasks4 = new myapp.model.Tasks({
completed: 1,
name: "Vacuuming the carpet"
}
);
// collection.tasks.js
myapp.collection.Tasks = Backbone.Collection.extend({
currentStatus : function(status){
return _(this.filter(function(data) {
return data.get("completed") == status;
}));
},
search : function(letters){
if (letters == "") return this;
var pattern = new RegExp(letters,"gi");
return _(this.filter(function(data) {
return pattern.test(data.get("name"));
}));
}
});
myapp.collection.tasks = new myapp.collection.Tasks([tasks1, tasks2, tasks3, tasks4]);
// route.tasks.js
myapp.router.Tasks = Backbone.Router.extend({
routes: {
"": "list",
},
list: function(){
this.listContainerView = new myapp.view.TasksContainer({
collection: myapp.collection.tasks
});
$("#contentContainer").append(this.listContainerView.render().el);
this.listContainerView.sorts()
}
});
myapp.router.tasks = new myapp.router.Tasks;
<!-- render views -->
myapp.view.TasksContainer = Backbone.View.extend({
events: {
"keyup #searchTask" : "search",
"change #taskSorting" : "sorts"
},
render: function(data) {
$(this.el).html(this.template);
return this;
},
renderList : function(tasks){
$("#taskList").html("");
tasks.each(function(task){
var view = new myapp.view.TasksItem({
model: task,
collection: this.collection
});
$("#taskList").append(view.render().el);
});
return this;
},
initialize : function(){
this.template = _.template($("#list_container_tpl").html());
this.collection.bind("reset", this.render, this);
},
search: function(e){
var letters = $("#searchTask").val();
this.renderList(this.collection.search(letters));
},
sorts: function(e){
var status = $("#taskSorting").find("option:selected").val();
if (status == "") status = 0;
this.renderList(this.collection.currentStatus(status));
}
});
myapp.view.TasksItem = Backbone.View.extend({
events: {},
render: function(data) {
$(this.el).html(this.template(this.model.toJSON()));
console.log(this.model.toJSON(), "became", this.template(this.model.toJSON()));
return this;
},
initialize : function(){
this.template = _.template($("#task_item_tpl").html());
}
});
</script>
<script>
// model.facets.js
facetsSearch.model.Facets = Backbone.Model.extend({
default: {
id: 0,
label: "",
facetValues: []
}
});
var facet1 = new facetsSearch.model.Facets({
id: 1,
label: "Organism",
facetValues: ["Orga1", "Orga2"]
});
var facet2 = new facetsSearch.model.Facets({
id: 2,
label: "Omics",
facetValues: ["Omics1", "Omics2"]
});
var facet3 = new facetsSearch.model.Facets({
id: 3,
label: "Publication Date",
facetValues: ["2016-11-01", "2016-11-02"]
});
// collection.facets.js
facetsSearch.collection.Facets = Backbone.Collection.extend({
search : function(letters){
if (letters == "") return this;
/**
* the g modifier is used to perform a global match (find all matches rather than stopping after the first match).
* Tip: To perform a global, case-insensitive search, use this modifier together with the "i" modifier.
*/
var pattern = new RegExp(letters, "gi");
return _(this.filter(function(data) {
return pattern.test(data.get("label"));
}));
}
});
facetsSearch.collection.facets = new facetsSearch.collection.Facets([facet1, facet2, facet3]);
// route.facets.js
facetsSearch.router.Facets = Backbone.Router.extend({
routes: {
"": "list",
},
list: function(){
this.mylistContainerView = new facetsSearch.view.FacetsContainer({
collection: facetsSearch.collection.facets
});
console.log("Facet collection: ", facetsSearch.collection.facets);
$("#contentContainer2").append(this.mylistContainerView.render().el);
this.mylistContainerView.sorts()
}
});
facetsSearch.router.Facets = new facetsSearch.router.Facets;
facetsSearch.view.FacetsContainer = Backbone.View.extend({
events: {
"keyup #searchFacet" : "search",
"change #facetSorting": "sorts"
},
render: function(data) {
$(this.el).html(this.template);
return this;
},
renderList : function(facets){
$("#facetList").html("");
facets.each(function(facet){
var view2 = new facetsSearch.view.FacetsItem({
model: facet,
collection: this.collection
});
$("#facetList").append(view2.render().el);
});
return this;
},
initialize : function(){
this.template = _.template($("#list2_container_tpl").html());
this.collection.bind("reset", this.render, this);
},
search: function(e){
var letters = $("#searchFacet").val();
this.renderList(this.collection.search(letters));
},
sorts: function(e){
/*var status = $("#taskSorting").find("option:selected").val();
if (status == "") status = 0;
this.renderList(this.collection.currentStatus(status));*/
}
});
facetsSearch.view.FacetsItem = Backbone.View.extend({
events: {},
render: function(data) {
$(this.el).html(this.template(this.model.toJSON()));
console.log(this.model.toJSON(), "became", this.template(this.model.toJSON()));
return this;
},
initialize : function(){
this.template = _.template($("#facet_item_tpl").html());
}
});
</script>
<script>
Backbone.history.start();
</script>
</body>
问题
在您的方面上方显示任务。我创建了两串代码来呈现 Tasks 和 Facets 但分别修改了变量名称。不幸的是,前者无法显示。
您制作了 2 个路由器,它们的路由都是空的。每个路由都在 Backbone.history
中注册,因此当 facets 路由器初始化时,它的路由会覆盖任务路由器路由。
如何拥有多个路由器?
对于您的应用程序范围,您应该首先制作一个路由器,并在 parent 视图中处理包含 2 个列表的页面。为该页面制作一种 Layout
视图,它将处理 2 个列表:
var Layout = Backbone.View.extend({
template: _.template($('#layout-template').html()),
// keep the selector strings in a simple object
selectors: {
tasks: '.task-container',
facets: '.facet-container',
},
initialize: function() {
this.view = {
tasks: new TaskList(),
facets: new FacetList()
};
},
render: function() {
this.$el.html(this.template());
var views = this.views,
selectors = this.selectors;
this.$(selectors.tasks).append(views.tasks.render().el);
this.$(selectors.facets).append(views.facets.render().el);
return this;
}
});
那么,只有一台路由器:
var Router = Backbone.Router.extend({
routes: {
"": "list",
},
list: function() {
this.listContainerView = new Layout();
$("body").html(this.listContainerView.render().el);
}
});
这不适用于您的代码 as-is,您必须自己将这些概念整合到您的应用程序中。
否则,如果你真的想要多个路由器,你必须明白它们不能共享一条路由,并且任何时刻只能触发一条路由。
当您有多个路由器时,每个路由器管理一个模块的路由。
var TaskRouter = Backbone.Router.extend({
routes: {
'tasks': 'taskList',
'tasks/:id': 'taskDetails'
}
// ...snip...
});
var FacetsRouter = Backbone.Router.extend({
routes: {
'facets': 'facetList',
'facets/:id': 'facetDetails'
}
// ...snip...
});
其他改进
编译模板一次
在扩展视图时编译一次模板比每次初始化新视图更有效。
myapp.view.TasksContainer = Backbone.View.extend({
// gets compiled once
template: _.template($("#list_container_tpl").html()),
initialize: function() {
// not here, as it gets compiled for each view
// this.template = _.template($("#list_container_tpl").html())
},
});
避免全局 jQuery 函数
- 避免
$(this.el)
转而使用this.$el
。 - 避免使用
$('#divInTemplate')
,而使用this.$('.divInTemplate')
。这是 a shortcut tothis.$el.find
.
有关其他信息,请参阅 What is the difference between $el and el。
缓存 jQuery objects
每当您想 select 视图元素的 child 时,执行一次并将结果放入变量中。
render: function(data) {
this.$el.html(this.template);
// I like to namespace them inside an object.
this.elements = {
$list: this.$('.task-list'),
$search: this.$('.task-sorting')
};
// then werever you want to use them
this.elements.$list.toggleClass('active');
return this;
},
使用listenTo
避免使用 bind
/unbind
和 on
/off
/once
(别名)而使用 listenTo
/stopListening
/listenToOnce
.
listenTo
is an improved version of bind
解决内存泄漏问题。
this.collection.bind("reset", this.render, this);
// becomes
this.listenTo(this.collection, "reset", this.render);
将 objects 的数组传递给 collection
myapp.collection.Tasks = Backbone.Collection.extend({
model: myapp.model.Tasks
// ...snip...
});
myapp.collection.tasks = new myapp.collection.Tasks([{
completed: 0,
name: "Clear dishes"
}, {
completed: 1,
name: "Get out the trash"
}, {
completed: 0,
name: "Do the laundry"
}, {
completed: 1,
name: "Vacuuming the carpet"
}]);
这就够了,Backbone collection 处理剩下的事情。
正如 Emile 在他的详细 [=11=] 中提到的,您的问题是您正在使用相同的路由初始化多个路由器。
既然你似乎是从 Backbone 开始的,我会给你一个比创建复杂的布局(父)视图更简单的答案:
只需为特定路由设置一个处理程序,并在其中初始化您的两个视图。
它看起来像:
myapp.router = Backbone.Router.extend({
routes: {
"": "list",
},
list: function() {
this.listContainerView = new myapp.view.TasksContainer({
collection: myapp.collection.tasks
});
$("#contentContainer").append(this.listContainerView.render().el);
this.listContainerView.sorts(); //this can be done inside the view
this.mylistContainerView = new facetsSearch.view.FacetsContainer({
collection: facetsSearch.collection.facets
});
$("#contentContainer2").append(this.mylistContainerView.render().el);
this.mylistContainerView.sorts(); //this can be done inside the view
}
});
您只需在同一路由中初始化 2 个视图。