Javascript garbage collection - javascript

I have base class which extends Backbone.View class.
I pass a Jquery DOM element through the constructor, is this object a candidate for garbage collection?
var MainView = BaseView.extend({
initialize: function(o) {
this.container = o.myJqueryDomElement;
this.render();
},
render: function() {
this.container.append(this.$el);
},
dispose: function() {
this.remove();
}
});
in some point in my code I do this:
var mainView = new MainView({
myJqueryDomElement = $('#content')
});

first of all you should pass the jQuery element in as el, so
var MainView = BaseView.extend({
initialize: function(o) {
this.render();
},
render: function() {
this.html(someTemplate)
},
dispose: function() {
this.remove();
}
});
var mainView = new MainView({
el: $('#content')
});
Then this will be available throughout the object as a jQuery object via this.$el and the view shouldn't know about its container just the element it lives in. So if you want this to be the main view change your selector to be $('#content .main') or just $('#main')

Related

JQuery not recognising elements added through backbone view even after using 'on'

$(document).ready(function(){
$('.tagLines').on('mouseover', function(){
$(this).css("background-color","#ffffff").css("box-shadow", "0 0 4px 4px #C9C9C9");
}).on('mouseleave', function(){
$(this).css('background-color', '#F7F7F7').css("box-shadow", "0 0 2px 2px #C9C9C9");
}).on('click', function(){
window.location.hash = "manageMarks";
});
//this section is for all the views used in the application
var PGHome = Backbone.View.extend({
tagName: 'div',
initialize: function(){
},
render: function(){
var template = _.template($("#home").html(), {} );
this.$el.html( template );
}
});
var pgHome = new PGHome({el: $("#bodyContainer") });
//***********************************************************
//this is a router for the application
var NavRouter = Backbone.Router.extend({
routes:{
"manageMarks": "manage",
"": "home"
}
});
var navRouter = new NavRouter();
navRouter.on('route:manage', function(){
console.log("Manage Modules");
}).on('route:home', function (){
pgHome.render();
});
Backbone.history.start();
//******************************************
});
The above is a js snippet and its working as far as rendering a view with backbone is concerned. The problem is after the view is rendered, the elements rendered's events (click, mouseover, mouseleave) are not firing. The events where working prior to adding them with backbone. Can anyone help?
You need use event delegation, because you add events, before element has been appended to page
$("#bodyContainer").on('mouseover', '.tagLines', function(){
$(this).css("background-color","#ffffff").css("box-shadow", "0 0 4px 4px #C9C9C9");
}).on('mouseleave', '.tagLines', function(){
$(this).css('background-color', '#F7F7F7').css("box-shadow", "0 0 2px 2px #C9C9C9");
}).on('click', '.tagLines', function(){
window.location.hash = "manageMarks";
});
There isn't .tagLine element in DOM when bind events to it.
The code like this,
var PGHome = Backbone.View.extend({
tagName: 'div',
events:{
'mouseover .tagLines': function(){
// ...
},
'mouseleave .tagLines': function(){
// ...
},
'click .tagLines': function(){
// ...
}
},
initialize: function(){},
render: function(){
var template = _.template($("#home").html(), {} );
this.$el.html( template );
}
});

Adding views to different el based on condition in Backbone.js

