Backbone.js event after view.render() is finished - backbone.js

Does anyone know which event is fired after a view is rendered in backbone.js?

I ran into this post which seems interesting
var myView = Backbone.View.extend({
initialize: function(options) {
_.bindAll(this, 'beforeRender', 'render', 'afterRender');
var _this = this;
this.render = _.wrap(this.render, function(render) {
_this.beforeRender();
render();
_this.afterRender();
return _this;
});
},
beforeRender: function() {
console.log('beforeRender');
},
render: function() {
return this;
},
afterRender: function() {
console.log('afterRender');
}
});

Or you can do the following, which is what Backbone code is supposed to look like (Observer pattern, aka pub/sub). This is the way to go:
var myView = Backbone.View.extend({
initialize: function() {
this.on('render', this.afterRender);
this.render();
},
render: function () {
this.trigger('render');
},
afterRender: function () {
}
});
Edit: this.on('render', 'afterRender'); will not work - because Backbone.Events.on accepts only functions. The .on('event', 'methodName'); magic is made possible by Backbone.View.delegateEvents and as such is only available with DOM events.

As far as I know - none is fired. Render function is empty in source code.
The default implementation of render is a no-op
I would recommend just triggering it manually when necessary.

If you happen to be using Marionette, Marionette adds show and render events on views. See this StackOverflow question for an example.
On a side note, Marionette adds a lot of other useful features that you might be interested in.

I realise this question is fairly old but I wanted a solution that allowed the same custom function to be called after every call to render, so came up with the following...
First, override the default Backbone render function:
var render = Backbone.View.prototype.render;
Backbone.View.prototype.render = function() {
this.customRender();
afterPageRender();
render();
};
The above code calls customRender on the view, then a generic custom function (afterPageRender), then the original Backbone render function.
Then in my views, I replaced all instances of render functions with customRender:
initialize: function() {
this.listenTo(this.model, 'sync', this.render);
this.model.fetch();
},
customRender: function() {
// ... do what you usually do in render()
}

Instead of adding the eventhandler manually to render on intialization you can also add the event to the 'events' section of your view. See http://backbonejs.org/#View-delegateEvents
e.g.
events: {
'render': 'afterRender'
}
afterRender: function(e){
alert("render complete")
},

constructor: function(){
Backbone.View.call(this, arguments);
var oldRender = this.render
this.render = function(){
oldRender.call(this)
// this.model.trigger('xxxxxxxxx')
}
}
like this http://jsfiddle.net/8hQyB/

Related

Event handling between views

Ok I have a layout like the one in this pic:
The table in the upper part of the screen is made by:
MessageListView
define(['backbone','collections/messages','views/message'], function(Backbone, MessageCollection, MessageView) {
var MessageListView = Backbone.View.extend({
el: '#messagesContainer',
initialize: function() {
this.collection = new MessageCollection();
this.collection.fetch({reset:true});
this.listenTo( this.collection, 'reset', this.render );
this.table = this.$el.find("table tbody");
this.render();
},
render: function() {
this.collection.each( function(message, index) {
this.renderMessage(message, index);
}, this);
},
renderMessage: function(message, index) {
var view = new MessageView({
model:message,
className: (index % 2 == 0) ? "even" : "odd"
});
this.table.append( view.render().el );
}
});
return MessageListView;
});
MessageView
define(['backbone','models/message'], function(Backbone, MessageCollection, MessageView) {
var MessageView = Backbone.View.extend({
template: _.template( $("#messageTemplate").html() ),
render: function() {
this.setElement( this.template(this.model.toJSON()) );
return this;
},
events:{
'click':'select'
},
select: function() {
// WHAT TO DO HERE?
}
});
return MessageView;
});
AppView
define(['backbone','views/messages'], function(Backbone, MessageList) {
var App = Backbone.View.extend({
initialize: function() {
new MessageList();
}
});
return App;
});
I will soon add a new view (maybe "PreviewView") in the lower part of the screen.
I want to make something happen inside the "PreviewView" when user clicks a row.
For example, it could be interesting to display other model's attributes (details, e.g.) inside the PreviewView.
What is the best practice?
holding a reference to PreviewView inside each MessageView ?
triggering events inside select method, and listening to them using on() inside the preview view.
using a transient "selected" attribute in my model, and make PreviewView listen to collection "change" events?
Thank you, if you need more details tell me please, I'll edit the question.
Not sure about the best practice but I found this solution trivial to implement. I created a global messaging object, bus, whatever:
window.App = {};
window.App.vent = _.extend({}, Backbone.Events);
You have to register the "triggerable" functions of PreviewView on the previously created event bus (according to your example, this should be in the PreviewView):
initialize: function () {
App.vent.on('PreviewView.show', this.show, this);
}
Now you should be able to trigger any of registered events from anywhere within your application by calling: App.vent.trigger. For example when the user click on a row you will have something similar:
App.vent.trigger('PreviewView.show');
in case if you have to send and object along with the triggered event use:
App.vent.trigger('PreviewView.show', data);

