backbone collection events not firing. Am I missing something? - javascript

I'm trying to make a preloader and getting caught at step one with backbone. I've built a nice one before using jquery and also with 'raw' js basically what happens is that I have a folder called img/ui and a server side script that just gives a JSON dump of that folder. This request is /preload the js then queues this and loads them one by one based upon a process of load events with timeouts & error handlers.
What I'm trying to is port this to Backbone. The pattern I thought was a collection which loads the JSON build a set of models for each of the assets then a single view attached to the collection to display the status of the queue...... simple.
But I'm already stuck.. first I have to manually fetch the JSON or it wont do anything.. fine.. done, second even when the JSON is loaded it wont fire the parse method (or any other):
var PreloaderCollection = Backbone.Collection.extend({
model:PreloaderModel,
url:"/preload",
events: {
"change":"parse"
},
initialize: function()
{
_.bindAll(this);
this.fetch(this.url);
},
setup: function(args)
{
},
update: function(args)
{
},
parse:function(args){
log(args)
},
remove: function(args)
{
}
});
I'm really starting to get frustrated with Backbone, It's my first major project and despite reading about every tutorial and fully going through the source there seems to be so much contradiction about the pattern and capabilities.
EDIT:
This was a last resort and feels very dirty but here's how I 'bypassed' the issue.
I essentially overrode the fetch function with my own like so, which now works but... hmm,
var PreloaderCollection = Backbone.Collection.extend({
url:"/preload",
events: {
"reset":"parse"
},
initialize: function()
{
log("initing preloader collection")
_.bindAll(this);
this.bind("change",this.parse)
this.fetch(this.url);
},
fetch:function(args){
$.getJSON(
args,
this.parse
)
},
setup: function(args)
{
},
update: function(args)
{
},
parse:function(args){
log("parse",args)
},
remove: function(args)
{
}
});

you are using the wrong way of binding to your events :)
whith the event's hash, you declare all events jquery need to bind to elements in the DOM, on your view.
in a model, or collection you bind to reset / change / error events like this:
var myModel = Backbone.Model.extend({});
var myCollection = Backbone.Collection.extend({
model: myModel,
initialize: function() {
this.bind('reset', this.parse, this)
},
parse: function() {
alert('parsing');
}
});
var c = new myCollection({});
c.reset([{name: 'name1'}, {name: 'name2'}]);
see more info on eventbinding here: http://documentcloud.github.com/backbone/#Events-bind
see more info on the delegateEvents you were trying to use, but are only meant to be used in a view for DOM element event binding: http://documentcloud.github.com/backbone/#View-delegateEvents
see jsfiddle for a working version: http://jsfiddle.net/saelfaer/kt2KJ/1/

The event to listen for after fetching is reset. So for actin on that you will have to write:
events: {
"reset":"parse"
}
The documentation for fetch can be found on the backbone page:
http://documentcloud.github.com/backbone/#Collection-fetch

Related

Backbone leaves Detached DOM elements no matter how I remove the view