I've got a problem with my Backbone.js app (Fiddle: http://jsfiddle.net/the_archer/bew7x010/3/). The app is list (ol element) with nested lists within. Here's my starting HTML:
<body>
<ol id="flowList"></ol>
</body>
<script type="text/template" id="item-template">
<%= content %>
</script>
When the list is empty. I add a new <li> element within it using Backbone.js and focus on the li.
When within the li, if I press the enter key, I want to insert a new li just after the li I pressed enter within.
If I press tab, I want to add a new sub ol element within the list element. I handle the keypress within the li elements like so:
handleKeyboardShortcuts: function(e){
if (e.keyCode == 13 && !e.shiftKey){
e.preventDefault();
this.el = $(e.target).parent();
Items.create({contnet: "New Item!"});
}
}
I have a listenTo on my collection, which on add, appends a li to the #flowList ol element using the addOne function:
addOne: function(todo) {
var view = new ItemView({model: todo});
$(this.el).append(view.render().el);
}
My problem is:
How do I pass the target element to add to, to the addOne function?
How do I pass an option to the addOne function, which instead of doing $(this.el).append does $(this.el).after ?
For some reason, I can't get my head around how to pass around those details. Here's the full Backbone.js code:
$(function() {
var Item = Backbone.Model.extend({
defaults: function() {
return {
content: "empty item..."
};
}
});
var ItemList = Backbone.Collection.extend({
model: Item,
localStorage: new Backbone.LocalStorage("todos-backbone"),
});
var Items = new ItemList;
var ItemView = Backbone.View.extend({
tagName: "li",
template: _.template($('#item-template').html()),
events: {
"click": "enableEdit",
"blur": "disableEdit",
},
initialize: function() {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
enableEdit: function(){
this.$el.attr("contenteditable","true").focus();
},
disableEdit: function(){
this.$el.attr("contenteditable","false");
}
});
var AppView = Backbone.View.extend({
el: $("#flowList"),
events: {
"keydown li": "handleKeyboardShortcuts"
},
initialize: function() {
this.listenTo(Items, 'add', this.addOne);
this.listenTo(Items, 'reset', this.addAll);
this.listenTo(Items, 'all', this.render);
Items.fetch();
if (Items.length === 0){
Items.create({content: "Sample Item!"});
}
},
render: function(e) {
console.log(e);
},
addOne: function(todo) {
var view = new ItemView({model: todo});
$(this.el).append(view.render().el);
},
addAll: function() {
Items.each(this.addOne, this);
},
handleKeyboardShortcuts: function(e){
if (e.keyCode == 13 && !e.shiftKey){
e.preventDefault();
this.el = $(e.target).parent();
Items.create({contnet: "New Item!"});
}
}
});
var App = new AppView;
});
Here's a link to the fiddle: http://jsfiddle.net/the_archer/bew7x010/3/
You could keep track of the selected item, enabling you to insert the new one after or within it.
In your existing enableEdit handler, trigger an 'item:select' event:
var ItemView = Backbone.View.extend({
enableEdit: function(){
this.trigger('item:select', this);
this.$el.attr("contenteditable","true").focus();
}
});
Then in your AppView, when you add a new item, attach a listener for the new item that updates the value of a 'selectedItem' property.
Then you can insert the new item into the dom based on the state of that property:
var AppView = Backbone.View.extend({
addOne: function(todo) {
var view = new ItemView({model: todo});
this.listenTo( view, 'item:select', this.handleItemSelect );
if(this.selectedItem) {
$(this.selectedItem.el).after(view.render().el);
}
else {
$(this.el).append(view.render().el);
}
},
handleItemSelect: function(item) {
console.log('item selected:', item);
this.selectedItem = item;
}
});
You should be able to do something similar for the tab key behaviour, by setting a flag when the tab key is pressed, before calling Items.create.

Drop event in backbone view

I'm trying to add a drop event in my backbone view but I can't get it right. Below is the code I'm using and you can also check out this jsfiddle.
var View = Backbone.View.extend({
events: {
'drop .dropzone': 'drop'
},
drop: function(e) {
e.preventDefault();
alert('Dropping');
},
initialize: function() {},
render: function() {}
});
$(function() {
var view = new View({
el: $('.dropzone');
});
});​
if your view's el == .dropzone then you should do
events: {
'drop': 'drop'
},
if you specify just event type without selector it will listen to event on the view's root element - $('.dropzone') in this case.
The way you did it here would try to find .dropzone element among children of .dropzone element - and clearly it would fail to find this element at all

Backbone.js hover event not triggering

I'm trying to use Backbone.js for the first time and I'm having some trouble. I don't know if my problem is that I'm not understanding how backbone is supposed to work or if it's just a code problem.
I'm trying to create a dynamic menu, and I have no problem creating the main menu bar with it's items, but I can't get the hover event to trigger whenever I hover one of the menu items.
Views
var MenuView = Backbone.View.extend({
initialize: function(items) {
this.menu = items;
//Main navigation bar
this.el = $("#main-nav");
this.trigger('start');
this.render();
},
render: function() {
var me = this;
_.each(this.menu, function(mi) {
mi.render(me.el);
});
return this;
},
handleHover: function(e) {
console.debug(e);
}
});
var MenuItemView = Backbone.View.extend({
tagName: 'li',
className:'menu-item',
events: { //none of these work
'hover a':'handleHover',
'mouseover a':'handleHover',
'mouseover':'handleHover',
'click': 'handleHover',
'click a': 'handleHover'
},
initialize: function(mi) {
this.menuItem = mi;
this.el = $("<li class=\"menu-item\"></li>")
},
render: function(parent) {
this.el.append('' + this.menuItem.get("text") + '');
parent.append(this.el);
return this;
},
handleHover: function(ev) {
console.debug("Hovering! " + ev + this.menuItem.get("cid"));
console.debug(ev);
return false;
}
});
Model
var MenuItem = Backbone.Model.extend({
defaults: {
parent: null,
children: [],
link: "",
text: ""
}
});
Startup code
$(document).ready(function() {
var menu = new MenuView([
new MenuItemView( new MenuItem({link: "/", text: "Home"})),
new MenuItemView( new MenuItem({link: "/", text: "Users"})),
new MenuItemView( new MenuItem({link: "/", text: "Configuration"}))
]);
});
Any help will be appreciated!
Thanks!
Update
Ok, after taking the definition of el outside of the initialize method on the MenuItemView view, it works, BUT that same element gets reused on all instances of the view, so I had to change the view to the following code in order to make it work the way I want it:
var MenuItemView = Backbone.View.extend({
events: { //none of these work
'hover a':'handleHover',
'mouseover a':'handleHover',
'mouseover':'handleHover',
'click': 'handleHover',
'click a': 'handleHover'
},
el: $('<li class="menu-item"></li>'),
initialize: function(mi) {
this.menuItem = mi;
this.el = $(this.el).clone(true);
},
render: function(parent) {
this.el.append('' + this.menuItem.get("text") + '');
parent.append(this.el);
return this;
},
handleHover: function(ev) {
console.debug("Hovering! " + ev + this.menuItem.get("cid"));
console.debug(ev);
return false;
}
});
Wny do I have to clone the element on a new instance?
hover is not a normal event, but a 'convenience' event provided by jquery. It is a combination of mouseenter and mouseleave.
Binding to mouseenter and mouseleave instead of hover will do what you need.
Re: "Why do I have to clone the element on a new instance?"
The underlying problem is right here:
var MenuItemView = Backbone.View.extend({
// ...
el: $('<li class="menu-item"></li>'),
The $('<li class="menu-item"></li>') call is executed when MenuItemView is being defined so you end up with only one $('<li>') being shared across all instances of MenuItemView.
If you create the el inside initialize or render then you'll have to bind the events by hand using delegateEvents:
By default, delegateEvents is called within the View's constructor for you [...]
So if you create this.el yourself then you'll have to call this.delegateEvents() yourself. For example:
var MenuItemView = Backbone.View.extend({
// ...
render: function() {
this.el = $('<li class="menu-item"><a>' + this.cid + '</a></li>');
this.delegateEvents();
return this;
},
//...
});
Demo: http://jsfiddle.net/ambiguous/RPqMh/2/
However, if you clone your this.el with the withDataAndEvents flag on, then you should be fine:
var MenuItemView = Backbone.View.extend({
el: $('<li class="menu-item"></li>'),
// ...
initialize: function() {
this.el = this.el.clone(true);
this.el.append('<a>' + this.cid + '</a>');
},
//...
});
Demo: http://jsfiddle.net/ambiguous/hCW3F/1/
But if you just this.el.clone(), it won't work because the delegate won't be bound to the clone:
var MenuItemView = Backbone.View.extend({
el: $('<li class="menu-item"></li>'),
// ...
initialize: function() {
this.el = this.el.clone();
this.el.append('<a>' + this.cid + '</a>');
},
// ...
});
Demo: http://jsfiddle.net/ambiguous/KZNPA/
But if you add your own delegateEvents call, you'll be okay:
var MenuItemView = Backbone.View.extend({
el: $('<li class="menu-item"></li>'),
// ...
initialize: function() {
this.el = this.el.clone();
this.el.append('<a>' + this.cid + '</a>');
},
render: function() {
this.delegateEvents();
return this;
},
// ...
});
Demo: http://jsfiddle.net/ambiguous/KZNPA/1/
It seems to me that you don't need this properties:
tagName: 'li',
className:'menu-item'
in MenuItemView if you specify this.el = $('<li class="menu-item"></li>');

Understanding Backbone.js event handler

So here is my view:
$(function() {
var ImageManipulation = Backbone.View.extend({
el: $('body'),
tagName: "img",
events: {
'mouseover img': 'fullsize',
'click img#current': 'shrink'
},
initialize: function() {
_.bindAll(this, 'render', 'fullsize', 'shrink');
//var message = this.fullsize;
//message.bind("test", this.fullsize);
},
render: function() {
},
fullsize: function() {
console.log("in fullsize function");
console.log(this.el);
$('.drop-shadow').click(function() {
console.log(this.id);
if (this.id != 'current') {
$('.individual').fadeIn();
$(this).css('position', 'absolute');
$(this).css('z-index', '999');
$(this).animate({
top: '10px',
height: '432px',
}, 500, function() {
this.id = "current";
console.log("animation complete");
return true;
});
};
});
},
shrink: function() {
$('.individual').fadeOut();
$('#current').animate({
height: '150px',
}, 500, function() {
this.id = "";
$(this).css('position', 'relative');
$(this).css('z-index', '1');
console.log("animation complete");
return true;
});
}
});
var startImages = new ImageManipulation();
});
What I don't understand is how to change the el to make 'this' take over the click function I have in full-size. I would much rather have the click jQuery function removed and have the mouseover function be another click, but I cant seem to figure out how to assign 'this' to the particular image that is being clicked. I hope my question makes sense.
Backbone's event handler assumes that you want to know about the object (both its code, and its DOM representation, the View.el object) for every event, and that the event is intended to change some aspect of the view and/or model. The actual target of the click is something you're assumed to know, or assumed to be able to derive.
Derivation is rather simple:
fullsize: function(ev) {
target = $(ev.currentTarget);
And replace all your this. references within your call to target.. this. will continue to refer to the View instance. In your inner function, the anonymous one assigned to .drop-shadow, this. will refer to the object that was just clicked on. If you want access to the surrounding context, use the closure forwarding idiom:
fullsize: function(ev) {
var target = ev.currentTarget;
var self = this;
$('.drop-shadow').click(function(inner_ev) {
console.log(this.id); // the same as inner_ev.currentTarget
console.log(self.cid); // the containing view's CID

Categories

Resources