Javascript Backbone.js-实施“最佳实践”;即时;搜寻
在我的主干应用程序中,有几个地方我希望对集合进行即时搜索,但我很难想出实现它的最佳方法 下面是一个快速实现。请记住,我的收藏可能包含200多种型号Javascript Backbone.js-实施“最佳实践”;即时;搜寻,javascript,backbone.js,Javascript,Backbone.js,在我的主干应用程序中,有几个地方我希望对集合进行即时搜索,但我很难想出实现它的最佳方法 下面是一个快速实现。请记住,我的收藏可能包含200多种型号 var CollectionView = Backbone.View.extend({ template: $('#template').html(), initialize: function() { this.collection = new Backbone.Collection([ { first: 'Joh
var CollectionView = Backbone.View.extend({
template: $('#template').html(),
initialize: function() {
this.collection = new Backbone.Collection([
{ first: 'John', last: 'Doe' },
{ first: 'Mary', last: 'Jane' },
{ first: 'Billy', last: 'Bob' },
{ first: 'Dexter', last: 'Morgan' },
{ first: 'Walter', last: 'White' },
{ first: 'Billy', last: 'Bobby' }
]);
this.collection.on('add', this.addOne, this);
this.render();
},
events: {
'keyup .search': 'search',
},
// Returns array subset of models that match search.
search: function(e) {
var search = this.$('.search').val().toLowerCase();
this.$('tbody').empty(); // is this creating ghost views?
_.each(this.collection.filter(function(model) {
return _.some(
model.values(),
function(value) {
return ~value.toLowerCase().indexOf(search);
});
}), $.proxy(this.addOne, this));
},
addOne: function(model) {
var view = new RowView({ model: model });
this.$('tbody').append(view.render().el);
},
render: function() {
$('#insert').replaceWith(this.$el.html(this.template));
this.collection.each(this.addOne, this);
}
});
每个模型都有一个小视图
var RowView = Backbone.View.extend({
tagName: 'tr',
events: {
'click': 'click'
},
click: function () {
// Set element to active
this.$el.addClass('selected').siblings().removeClass('selected');
// Some detail view will listen for this.
App.trigger('model:view', this.model);
},
render: function() {
this.$el.html('<td>' + this.model.get('first') + '</td><td>' + this.model.get('last') + '</td>');
return this;
}
});
new CollectionView;
var RowView=Backbone.View.extend({
标记名:“tr”,
活动:{
“单击”:“单击”
},
单击:函数(){
//将元素设置为活动
此.el.addClass('selected').sides().removeClass('selected');
//一些细节视图将对此进行监听。
App.trigger('model:view',this.model);
},
render:function(){
this.$el.html(“”+this.model.get('first')+“”+this.model.get('last')+“”);
归还这个;
}
});
新集合视图;
问题1
在每次按下键时,我过滤集合,清空tbody
,并呈现结果,从而为每个模型创建一个新视图。我刚刚创建了幻影视图,是吗?是否最好适当地破坏每个视图?或者我应该尝试管理我的RowView
s。。。只创建一次每一个,并在其中循环以仅渲染结果?我的CollectionView
中可能有一个数组?清空t正文
后,行视图
是否仍保留其el
,还是现在为空,需要重新渲染
问题2,型号选择
您会注意到我在我的行视图中触发了一个自定义事件。我想在某个地方有一个细节视图来处理该事件并显示整个模型。搜索列表时,如果我选择的模型仍保留在搜索结果中,我希望保留该状态并使其保留在详细视图中。一旦它不再出现在我的结果中,我将清空细节视图。所以我肯定需要管理一组视图,对吗?我考虑了一个双链接结构,其中每个视图指向它的模型,每个模型指向它的视图。。。但是,如果我将来要在我的模型上实现一个单例工厂,我不能把它强加在模型上/
那么,管理这些视图的最佳方法是什么?与您的CollectionView关联的集合必须与您正在渲染的内容一致,否则您将遇到问题。您不必手动清空车身。您应该更新集合,并在CollectionView中侦听集合发出的事件,并使用该事件更新视图。在搜索方法中,您应该只更新集合,而不更新CollectionView。这是在CollectionView初始化方法中实现它的一种方法:
initialize: function() {
//...
this.listenTo(this.collection, "reset", this.render);
this.listenTo(this.collection, "add", this.addOne);
}
在搜索方法中,只需重置集合,视图就会自动渲染:
search: function() {
this.collection.reset(filteredModels);
}
其中,filteredModels
是匹配搜索查询的模型数组。请注意,一旦使用筛选的模型重置集合,您将无法访问搜索之前原始存在的其他模型。您应该拥有对包含所有模型的主集合的引用,而不考虑搜索。此“主集合”本身不与视图相关联,但您可以使用此主集合上的过滤器,并使用过滤后的模型更新视图的集合
至于第二个问题,您不应该从模型中引用视图。模型应该完全独立于视图-只有视图应该引用模型
您的addOne
方法可以这样重构以获得更好的性能(始终使用$el附加子视图):
我玩弄你的问题时有点得意忘形
首先,我将创建一个专用集合来保存过滤后的模型,并创建一个“状态模型”来处理搜索。比如说,
var Filter = Backbone.Model.extend({
defaults: {
what: '', // the textual search
where: 'all' // I added a scope to the search
},
initialize: function(opts) {
// the source collection
this.collection = opts.collection;
// the filtered models
this.filtered = new Backbone.Collection(opts.collection.models);
//listening to changes on the filter
this.on('change:what change:where', this.filter);
},
//recalculate the state of the filtered list
filter: function() {
var what = this.get('what').trim(),
where = this.get('where'),
lookin = (where==='all') ? ['first', 'last'] : where,
models;
if (what==='') {
models = this.collection.models;
} else {
models = this.collection.filter(function(model) {
return _.some(_.values(model.pick(lookin)), function(value) {
return ~value.toLowerCase().indexOf(what);
});
});
}
// let's reset the filtered collection with the appropriate models
this.filtered.reset(models);
}
});
它将被实例化为
var people = new Backbone.Collection([
{first: 'John', last: 'Doe'},
{first: 'Mary', last: 'Jane'},
{first: 'Billy', last: 'Bob'},
{first: 'Dexter', last: 'Morgan'},
{first: 'Walter', last: 'White'},
{first: 'Billy', last: 'Bobby'}
]);
var flt = new Filter({collection: people});
然后,我将为列表和输入字段创建单独的视图:更易于维护和移动
var BaseView = Backbone.View.extend({
render:function() {
var html, $oldel = this.$el, $newel;
html = this.html();
$newel=$(html);
this.setElement($newel);
$oldel.replaceWith($newel);
return this;
}
});
var CollectionView = BaseView.extend({
initialize: function(opts) {
// I like to pass the templates in the options
this.template = opts.template;
// listen to the filtered collection and rerender
this.listenTo(this.collection, 'reset', this.render);
},
html: function() {
return this.template({
models: this.collection.toJSON()
});
}
});
var FormView = Backbone.View.extend({
events: {
// throttled to limit the updates
'keyup input[name="what"]': _.throttle(function(e) {
this.model.set('what', e.currentTarget.value);
}, 200),
'click input[name="where"]': function(e) {
this.model.set('where', e.currentTarget.value);
}
}
});
BaseView
允许就地更改DOM,有关详细信息,请参阅
这些实例看起来像
var inputView = new FormView({
el: 'form',
model: flt
});
var listView = new CollectionView({
template: _.template($('#template-list').html()),
collection: flt.filtered
});
$('#content').append(listView.render().el);
以及现阶段的搜索演示
最后,我将修改CollectionView
以移植渲染函数中的行视图,类似于
var ItemView = BaseView.extend({
events: {
'click': function() {
console.log(this.model.get('first'));
}
}
});
var CollectionView = BaseView.extend({
initialize: function(opts) {
this.template = opts.template;
this.listenTo(this.collection, 'reset', this.render);
},
html: function() {
var models = this.collection.map(function (model) {
return _.extend(model.toJSON(), {
cid: model.cid
});
});
return this.template({models: models});
},
render: function() {
BaseView.prototype.render.call(this);
var coll = this.collection;
this.$('[data-cid]').each(function(ix, el) {
new ItemView({
el: el,
model: coll.get($(el).data('cid'))
});
});
return this;
}
});
又是一把小提琴谢谢你的回复。第一个问题。。。this.listenTo(this.collection,“reset”,this.render)
和this.collection.on(“reset”,this.render,this)
之间有什么区别?第二个问题。我喜欢你说的关于主收藏和收藏视图收藏。。。但是您没有处理子视图。使用每个addOne
创建新的RowView
s可以吗?@savinger它们基本上完成了相同的任务—它们侦听事件。但是,this.listenTo
将侦听与视图相关联,而this.collection.on
将侦听与集合相关联。这似乎没有什么区别,但请记住,如果使用此.collection.on
,即使删除视图,该集合仍将继续侦听,这可能会导致内存泄漏并大大降低应用程序的速度。另一方面,如果使用this.listenTo
,则在删除视图后,它将不会侦听事件。不,不会出现内存泄漏。但是,您可能需要考虑使用<代码> SET>代码>而不是<代码> RESET>代码>。当重置清除现有模型并渲染新模型时,set将执行“智能合并”,该“智能合并”将“更新”视图并仅删除需要删除的模型-这可能更有效。作为旁注,如果您知道要调用this.$('something')
那么多次可能会更好地缓存该选择器,对吗?比如var$foo=this.$('foo')代码>见此:谢谢,这非常有帮助。我真的很喜欢你所做的
var ItemView = BaseView.extend({
events: {
'click': function() {
console.log(this.model.get('first'));
}
}
});
var CollectionView = BaseView.extend({
initialize: function(opts) {
this.template = opts.template;
this.listenTo(this.collection, 'reset', this.render);
},
html: function() {
var models = this.collection.map(function (model) {
return _.extend(model.toJSON(), {
cid: model.cid
});
});
return this.template({models: models});
},
render: function() {
BaseView.prototype.render.call(this);
var coll = this.collection;
this.$('[data-cid]').each(function(ix, el) {
new ItemView({
el: el,
model: coll.get($(el).data('cid'))
});
});
return this;
}
});