Marionette ItemView how to re-render model on change

I'm using Handlebars template engine.
so, I have Model:
Backbone.Model.extend({
urlRoot: Config.urls.getClient,
defaults: {
contract:"",
contractDate:"",
companyTitle:"",
contacts:[],
tariff: new Tariff(),
tariffs: [],
remain:0,
licenses:0,
edo:""
},
initialize:function(){
this.fetch();
}
});
then Marionette ItemView:
Marionette.ItemView.extend({
template : templates.client,
initialize: function () {
this.model.on('change', this.render, this);
},
onRender: function () {
console.log(this.model.toJSON());
}
});
and then I call everything as:
new View({
model : new Model({id:id})
})
and, it's immediately render a view for me and this is cool.
But after the model fetched data it's trigger "change", so I see in console serialised model twice, and I see for first time empty model and then filled one.
But, the view is NOT updated.
How I can fix it?
P.S. I understand, that I can call a render method on fetch done callback. But I also need it for further actions, when user will change model.
In the View, You can use following code
modelEvents: {
'change': 'render'
}
instead of
initialize: function () {
this.model.on('change', this.render, this);
},
onRender: function () {
console.log(this.model.toJSON());
}
Actually, Backbone and Marionette are smart enough to do it.
Problem was in template and data as I found it another question. So, I re-checked everything and got result.

Rendering each collection item asynchronously in Backbone.js

I am trying to render a collection of items. Normally what I would do is something like this:
StuffView = Backbone.View.extend({
...
render: function(){
...
this.$el.html( ... );
return this;
}
...
});
StuffCollectionView = Backbone.View.extend({
...
render: function(){
this.collection.each(addOne, this);
},
addOne: function(stuff){
var view = new StuffView({model: stuff});
this.$el.append(view.render().el);
}
...
});
However, this time I'm building a bit different type of view. Each StuffView's rendering takes some time, so I can't do this synchronously. The code for the new StuffView looks something like this:
StuffView = Backbone.View.extend({
...
render: function(){
...
// Asynchronous rendering
SlowRenderingFunction(function(renderedResult){
this.$el.html(renderedResult);
});
}
});
In this case, I can't just return this from render and append its result to the StuffCollectionView's el. One hack I thought of was to pass a callback function to StuffView's render, and let it callback when it has finished rendering. Here's an example:
StuffView = Backbone.View.extend({
...
render: function(callback){
...
// Asynchronous rendering
SlowRenderingFunction(function(renderedResult){
this.$el.html(renderedResult);
callback(this);
});
}
});
StuffCollectionView = Backbone.View.extend({
...
initialize: function(){
_.bindAll(this, "onStuffFinishedRendering");
},
render: function(){
this.collection.each(addOne, this);
},
addOne: function(stuff){
var view = new StuffView({model: stuff});
view.render(onStuffFinishedRendering);
},
onStuffFinishedRendering: function(renderedResult){
this.$el.append(renderedResult.el);
}
...
});
But it's not working for some reason. Furthermore, this feels too hacky and doesn't feel right. Is there a conventional way to render children views asynchronously?
Can't you pass StuffCollectionView's el into the SlowRenderingFunction? It's a bit nasty but I don't see why it wouldn't work.
Edit: I should say, and make SlowRenderingFunction an actual property of StuffView, so that StuffViewCollection can call it instead of calling render.
You can try using _.defer to prevent the collection items rendering blocking the UI.
Refer http://underscorejs.org/#defer for more details.
StuffCollectionView = Backbone.View.extend({
...
render: function(){
var self = this;
_(function() {
self.collection.each(addOne, self);
}).defer();
}
...
});

Event delegation failing to attach events in Backbone.js app