I'm struggling with getting the concept of memory management with single page applications. This is my code:
var FilterModel = Backbone.Model.extend({});
var taskView = Backbone.View.extend({
template: _.template('<h1><%= title %></h1>'),
initialize: function(){
this.render();
this.listenTo(this.model, 'destroy', this.remove);
console.log(this.model)
},
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
},
events:{
'click h1': 'removeView'
},
removeView: function(){
this.model.destroy();
console.log('removed');
}
});
var filterModel = new FilterModel({title: 'Test'});
var taskview = new taskView({model:filterModel});
// I make heap snapshot before and after the change!
setTimeout(function(){
$("h1").click()}, 3000
)
$('body').append(taskview.$el);
I was told by numerous articles that using "remove" and "destroy" would clean up any memory leaks when removing the DOM tree.
But Chrome profile utility tells otherwise. I get detached DOM elements no matter what I do.
UPDATE!!!
After trying a few things in the responses I still get this in Google Chrome:
Here is jsfiddle: http://jsfiddle.net/HUVHX/
taskview is still holding a strong reference to this.el, although it is not connected to the dom. This is not a memory leak because taskview is held strongly also by it's variable
To test my assumption just add:
removeView: function(){
this.model.destroy();
this.el = undefined;
this.$el = undefined;
}
Another approach is to undef taskview var
EDIT:
When I change: "click h1" : "removeView" To "click": "removeView" it solves the detached dom node leak.
I suspect this has something to do with jquery selector caching.
You can see in backbone code, the difference is in calling jquery on function with a selector:
if (selector === '') {
this.$el.on(eventName, method);
} else {
this.$el.on(eventName, selector, method);
}
I tried to trace the cache deep into jquery code, with no luck.
So Janck, you can fin your answer here:
Backbone remove view and DOM nodes
The problems is that you have to do more than just remove you model and view.
You need to properly destroy all of the events and other bindings that are hanging around when you try to close your views.
I don't know if you know about Marionette.js (Backbone.Marionette), but it's a great extension to Backbone to handle this Zombie Views and to create robust JS applications.
You can read some articles about this as well, they were pointed in the Stackoverflow link that I posted.
http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
http://lostechies.com/derickbailey/2012/03/19/backbone-js-and-javascript-garbage-collection/
But the logic is this: If a View is listening a model, then the contrary also occurs, so you'll always get a instance of your View in your DOM.

Understanding click events in Backbone and Express

