I have a dynamic table created as following
app.View.FriendRequestTableView = Backbone.View.extend({
tagName: 'table',
className: 'table table-hover',
initialize : function(options) {
this.options = options;
this.render();
},
render: function() {
$(this.el).empty();
this.options.collection.each(function(user){
var row = new app.View.FriendRequestRowView({model:user});
$(this.el).append(row.el);
});
return $(this.el);
}
});
I checked and i saw the Row is properly constructed but the following line is not working
$(this.el).append(row.el);
I also have seen only table element is created but the table is empty.
Any ideas???
The reference to "this" inside the iterator function for this.options.collections.each is probably a reference to window if I'm not mistaken. Creating an explicit reference to your view that the iterator function can use should solve your problem.
$(this.el).empty();
var _this = this;
this.options.collection.each(function(user){
var row = new app.View.FriendRequestRowView({model:user});
$(_this.el).append(row.el);
});
return $(this.el);
Related
I plan to create a Backbone.js View for rendering a list of items. The list will grow. It seems to me that for performance reasons I should not empty and rebuild the DOM with the items. Does this make sense? How would you approach this?
My typical setup for this kind of thing is to use a define a Backbone.View for the ul or whatever containing element I have and bind that to a collection. Then define another Backbone.View to render a single list item. Instances of this view have a one-to-one relationship with the models in the collection.
Collections have different events that correspond nicely with the different types of DOM operations you would need to perform to reflect them. I map them like this:
sync = [render entire list on first fetch]
add = this.$append(...)
remove = [find corresponding list view item].remove()
OK, so this code is just hammered down from memory and not tested, but you get the idea:
var collection = new Backbone.Collection({
model: Backbone.Model,
url: '/some/api/endpoint'
});
var Li = Backbone.View.extend({
tagName: 'li',
initialize: function(){
this.render();
},
render: function(){
var template = _.template($('li-template').html());
this.el = template(model.toJSON());
}
});
var Ul = Backbone.View.extend({
collection:collection,
el: 'ul',
initialize: function(){
this.listItems = [];
this.collection.on('sync', this.addAll);
this.collection.on('add', this.addOne);
this.collection.on('remove', this.removeOne);
},
addAll: function(){
var frag = document.createDocumentFragment();
this.collection.forEach(function(model){
var view = new Li({model: model});
frag.appendChild(view.el);
this.listItems.push(view);
});
this.el.appendChild(frag);
},
addOne: function(model){
var view = new Li({model: model});
this.el.appendChild(view.el);
this.listItems.push(view);
},
removeOne: function(model){
for (var i = 0, num = this.listItems.length, item; i < num; i++) {
view = this.listItems[i];
if (view.model.cid === model.cid) {
this.el.removeChild(view.el);
this.listItems.splice(i, 1);
}
}
}
});
Previous Title :- Search functionality in Backbone.js.
I am working on Backbone.js to achieve search functionality( when user types something in search textbox, the list should get filetered as per search criteria).
For this I have my controller as :-
var departments = backbone.Collection.extend({
model: departmentModel,
url: '/MyController/GetDepartments',
comparator: function (department) {
return department.get('name');
},
initialize: function () {
this.selected = [];
},
search: function (letters) {
if (letters == "") return this;
var pattern = new RegExp(letters, "gi");
return _(this.filter(function (data) { //without wrapping the filter with the underscore function, the filter does not return a collection
return pattern.test(data.get("Name"));
}));
}
});
return departments;
In my Backbone View I have my keyup event defined on search textbox
"keyup #searchDepartments": "searchDepartments",
Where my searchDepartments is -
searchDepartments: function (e) {
var letters = $(e.currentTarget).val();
var searchResult = this.collection.search(letters);
var collection = new departmentCollection(searchResult.toArray());
// debugger;
this.renderFileteredData(collection);
//$(e.currentTarget).val(letters);
}
Lastly, renderFilteredData is simple
renderFileteredData: function (departments) {
$(this.$el).html(this.template(departments.toJSON()));
return this;
},
Now the issue is- when ever I am typing any text the list gets filtered out but the search text goes off. Whats wrong?
EDIT:- As per suggestions, now I have created a different view as below for search text box:-
function ($, _, backbone) {
'use strict';
var searchDepartmentView = backbone.View.extend({
el: "#search-container",
tagName: 'div',
template: Handlebars.templates.DepartmentSearchView,
initialize: function () {
//this.render();
return this;
},
render: function () {
console.log('in search departmnet view render method');
// $(this.$el).html(this.template());// I am trying to render this template.. But its not working
this.$el.html('in search departmnet view render method');//Passing dummy value to it
return this;
}
});
return searchDepartmentView;
});
and in my main view :-
render: function () {
// _.bindAll(this, "search");
this.innerView = new departmentSearchView();
$(this.$el).html(this.template(this.collection.toJSON()));
this.$el.find("#search-container").html(this.innerView.render().$el); //Tried approach 1 as suggested
// $(this.$el).html(this.innerView.render().el);//Tried approach 2 as suggested
return this;
},
Now, I am not getting my search text in search departmnet view render method displayed in main view
Finally, I found a way to do it. Thank to the great answer by Kelvin Peel in how-to-handle-initializing-and-rendering-subviews-in-backbone-js
Thanks to YuruiRayZhang for his suggestions!
Code snippet from there, which describes a detailed process of nested views :-
var ParentView = Backbone.View.extend({
el: "#parent",
initialize: function() {
// Step 1, (init) I want to know anytime the name changes
this.model.bind("change:first_name", this.subRender, this);
this.model.bind("change:last_name", this.subRender, this);
// Step 2, render my own view
this.render();
// Step 3/4, create the children and assign elements
this.infoView = new InfoView({el: "#info", model: this.model});
this.phoneListView = new PhoneListView({el: "#phone_numbers", model: this.model});
},
render: function() {
// Render my template
this.$el.html(this.template());
// Render the name
this.subRender();
},
subRender: function() {
// Set our name block and only our name block
$("#name").html("Person: " + this.model.first_name + " " + this.model.last_name);
}
});
Also, an extended tutorial for the above mentioned process backbone-js-subview-rendering-trick/
I have a view called layerPanel that is using screenData model. Now on model.set i get update event from model itself, but its not working on view.
MODEL
var screenData = Backbone.Model.extend({
initialize : function() {
_.bindAll(this,"update");
this.bind('change:vdata', this.update);
},
update: function() {
var obj = this.vdata;
alert("update");
},
vdata:[{id : 0, title : "Welcome to Persieve 0"}]
});
VIEW
var layerPanel = Backbone.View.extend({
el: "#allLayers",
model: new screenData(),
initialize: function() {
this.render();
this.model.bind('change:vdata', this.render);
},
render: function() {
this.template = _.template(LayersTpl, {splitData: this.model.vdata});
this.$el.html(this.template);
return this;
}
});
Here is how I set values in Model.
screendata = new screenData;
var obj = screendata.vdata;
obj[obj.length] = {id : $(".bullet").length, title : "Welcome to Persieve"};
var tempData = [];
for ( var index=0; index<obj.length; index++ ) {
if ( obj[index]) {
tempData.push( obj );
}
}
obj = tempData;
screendata.set({vdata:[obj]});
The event should fire. But your render wont work as the 'this' context needs setting.
try:
this.model.bind('change:vdata', this.render, this);
or even better, use listenTo and the context is implicit (+ you can clean up easily this.remove())
Edit. From the edit you made above, I can see that you are creating a new screendata instance. The binding you created is for a different instance model: new screenData() .
You must reference the binded object and set it if you want the event to trigger.
If all the model setting happens in the actual model. Call this.set({vdata:[obj]});
I've been using backbone for quite some time now, and each time I get dynamic lists of views that have their own events and behaviors, I wonder how should they be stored. I've two ways and my thoughts on them are..
Store views internally in another view. This requires overhead in proper filtering, but is sort-of independent from DOM + might have better memory usage
Just generate views, put them in DOM and trigger events of views with jquery, like $('#someviewid').trigger('somecustomfunction'); - easier to write and access but dependencies are harder to see and I'm not certain that view/model gets deleted if I just remove DOM node
What would you recommend?
So here is expanded second example, where new views are just appended to internal html and storyViews themselves are forgotten. But If I want to access specific view from this list, I would have to use DOM attributes, like id or data and then trigger view functions with jquery accessors
Devclub.Views.StoriesList = Backbone.View.extend({
initialize: function () {
this.collection.bind('reset', this.reset, this);
this.collection.fetch();
},
reset: function (modelList) {
$(this.el).html('');
var me = this;
$.each(modelList.models, function (i, model) {
me.add(model);
});
},
add: function (model) {
var contact_model = new Devclub.Models.Story(model);
var view = new Devclub.Views.Story({
model: contact_model
});
var storyView = view.render().el;
$(this.el).append(storyView);
}
});
In contrast, I could instead store same view list in an array and iterate over it if I want to call some view methods directly
I think you should keep a reference of the child views. Here is an example from the book by Addy Osmani.
Backbone.View.prototype.close = function() {
if (this.onClose) {
this.onClose();
}
this.remove(); };
NewView = Backbone.View.extend({
initialize: function() {
this.childViews = [];
},
renderChildren: function(item) {
var itemView = new NewChildView({ model: item });
$(this.el).prepend(itemView.render());
this.childViews.push(itemView);
},
onClose: function() {
_(this.childViews).each(function(view) {
view.close();
});
} });
NewChildView = Backbone.View.extend({
tagName: 'li',
render: function() {
} });
I am having problems including an additional model into my view which is based on a collection. I have a list of comments which is created by a parent view. Its need that I have the current user name when rendering the comments to show delete button and to highlight if its his own comment. The problem is now that I cant access in CommentListView the model session, so this.session in initialize or a call from a method like addAllCommentTo list is undefinied. What I am doing wrong here? I thought its easily possible to add another object to an view appart from the model.
CommentListView:
window.CommentListView = Backbone.View.extend({
el: $("#comments"),
initialize: function () {
this.model.bind('reset', this.addAllCommentToList, this);
this.model.bind('add', this.refresh, this);
this.model.bind('remove', this.refresh, this);
},
refresh: function(){
this.model.fetch();
},
addCommentToList : function(comment) {
console.log("comment added to dom");
//need to check why el reference is not working
$("#comments").append(new CommentView({model:comment, sessionModel: this.session}).render().el);
},
addAllCommentToList: function() {
$("#comments").empty();
this.model.each(this.addCommentToList);
}
});
Call from parent list in initialize method:
window.UserDetailView = Backbone.View.extend({
events: {
"click #newComment" : "newComment"
},
initialize: function () {
this.commentText = $("#commentText", this.el);
new CommentListView({ model: this.model.comments, session: this.model.session });
new LikeView({ model: this.model.like });
this.model.comments.fetch();
},
newComment : function() {
console.log("new comment");
this.model.comments.create(
new Comment({text: this.commentText.val()}), {wait: true}
);
this.commentText.val('');
}
});
Model:
window.UserDetail = Backbone.Model.extend({
urlRoot:'/api/details',
initialize:function () {
this.comments = new Comments();
this.comments.url = "/api/details/" + this.id + "/comments";
this.like = new Like();
this.like.url = "/api/details/" + this.id + "/likes";
this.session = new Session();
},
...
});
I see one problem, but can there be others.
You are initializing the View like this:
new CommentListView({ model: this.model.comments, session: this.model.session });
And you are expecting into your View to have a reference like this this.session.
This is not gonna happen. All the hash you send to the View constructor will be stored into this.options, from Backbone View constructor docs:
When creating a new View, the options you pass are attached to the view as this.options, for future reference.
So you can start changing this line:
$("#comments").append(new CommentView({model:comment, sessionModel: this.session}).render().el);
by this other:
$("#comments").append(new CommentView({model:comment, sessionModel: this.options.session}).render().el);
Try and tell us.
Updated
Also change this line:
this.model.each(this.addCommentToList);
by this:
this.model.each(this.addCommentToList, this);
The second argument is the context, in other words: what you want to be this in the called handler.