My Backbone.js app has a popup editor view that will be repeatedly closed and reopened as the user makes changes. I'm trying to figure out the cleanest way to implement this, and I'm stuck on an event delegation issue.
I believe the problem lies in the way I'm applying the template in my render method. I haven't had any issues with getting events to fire in other views, but those views differed in that they had a model. The view I'm having issues with is more of an application view that contains sub-views, so I'm not sure how to pass the view's context to the MyApp view.
Here's my code:
MyApp = Backbone.View.extend({
tagName: 'div',
template: _.template($('#app-template').html()),
initialize: function() {
_.bindAll(this);
this.render();
},
render: function() {
$('#container').html(this.template);
return this;
},
events: {
"click .save" : "onSaveClicked"
},
onSaveClicked: function () {
console.log("Save clicked.");
this.$el.remove();
}
});
$('#show').click(function () {
var myapp = new MyApp;
});
I've also posted it as a jsFiddle.
I stepped through the Backbone.js source, and it appears that render is called first, then events are assigned, which is what I'd expect. Everything looks OK from what I can tell, but onSaveClicked never fires when I click Save.
The desired functionality is that clicking Show displays the form and Save removes it. If there's a better way to do this that's more inline with Backbone's underlying philosophy I'm open to that as well. You'll notice that I'm nesting an unnamed div inside container, and that's because I wanted to maintain a consistent anchor point for my popup.
The events are bound to the view el, but you never append the el to the DOM. Try
MyApp = Backbone.View.extend({
tagName: 'div',
template: _.template($('#app-template').html()),
initialize: function() {
_.bindAll(this);
this.render();
},
render: function() {
this.$el.html(this.template);
return this;
},
events: {
"click .save" : "onSaveClicked"
},
onSaveClicked: function () {
console.log("Save clicked.");
this.$el.remove();
}
});
$('#show').click(function () {
var myapp = new MyApp;
$("#container").append(myapp.$el);
});
http://jsfiddle.net/WBPqk/18/
Note that in your Fiddle you bound the click event to .save where your template uses a done class.

Backbone.Collection.Create not triggering "add" in view

Hopefully this is an easy question. I'm trying to learn backbone and i'm stuck on a really simple thing. the render on the view never gets called when I update the collection by using the create method. I thought this should happen without explicitly calling render. I'm not loading anything dynamic, it's all in the dom before this script fires. The click event works just fine and I can add new models to the collection, but the render in the view never fires.
$(function(){
window.QuizMe = {};
// create a model for our quizzes
QuizMe.Quiz = Backbone.Model.extend({
// override post for now
"sync": function (){return true},
});
QuizMe._QuizCollection = Backbone.Collection.extend({
model: QuizMe.Quiz,
});
QuizMe.QuizCollection = new QuizMe._QuizCollection
QuizMe.QuizView = Backbone.View.extend({
el:$('#QuizMeApp'),
template: _.template($('#quizList').html()),
events: {
"click #addQuiz" : "addQuizDialog",
},
initialize: function() {
// is this right?
_.bindAll(this,"render","addQuizDialog")
this.model.bind('add', this.render, this);
},
addQuizDialog: function(event){
console.log('addQuizDialog called')
QuizMe.QuizCollection.create({display:"this is a display2",description:"this is a succinct description"});
},
render: function() {
console.log("render called")
},
});
QuizMe.App = new QuizMe.QuizView({model:QuizMe.Quiz})
});
Your problem is that you're binding to the model:
this.model.bind('add', this.render, this);
but you're adding to a collection:
QuizMe.QuizCollection.create({
display: "this is a display2",
description: "this is a succinct description"
});
A view will usually have an associated collection or model but not both. If you want your QuizView to list the known quizzes then:
You should probably call it QuizListView or something similar.
Create a new QuizView that displays a single quiz; this view would have a model.
Rework your QuizListView to work with a collection.
You should end up with something like this:
QuizMe.QuizListView = Backbone.View.extend({
// ...
initialize: function() {
// You don't need to bind event handlers anymore, newer
// Backbones use the right context by themselves.
_.bindAll(this, 'render');
this.collection.bind('add', this.render);
},
addQuizDialog: function(event) {
this.collection.create({
display: "this is a display2",
description: "this is a succinct description"
});
},
render: function() {
console.log("render called")
// And some stuff in here to add QuizView instances to this.$el
return this; // Your render() should always do this.
}
});
QuizMe.App = new QuizMe.QuizView({ collection: QuizMe.QuizCollection });
And watch that trailing comma after render, older IEs get upset about that and cause difficult to trace bugs.
I'd give you a quick demo but http://jsfiddle.net/ is down at the moment. When it comes back, you can start with http://jsfiddle.net/ambiguous/RRXnK/ to play around, that fiddle has all the appropriate Backbone stuff (jQuery, Backbone, and Underscore) already set up.

Resources