I am trying to learn some javascript and I've gone through several tutorials, now I'm trying to understand a real-life system. Here is a demo site that has been pretty well put together:
http://nodecellar.coenraets.org/
https://github.com/ccoenraets/nodecellar
I think I understand the basics of how events can be assigned to elements on the page but then when I look through his source code I can't figure out how even the first click works. When you click "Start Browsing" it should be caught by javascript somehow which fires off an asynchronous request and triggers the view to change with the data received. But in his / public/js/views/ nowhere is there event catching plugged in (except in the itemdetail view but that's a different view entirely).
I also tried using chrome developer tools to catch the click and find out what script caught it.
Under sources I tried setting an event breakpoint for DOM mutation and then clicked.... but no breakpoint (how is that possible? There's definitely a DOM mutation happening)
I then went under elements and checked under the "click" event listener and didn't see anything revealing there either.
Obviously I don't know what I'm doing here. Can anyone help point me in the right direction?
This app is using backbones routing capabilities to switch contexts.
It is basically using hash tags and listening for location change events to trigger updates to the page.
The routing configuration is in main.js:
See: Backbone.Router for more information.
Code Reference: http://nodecellar.coenraets.org/#wines
var AppRouter = Backbone.Router.extend({
routes: {
"" : "home",
"wines" : "list",
"wines/page/:page" : "list",
"wines/add" : "addWine",
"wines/:id" : "wineDetails",
"about" : "about"
},
initialize: function () {
this.headerView = new HeaderView();
$('.header').html(this.headerView.el);
},
home: function (id) {
if (!this.homeView) {
this.homeView = new HomeView();
}
$('#content').html(this.homeView.el);
this.headerView.selectMenuItem('home-menu');
},
list: function(page) {
var p = page ? parseInt(page, 10) : 1;
var wineList = new WineCollection();
wineList.fetch({success: function(){
$("#content").html(new WineListView({model: wineList, page: p}).el);
}});
this.headerView.selectMenuItem('home-menu');
},
// etc...
});
utils.loadTemplate(['HomeView', 'HeaderView', 'WineView', 'WineListItemView', 'AboutView'], function() {
app = new AppRouter();
Backbone.history.start();
});

tying button onclick event to Backbone View

I have a sample single-page-application in Backbone that I am messing around with. The idea is that I have a button that will trigger a refresh on a view. I am trying to get the event handler to remember 'this' as the backbone view, not the element that it was called on. No matter how much docs I read, I cant seem to make it past this mental hump.
in my view, I have
initialize: function() {'
//i am trying to say, call render on this view when the button is clicked. I have tried all of these calls below.
//this.$("#add-tweet-button").click(this.render);
//this.$("#add-button").bind("click", this.render);
}
When the render function is called, the 'this' element is the button. I know what im missing is pretty easy, can someone help me out with it? Also, is this sound as coding conventions go?
If you use the View's 'delegateEvents' functionality, the scoping is taken care of for you:
var yourView = Backbone.View.extend({
events: {
"click #add-tweet-button" : "render"
},
render: function() {
// your render function
return this;
}
});
This only works with elements that are 'under' the View's El. But, your example shows this.$(...), so I'm assuming this is the case.
#Edward M Smith is right, although if you need to handle a jquery event of element outside the scope of your View you might write it that way :
var yourView = Backbone.View.extend({
initialize: function() {
var self = this;
$("button").click(function () {
self.render.apply(self, arguments);
});
},
render: function(event) {
// this is your View here...
}
});

Backbone.js: calling custom events inside a View?

I would like to update part of my view when the user types into a input field. Initially I bound to the keyup event listener within the View's events field, and that worked well:
window.AppView = Backbone.View.extend({
el: $("#myapp"),
events: {
"keyup #myInput": "updateSpan",
}, ...
updateSpan: function() {
this.span.text(this.input.val());
}, ...
});
But then I realised that keyup updated too often and made the app slow. So I decided to use the typeWatch plugin so the event would only fire the user stopped typing. But now I don't know how to set the custom event listener in Backbone. Currently I have this:
window.AppView = Backbone.View.extend({
initialize: {
var options = {
callback: function(){
alert('event fired');
this.updateSpan;
},
wait:750
}
this.input.typeWatch(options);
}, ...
updateSpan: function() {
this.span.text(this.input.val());
}, ...
});
Two questions:
I see the alert, but updateSpan is not being fired. I think I'm using this incorrectly in the callback, but how should I do it?
Is initialize now the right place to set the typeWatch event listener, or can I continue to use the events field as I did before?
You aren't actually calling updateSpan, and you're right that this wont be the correct thing. Easiest way to solve it is to just capture the view into another variable first:
var v = this;
var options = {
callback: function() {
alert('event fired');
v.updateSpan();
},
wait: 750
};
this.input.typeWatch(options);
As for your second question, usually I will attach functionality like this in initialize if it's on the base element and in render if it's not, so I think in this case you've probably got it right.

BackboneJS: Trigger form validation on form submit from view

So, I've started using Backbone.js to structure my javascript code and have modular applications, and I came across a problem reggarding events.
I want to make a simple View that handles forms and validates them. In the future I would like to add all the javascript functionallity like live validation, hover effects, etc.
This is the simplified code I have right now:
var Form = Backbone.View.extend({
attributes: {
att1 = 'att1',
att2 = 'att2'
},
events: {
'submit': 'validateFields'
},
initialize: function(element) {
this.el = $(element);
},
validateFields: function() {
alert(this.attributes.att1); //do something
return false;
}
});
var f = new Form('#formid');
The problem I had is that the validateFields function is not called when I submit the form. I also tried using this on the constructor:
this.el.bind('submit', this.validateFields);
Now, that last code works but the "this" inside the validation function would be the $('#formid') object, instead of my Form object.
Backbone uses jQuery's delegate method to bind the events to the callbacks in the events hash.
Unfortunately the delegate method does not work for the submit event in IE See comment in Backbone source
An easy fix is to add this line of code to your render method.
render: function() {
//render the view
$(this.el).submit(this.validateFields);
}
You will also need to bind the context for validateFields in the initialize method
initialize: function() {
_.bindAll(this, 'render', 'validateFields');
}
Try setting your el in other way:
var f = new Form({el: '#formid'});
In this case you can even remove initialize method (or change it):
var Form = Backbone.View.extend({
// ...
initialize: function() {
// Some code
},
// ...
});
As far as this code is concerned: this.el.bind('submit', this.validateFields);. If you want to preserve Form object context you should use binding:
this.el.bind('submit', _.bind(this.validateFields, this)); // using Underscore method
this.el.bind('submit', $.proxy(this.validateFields, this)); // using jQuery method

Categories