I'm going to lose it!!! how can this not be working
$(function() {
var someView = new Backbone.View.extend({
el: $('.whatever'),
...
render: function() {
this.$el.append('AAARGH');
}
});
// init view...
});
Cannot call method append of undefined, yet it is in ready block.
Try
this.$el
Instead of
this.el
Related
In the function jsonRequest below, I can log this.model to the console, but I can't call this.model.fetch(), which I thought was the appropriate way to make a request to my server (at localhost:8080/jsonapi). The error it gives me is can't call fetch of undefined. Is there something I'm doing wrong in the code below?
var MyModel = Backbone.Model.extend({
url: 'jsonapi',
});
var MyView = Backbone.View.extend({
el: '#blahblah',
initialize: function(){
},
events: {
'click #start' : 'callJsonRequest',
},
callJsonRequest: function(){
setInterval(this.jsonRequest, 1000);
},
jsonRequest: function(){
console.log("jsonrequest", this.model); //this logs the model
this.model.fetch();
},
});
window.myModel = new MyModel();
window.startStop = new StartView({model: myModel});
You likely need to use bind to make sure this is the context of your View object.
As you mentioned in the comments, you can do:
setInterval(this.jsonRequest.bind(this), 1000);
The problem I am having is click events keep piling up (still attached after changing the view). I have fixed the problem by only having one instance of the view (shown below). I thought backbone got rid of events when the markup is changed. I haven't had this problem with other views.
BROKEN CODE: Click events keep piling up on loadPlayerCard as more views are created.
//Player Thumb View
PgaPlayersApp.PlayerThumbView = Backbone.View.extend({
events: {
'click': 'loadPlayerCard'
},
tagName: 'li',
template: _.template( $('#player_thumb').html()),
render: function()
{
this.$el.html(this.template(this.model.toJSON()));
return this;
},
loadPlayerCard: function()
{
new PlayerCardView({model: this.model}).render();
return false;
}
});
//Router
var Router = Backbone.Router.extend({
routes:{
'': 'loadPlayers'
},
loadPlayers: function()
{
PgaPlayersApp.Players.fetch({reset: true, success: function()
{
//When players is first fetched, we want to render the first player into the card area
new PlayerCardView({model: PgaPlayersApp.Players.first()}).render();
}});
}
});
PgaPlayersApp.Router = new Router();
Backbone.history.start();
FIXED CODE: Code that fixes the problem:
PgaPlayersApp.CurrentPlayerCard = new PlayerCardView();
//Player Thumb View
PgaPlayersApp.PlayerThumbView = Backbone.View.extend({
events: {
'click': 'loadPlayerCard'
},
tagName: 'li',
template: _.template( $('#player_thumb').html()),
render: function()
{
this.$el.html(this.template(this.model.toJSON()));
return this;
},
loadPlayerCard: function()
{
PgaPlayersApp.CurrentPlayerCard.model = this.model;
PgaPlayersApp.CurrentPlayerCard.render();
return false;
}
});
//Router
var Router = Backbone.Router.extend({
routes:{
'': 'loadPlayers'
},
loadPlayers: function()
{
PgaPlayersApp.Players.fetch({reset: true, success: function()
{
//When players is first fetched, we want to render the first player into the card area
PgaPlayersApp.CurrentPlayerCard.model = PgaPlayersApp.Players.first();
PgaPlayersApp.CurrentPlayerCard.render();
}});
}
});
PgaPlayersApp.Router = new Router();
Backbone.history.start();
PlayerCardView (For reference):
var PlayerCardView = PgaPlayersApp.PlayerCardView = Backbone.View.extend({
events: {
'click': 'flipCard'
},
el: '#pga_player_card',
template: _.template( $('#player_card').html()),
render: function()
{
this.$el.html(this.template(this.model.toJSON()));
return this;
},
flipCard: function()
{
this.$("#player_card_container").toggleClass('flip');
}
});
In your router you keep creating new PlayerCardViews:
new PlayerCardView({model: PgaPlayersApp.Players.first()}).render();
All of those views share exactly the same el:
el: '#pga_player_card'
So you keep creating new PlayerCardViews and each one binds to #pga_player_card.
Every time you do that, you bind a brand new view to exactly the same DOM element and each of those views will call delegateEvents to bind the event handlers. Note that delegateEvents binds to el and that jQuery's html method:
removes other constructs such as data and event handlers from child elements before replacing those elements with the new content.
So html does nothing to el but it will remove event handlers from child elements. Consider this simple example with <div id="d"></div>:
$('#d').on('click', function() {
console.log('Before .html');
});
$('#d').html('<p>Where is pancakes house?</p>');
$('#d').on('click', function() {
console.log('After .html');
});
If you then click on #d, you'll see both the before and after messages in the console.
Demo: http://jsfiddle.net/ambiguous/ftJtS/
That simple example is, more or less, equivalent to what you're doing.
You'll have a better time if you:
Put the view inside #pga_player_card and let the router do $('#pga_player_card').append(view.render().el).
Keep track of the view that's already there and view.remove() it before adding the new one.
Avoid trying to reuse DOM elements for multiple view instances and avoid trying to reuse views, neither is worth the hassle.
I've got a couple of views in backbone.js and I want want to be a child of the other. So I've created the following code.
var app = app || {} ;
app.ProjectCardView = Backbone.View.extend({
el: $('.list'),
template: _.template( $("#tpl-project-card-summary").html() ),
initialize: function() {
this.render();
},
render: function() {
return this.$el.html( this.template() );
}
});
app.DashboardView = Backbone.View.extend({
el: $('.contentwrap'),
template: _.template( $("#tpl-dashboard").html() ),
initialize: function() {
this.render();
this.addProjects();
},
render: function() {
//console.log(this.template());
this.$el.html( this.template() );
},
addProjects: function() {
var pcv = new app.ProjectCardView();
}
});
app.dash = new app.DashboardView;
The DashboardView renders perfectly, but when I create the ProjectCardView, the view doesn't seem to initialise so the template is empty and el is not set. If I set el in the initialize function, then $el is still not set. I just can't see what I'm doing wrong.
EDIT: looks like I found the issue; $('.list') is an element introduced by the first view, as such, it's not rendered by the time the second view is trying to find it - even though the first view has rendered in the DOM.
So how do I fix that?
If you specify your el as a jQuery selector $('.list') you should know that it will be executed when your Backbone.View.extend is executed. So if your element is not in the DOM by that time your selector won't work.
So you need to specify the el when it is in the DOM. So in your case you can write:
addProjects: function() {
var pcv = new app.ProjectCardView({ el: $('.list') });
}
Or you can use the Backbone feature that you can specify the el as a string istead of the jQuery selector:
app.ProjectCardView = Backbone.View.extend({
el: '.list',
//...
}
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();
}
...
});
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/