In my Backbone view, i am setting tagname, classname, temp value.. all are works fine apart from classname..
how do i set the classname.. or what is the mistake on my code..
define(["singleton","listCollection","listModel"],function(singleton,collection,listModel){
singleton.view = Backbone.View.extend({
tagName :'article',
className :'indBoard',
projectName : true,
template0 : _.template($('#listTemplate').html()),
template1 : _.template($('#boardTemplate').html()),
initialize :function(options){
this.template = this['template'+options.tempNo];
this.tagName = options.tagName;
//i am changing to 'li' works
this.className = options.cName;
//changing to new class name not working
console.log(options.cName);//consoles new class name properly
this.projectName = options.subTempNo == 0 ?true:false;
//condition as well works..
},
render:function(){
var temp = this.template;
this.$el.html(temp(this.model.toJSON()));
return this;
}
});
return singleton.view;
});
If you set options.className when you create your view instance instead of options.cName, you don't need to try to set it in initialize like that (same for tagName).
Try something like this instead:
var view = new singleton.view({className: 'someClass'});
className is one of the special options that Backbone looks for during the view creation.
From Backbone source:
// List of view options to be merged as properties.
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
Actually, I think the reason tagName is working for you is because it is being merged in by Backbone, and not because you are setting it in initialize.
Related
When I set idAtribute of a model I expect it to be used in url. Is it the right thing to expect or am I missing something? I see that backbone model sees 'id' attribute on itself and uses it to build url, but I explicitly told it to use '_id' property. It doesn't look like the right behavior to me.
var model = new Backbone.Model();
model.set('id', 1); // if you remove this line everything works properly
model.set('_id', 2);
model.idAttribute = '_id';
model.urlRoot = 'models';
model.url(); // returns "models/1"
I would do that this way.
var someModel = Backbone.Model.extend({
initialize: function(attr,params) {
this.id = attr.id;
this.url= "somerooturl/"+params.id;
}
});
var model = new someModel(attr,params);
model.set ...
here's the situation:
When page is opened for the first time, it already has prepared DOM by server(php).
If user has javascript turned on, then i want to convert my page to web app(or whatever you call it).
As soon as Javascript is initialized, Backbone fetches collection from server.
The problem is, that some of these fetched items are already on page.
Now how can i mark those items which already are in the DOM?
And how can i tie them up with the Backbone view?
Hooking up a Backbone.View to an existing DOM element is simple:
//get a referent to the view element
var $el = $("#foo");
//initialize new view
var view = new FooView({el:$el});
The view now handles the #foo element's events, and all the other View goodness. You shouldn't call view.render. If you do, it will re-render the view to the element. This means that you can't define any necessary code in the render method.
As to how to find out which elements are already in the DOM, and how to find the corresponding element for each view - that's a bit more complicated to answer without knowing exactly how your data and html looks like. As a general advice, consider using data-* attributes to match up the elements.
Let's say you have a DOM tree:
<ul id="list">
<li data-id="1">...</li>
<li data-id="2">...</li>
<li data-id="5">...</li>
</ul>
You could bind/render a model to the container like so:
var view;
//container element
var $list = $("ul#list");
//find item node by "data-id" attribute
var $item = $list.find("li[data-id='" + model.id+ "']");
if($item.length) {
//element was in the DOM, so bind to it
view = new View( {el:$item, model:model} );
} else {
//not in DOM, so create it
view = new View( {model:model} ).render();
$list.append(view.el);
}
Ok, i managed to do that like so:
var Collection = Backbone.Collection.extend({...});
var ItemView = Backbone.View.extend({...});
var ItemsView = Backbone.View.extend({
initialize: function () {
var that = this,
coll = new Collection;
coll.fetch({ success: function () {
that.collection = coll;
that.render();
}});
},
render: function () {
this.collection.each(this.addOne, this);
},
addOne: function (model) {
var selector = '#i'+model.get("id");
if( $(selector).length ) {
//If we are here, then element is already in the DOM
var itemView = new ItemView({ 'model': model, 'el': selector, 'existsInDom': true });
} else {
var itemView = new ItemView({ 'model':model });
}
}
});
I render a collection of models, which is associated with a collectionView where when rendered each element in the collection has its own 'itemview' which is rendered.
When a collection is sorted and the listView re-rendered based on the new order, I had been creating a totally new view for each item, and as I was not clearing up any previous instances of views associated with that model, I believe zombies being left around.
So initially rendering my collection I would do...
render : function() {
$(this.el).empty();
var content = this.template.tmpl({});
$(this.el).html(content);
sortingView.el ='#sorting-container';
var els = [];
_.each(this.collection.models, function(model){
var view = new TB_BB.RequestItemView({model : model});
els.push(view.render().el);
});
$('#request-list').append(els);
sortingView.render();
return this;
}
So whenever the render function was called a second/third etc time, I had not cleared up the TB_BB.RequestItemView (hence the zombies)
To overcome this I tried to add some simple caching in the collections view, so that instead of creating a new itemview if it had already been created use that instead. My code
initialize : function(){
_.bindAll(this,"render");
this.collection.bind("add", this.render);
this.collection.bind("remove", this.render);
this.template = $("#request-list-template");
this.views = {};
},
events : {
"change #sort" : "changesort",
"click #add-offer" : "addoffer",
"click #alert-button" : "addalert"
},
render : function() {
$(this.el).empty();
outerthis = this;
var content = this.template.tmpl({});
$(this.el).html(content);
sortingView.el ='#sorting-container';
var els = [];
_.each(this.collection.models, function(model){
var view;
if(outerthis.views[model.get('id')]) {
view = outerthis.views[model.get('id')];
} else {
view = new TB_BB.RequestItemView({model : model});
outerthis.views[model.get('id')] = view;
}
});
$('#request-list').append(els);
sortingView.render();
return this;
}
So this works in so much as the views are re-used - however what I have noticed is that if I use a cached view (e.g. the collection has been sorted and the render function finds a cached view) that all of the events on the sub itemview stop working? why is that?
Also could anyone suggest a better way of doing this?
You can use delegateEvents ( http://documentcloud.github.com/backbone/#View-delegateEvents ) to bind the events again.
As OlliM mentioned the reason is because the events are bound to the dom element, but instead of rebinding the element you can also just detach them instead of removing them (detach keeps the event bindings http://api.jquery.com/detach/)
something like
var $sortContainer = $('#sorting-container');
$('li', $sortContainer).detach();
And then just reattach the element
$cnt.append(view.el);
I would also consider using a document fragment while rebuilding/sorting the list and then attaching appending that instead.
When I add collection to the view like this:
var View = new MyCollectionView({ collection: new MyCollection() });
everything is okey. I can use this collection in initialize method (for binding events, for example). But how can I add another one?
I can't do this way:
var View = new MyCollectionView({
collection: new MyCollection(),
secondCollection: new MySecondCollection()
});
From the fine manual:
constructor / initialize new View([options])
There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, tagName, attributes and events.
So, if you create a view like this:
new View({collection: c})
then Backbone will automatically assign c to the view's this.collection. But if you create the view like this:
new View({collection: c, secondCollection: c2})
then inside the View's constructor:
initialize: function(options) {
// this.collection will be 'c' from above
// options.secondCollection will be 'c2'
}
So you can do this:
var View = new MyCollectionView({
collection: new MyCollection(),
secondCollection: new MySecondCollection()
});
provided that your MyCollectionView has an initialize method that knows to pull the secondCollection out of its options argument.
Open your JavaScript console and have a look at what this does:
var V = Backbone.View.extend({
initialize: function(options) {
var c1 = options.collection;
var c2 = options.secondCollection;
console.log(this.collection);
console.log(c1);
console.log(c2);
}
});
var view = new V({collection: 1, secondCollection: 2});
Demo: http://jsfiddle.net/ambiguous/XyeSD/
I have several views that have common code I'd like to abstract into a custom Backbone.View class. Is there any best practices for doing this?
is a good pattern to do something like this? :
// Base Grid view
var GridView = Backbone.View.extend({
initialize : function(){
//common view init code ..
//do the plug in overrides
if (options.addHandler)
this.addHandler = options.addHandler;
if (options.events)
//?? extend default events or override?
this.events = $.extend(this.events, options.events);
},
addHandler : function() {
//defaulthandler this code can be overridden
});
});
// in another object create some views from the GridView base
....
var overrides = { events:"xxx yyy", el: ulElement addHandler: myAddFunction }
var UserList = GridView.extend(overrides)
var userList = new UserList(users, options);
....
var coursesOverrides : {addHandler: ...}
var coursesOptions: {el: courseElement, ...}
var CourseList = GridView.extend(coursesOverrides)
var courseList= new CourseList (courses, coursesOptions)
// along the same lines maybe there's an abstraction for toolbar views
var ClassToolbarView = ToolbarBase.extend(toolOverrides)
var classtoolbar = new ClassToolbarView(actions, toolbaropts)
Any pointers to good examples of extending a View for refactoring common view code is appreciated.
First, I don't see the options being passed in your initializer(), so that's a bug.
Secondly, the .extend() method is inherited:
var GridView = Backbone.View.extend({ ... })
var GridViewWithNewFunctionsAndEvents = GridView.extend({ ... })
And you can replace or extend GridView's functionality, and call new GridViewWithNewFunctionsAndEvents() and get the extra functionality in a new object you need, just like you extend the Backbone stock View class.
If you need to extend the initializer, you can do this to call the initializer on the superclass:
var GridViewWithNewFunctionsAndEvents = GridView.extend({
initializer: function(options) {
GridView.prototype.initializer.call(this, options);
/* Your stuff goes here */
}
});