I think I am missing something very trivial here. I have created a Backbone View as follows(without extending Backbone.View):
var PlayersView = new Backbone.View({
initialize: function() {
this.render();
},
render: function() {
console.log("hello World");
}
});
But it doesn't log anything when I run this code. It doesn't work when I explicitly do: PlayersView.render(); as well.
But the following code works :
var PlayersView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function() {
console.log("hello World");
}
});
var playersview = new PlayersView();
The View constructor does not accept properties to add to the constructed object. It accepts a few special options like 'model', 'tagName', and so on. But the initialize(...) and render(...) properties in your first code snippet are effectively ignored.
The proper way to provide initialize, render, is to use Backbone.View.extend({...}).
From http://backbonejs.org/#View-extend
extend
Backbone.View.extend(properties, [classProperties]) Get started with
views by creating a custom view class. You'll want to override the
render function, specify your declarative events, and perhaps the
tagName, className, or id of the View's root element.
In other words, your first view's render method was not overridden by your custom render/initialize function.
When using extend you actually let Backbone understand you wish to use your own methods instead of the "default" ones.
Related
I have a large number of views (more than 50) which all extend from a single abstract base view, and therefore have a similar layout and many other features in common (event handlers, some custom methods and properties, etc).
I am presently using the initialize method of my base view to define the layout, which involves a subview, somewhat like the following:
App.BaseView = Backbone.View.extend({
//...
initialize: function() {
this.subView = new App.SubView();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.subView.$el = this.$('#subview-container');
this.subView.render();
return this;
},
//...
});
I find, however, that for many views which extend my base view I need to override the initialize method, which calls the base class initialize (I also extend my events hash quite often as well). I dislike having to do this, especially with so many views which extend the base class.
In this post from a Backbone Github repository issue Derick Bailey says:
I'm also not a fan of requiring extending classes to call super
methods for something like initialize. This method is so basic and
so fundamental to any object that extends from a Backbone construct.
It should never be implemented by a base type - a type that is built
with the explicit intent of never being instantiated directly, but
always extended from.
So on this model I should be able to have an initialize available for each inheriting view class. This makes perfect sense to me; but how can I then implement the kind of general layout I need for my inheriting views? In the constructor method?
I don't know if what I want might be possible out-of-the-box with something like Marionette or LayoutManager, both of which I've briefly looked at but never used, but I would much prefer doing this in vanilla Backbone at the moment.
Where to implement the initializing of the base class?
The way I like to do it is to initialize base classes in the constructor leaving the initialize function empty. It makes sense as the initialize function is only a convenience offered by Backbone and is really just an extension of the constructor.
In fact, Backbone do this a lot. Most if not all functions and properties that we override often are there only to be easily overriden.
Here's a quick list of such example:
Model: initialize, defaults, idAttribute, validate, urlRoot, parse, etc.
Collection: initialize, url, model, modelId, comparator, parse, etc.
View: initialize, attributes, el, template, render, events, className, id, etc.
These functions are left to the user to implement his own behaviors and to keep that useful pattern in a base class, they should be kept untouched and the base class behavior should be hooked into other functions if possible.
Sometimes, it can get difficult, like if you want to do something before initialize is called in the constructor, but after the element and other properties are set. In this case, overriding _ensureElement (line 1223) could be a possible hook.
_ensureElement: function() {
// hook before the element is set correctly
App.BaseView.__super__._ensureElement.apply(this, arguments);
// hook just before the initialize is called.
}
This was just an example, and there are almost always a way to get what you want in the base class without overriding a function that the child will also override.
Simple base class
If the base view is used in a small component and overriden by few child views, and mostly used by the same programmer, the following base view could be enough. Use Underscore's _.defaults and _.extend to merge the child class properties with the base class.
App.BaseView = Backbone.View.extend({
events: {
// default events
},
constructor: function(opt) {
var proto = App.BaseView.prototype;
// extend child class events with the default if not already defined
this.events = _.defaults({}, this.events, proto.events);
// Base class specifics
this.subView = new App.SubView();
// then Backbone's default behavior, which includes calling initialize.
Backbone.View.apply(this, arguments);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
// don't set `$el` directly, use `setElement`
this.subView
.setElement(this.$('#subview-container'))
.render();
// make it easy for child view to add their custom rendering.
this.onRender();
return this;
},
onRender: _.noop,
});
Don't set $el directly, use setElement instead.
Then a simple child view:
var ChildView = App.BaseView.extend({
events: {
// additional events
},
initialize: function(options) {
// specific initialization
},
onRender: function() {
// additional rendering
}
});
Advanced base class
If you're facing one of the following situation:
overriding render is problematic, don't like onRender
the events property (or any other property) is a function in the child or the parent or both
the programmer using the base class don't know about its specifics
Then it's possible to wrap the child properties implementation into new functions and Underscore's _.wrap function does just that.
App.BaseView = Backbone.View.extend({
// works with object literal or function returning an object.
events: function() {
return { /* base events */ };
},
// wrapping function
_events: function(events, parent) {
var parentEvents = App.BaseView.prototype.events;
if (_.isFunction(parentEvents)) parentEvents = parentEvents.call(this);
if (parent) return parentEvents; // useful if you want the parent events only
if (_.isFunction(events)) events = events.call(this);
return _.extend({}, parentEvents, events);
},
constructor: function(opt) {
var proto = App.BaseView.prototype;
// wrap the child properties into the parent, so they are always available.
this.events = _.wrap(this.events, this._events);
this.render = _.wrap(this.render, proto.render);
// Base class specifics
this.subView = new App.SubView();
// then Backbone's default behavior, which includes calling initialize.
Backbone.View.apply(this, arguments);
},
/**
* render now serves as both a wrapping function and the base render
*/
render: function(childRender) {
// base class implementation
// ....
// then call the child render
if (childRender) childRender.call(this);
return this
},
});
So the child looks completely normal while keeping the base class behaviors.
var ChildView = App.BaseView.extend({
events: function() {
return {
// additional events
};
},
initialize: function(options) {
// specific initialization
},
render: function() {
// additional rendering
}
});
Potential problems
This could become a problem if you want to override the base class behavior completely, you would need to cancel some of the base class behavior manually in the child class, and it could prove to be confusing.
Say you have a special child used once that need to override the render completely:
var SpecialChildView = App.BaseView.extend({
initialize: function(options) {
// Cancel the base class wrapping by putting
// the this class's prototype render back.
this.render = SpecialChildView.prototype.render;
// specific initialization
},
render: function() {
// new rendering
}
});
So it's not black and white and one should evaluate what is needed and what is going to be in the way and choose the right overriding technique.
I want to render marionette ItemView and append result html() to my div.
My code:
var ProjectItemView = Backbone.Marionette.ItemView.extend({
template: "#ProjectItem",
tagName: 'div',
initialize: function () {
this.model.on('change', this.life, this);
this.model.on('change', this.render, this);
},
life: function () {
alert(JSON.stringify(this.model));
}
});
var tmp = new Project({project_id: 1});
tmp.fetch();
$('#container').append(new ProjectItemView({model: tmp}).$el);
alert in life: function shows model right. It means fetch works fine.
The question is - how to get html as result of view.
I also tried $('#container').append(new ProjectItemView({model: tmp}).render().el);
The problem was with the REST service that I use to populate collections/models. It returns array that contains one element - not plain object directly.
You have to react to render event from marionette.
...
onRender : function() {
$('#container').append(this.$el);
}
...
new ProjectItemView({model: tmp});
tmp.fetch();
If you want to get uncoupled, fire a distinct app event from within your view handler to the outside world. Radio might be worth considering if you're not already.
I think your problem is only the order of operations. Try this:
$('#container').append((new ProjectItemView({model: tmp})).render().el);
The way you had it before, you were invoking .render() on the constructor. With the extra parenthesis above, .render() is called on the instance.
pass element to view:
new ProjectItemView({model: tmp, el:'#container'}).render();
I'm developing a webapplication with Resthub, so there is a backbone.js stack at the front-side. I need to call a method, everytime a new view (also all sorts of subviews) is rendered, to add some Twitter-Bootstrap specific stuff (help-popovers, kind of quick help, which get their options from a global json file, so the help-texts are easier to maintain).
As far as I know there isn't a backbone-built-in event which is fired every time a view is rendered.
So my question is: What is the easiest way to extend all views, so that they fire an event when the render method is (implicitly or explicitly) called. I want to extend all my views cause I don't want to trigger this event manually in all views I have, because it's error-prone and all developers has to remember that they've to trigger that event.
If you want to do something(fire an event or anything else) for all cases when the render method is called, the most straight forward way might be to update the render method in your copy of Backbone's source code (assuming you want the behavior across the project).
By default the render method just returns 'this'
render: function() {
return this;
},
If there is something you always want to do before render, you can add it within the render method
render: function() {
//add your extra code/call
return this;
},
Alternatively you can also override the prototype of Backbone.View function and update/create your own version(s) something like
_.extend(Backbone.View.prototype, Backbone.Events, {
render: function() {
console.log('This is a test');
return this;
}
});
var testView = Backbone.View.extend({
});
var testview = new testView();
testview.render(); //displays This is a test
//any view rendered will now have the console log
Taking this a step further, you can add your own version of render, calling it say 'myrender' and/or add your own event(s) say 'myevent' which can then be called before/after you call render/myrender
_.extend(Backbone.View.prototype, Backbone.Events, {
render: function() {
//console.log('This is a test');
this.mynewevent();
return this;
},
myrender: function() {
console.log('Pre-render work');
this.render();
},
mynewevent: function() {
console.log('New Event work');
}
});
var testView = Backbone.View.extend({
});
var testview = new testView();
//testview.render();
testview.myrender();
Underscore's extend is being used here and since Backbone has a dependency on Underscore, if you are using Backbone, Underscore should be available for you as well.
I couldn't find a question about this subject although I guess someone would have faced this problem before. Excuse me if I'm re-posting.
I have a problem with a component I'm creating. Imagine component implemented as a ItemView that defines certain elements from it's UI as a "View.ui" hash.
What happens if I want to create an specialized version of that component by subclassing it and add extra ui element definitions? What I'm getting here is that new definitions overwrite the parent ones, so the parent functionality breaks.
Is there any common solution to workaround this issue?
The only one that comes to my mind is tweaking the ".extend" functionality for base Marionette View class in order to treat specially these "ui" and "triggers" attributes when subclassing, using something more like a ".merge" instead of a "_.extend".
Any other thoughts?
thanks in advance,
You can use _.extend in the inheriting objects constructor to merge the hashes.
fiddle: http://jsfiddle.net/puleos/zkBCm/
var ParentView = Backbone.Marionette.ItemView.extend({
ui: {
link : 'a',
checkbox : "input[type=checkbox]"
}
});
var ChildView = ParentView.extend({
ui: {
list : 'ul'
},
constructor: function(options) {
var args = Array.prototype.slice.apply(arguments);
ParentView.prototype.constructor.apply(this, args);
this.ui = _.extend(this.ui, ParentView.prototype.ui);
}
});
var parentView = new ParentView();
var childView = new ChildView();
console.log('parent', parentView.ui); // Returns link & checkbox
console.log('child', childView.ui); // Returns ul, link & checkbox
Same as Scott's but I think it's a little more straight forward. I dont see the point in rewriting the constructor function. I was looking at constructor functions for each view type(ItemView, CompositeView, CollectionView), and they are each built differently. So just stick the last line in the initialize function, and it should work across all views.
So like this.
initialize: function () {
this.ui = _.extend(this.ui, ParentView.prototype.ui);
},
My solution is something more like this:
var ParentView = Backbone.Marionette.ItemView.extend({
events: {
'click #ui.link': 'onLinkClick'
},
ui: {
link : 'a',
checkbox : 'input[type=checkbox]'
},
onLinkClick: function () { ... }
});
var ChildView = ParentView.extend({
events: _.extend({
'click #ui.buttonStuff': 'onButtonStuffClick'
}, ParentView.prototype.events),
ui: _.extend({
buttonStuff : 'button[data-action=do_stuff]'
}, ParentView.prototype.ui),
onButtonStuffClick: function () { ... }
});
Yes, you have to do it for every subclass, but the syntax is fairly compact.
I do this all the time with Backbone. I haven't yet tried it with any Marionette projects, but I can't see any reason why it wouldn't work.
This is similar to minijag's solution, but more efficient. Rather than extending the hashes every time the class is instantiated, it extends them precisely once, when the class is defined.
I have a backbone app with a view structure that looks like the following - note that I've removed implementations, models, collections, etc. for brevity:
NewsListView = Backbone.View.extend({
el: $('li#newspane'),
// This is what I would like to be able to do
// events: { 'filtered': 'reset' }
initialize: function() {
_.bindAll(this);
},
render: function() {
},
reset: function(){
}
});
FilterView = Backbone.View.extend({
el: $('li.filter'),
initialize: function() {
},
render: function() {
},
toggleFilter: function() {
}
});
AllView = Backbone.View.extend({
initialize: function() {
this.newsListView = new NewsListView();
this.filterView = new FilterView();
}
});
Essentially, whenever the FilterView's toggleFilter() function is called, I would like to fire off an event called filtered or something like that that is then caught by the NewsListView, which then calls its reset() function. Without passing a reference of a NewsListView object to my FilterView, I'm not sure how to send it an event. Any ideas?
You're on the right track. It sounds like what you need is a global event dispatcher. There a decent article and example here: http://www.michikono.com/2012/01/11/adding-a-centralized-event-dispatcher-on-backbone-js/
You might be able to do this using the already available functionality of jquery events and the backbone events property.
For example, instead of doing this from inside your subview:
this.trigger("yourevent", this);
do this instead:
this.$el.trigger("yourevent", this);
Then in any view that is a parent, grandparent, etc of your child view, listen for the event on that view's $el by defining a property on that view's events object:
events:{
"yourevent":"yourhandler"
}
and define the handler on that view as well:
yourhandler:function(subview) {
}
So this way, a view doesn't need to know about what descendant views exist, only the type of event it is interested in. If the view originating the event is destroyed, nothing needs to change on the ancestor view. If the ancestor view is destroyed, Backbone will detach the handlers automatically.
Caveat: I haven't actually tried this out yet, so there may be a gotcha in there somewhere.
You should check out the Backbone.Courier plugin as bubbling events is a perfect use case:
https://github.com/dgbeck/backbone.courier
The easiest way I've found to trigger and listen to events is to just use the Backbone object itself. It already has the events functions mixed in to it, so you can just trigger eg:
Backbone.trigger('view:eventname',{extra_thing:whatever, thing2:whatever2});
then, in any other backbone view in your app, you can listen for this event eg:
Backbone.on('view:eventname', function(passed_obj) {
console.log(passed_obj.extra_thing);
});
I'm not exactly sure what the advantage is in not using the Backbone object as your event handler, and instead creating a separate object to do it, but for quick-and-dirty work, the above works fine. HTH!
NOTE: one disadvantage to this is that every listener will "hear" every single event triggered in this way. Not sure what the big O is on that, but work being careful to not overload your views with lots of this stuff. Again: this is quick and dirty! :)
This problem can be solved using small backbone.js hack. Simply modify Backbone.Events.trigger for passing events to the this.parent
if this.parent != null
So, I came up a with a solution - create an object that extends Backbone.Events, and pass it as a parameter to multiple views. This almost feels like message passing between actors, or something. Anyway - I'm posting this as an answer in case anybody else needs a quick solution, but I'm not going to accept the answer. This feels hacky. I'd still like to see a better solution.
NewsListView = Backbone.View.extend({
el: $('li#newspane'),
// Too bad this doesn't work, it'd be really convenient
// events: { 'filtered': 'reset' }
initialize: function() {
_.bindAll(this);
// but at least this does
this.options.eventProxy.bind('filtered', this.reset);
},
render: function() {},
reset: function() {}
});
FilterView = Backbone.View.extend({
el: $('li.filter'),
initialize: function() {},
render: function() {},
toggleFilter: function() {
this.options.eventProxy.trigger('filtered');
}
});
AllView = Backbone.View.extend({
initialize: function() {
var eventProxy = {};
_.extend(eventProxy, Backbone.Events);
this.newsListView = new NewsListView({eventProxy: eventProxy});
this.filterView = new FilterView({eventProxy: eventProxy});
}
});