Backbone .remove() and .unbind() not killing zombies, creating more - javascript

I am creating a single page app using Backbone and functions are firing twice ("Show Answer" prints to the console twice, and the accordion slides down, then up).
events: {
"click .question": "showAnswer"
},
showAnswer: function(e){
console.log("Show Answer");
$(e.target).siblings("div.hidden").slideToggle(600);
}
Through research I found out that this is most likely due to Backbone Zombies/memory leaks. I tried to implement the solutions suggested in this LosTechies article, other articles, and at least a dozen different Stack questions by adding .remove() and .unbind() whenever the view changed.
I'm using handlebars for templating, so I can't create a separate homeView and faqView and I have no models, making it difficult to implement the solutions offered.
The problem is, once I add .remove() and .unbind(), it causes the showAnswer() function to fire three times instead of two. For some reason, removing and unbinding the view is creating a third view.
Here is a fiddle with both my original code and the code with .remove()/.unbind added: http://jsfiddle.net/gR5aH/3/
Chances are very, very high that I'm calling the .remove/.unbind in the wrong place and the wrong way. I apologize for asking a question discussed a dozen times over, but I'm continuously failing to grasp the concept.
Any help would be immensely appreciated.
EDITED: updated the fiddle with html.

Made minor additions to your code to get it working in a fiddle:
http://jsfiddle.net/82n9L/2/
You'll want to note undelegate() on line 25:
this.$el.undelegate().fadeOut(1000, function(){
self.render(app.current_page);
self.$el.fadeIn(1500);
});
Since every time you instantiate a UI view, those events are being delegated on the #main-container element (Backbone uses .delegate() not .bind()). Each time you switch views back and forth, you re-delegate those events, resulting in the same handlers firing multiple times.
Also, line 37:
I changed:
var template = Handlebars.compile(source); // a function that accepts JSON
to:
var template = Handlebars.compile(source)(); // a compiled string
And finally, line 55:
//app.home();
You don't need to call app.home() explicitly. It gets triggered automatically when you start your router with Backbone.history.start()

It is likely that you are calling the remove method first when you should be calling unbind first. Also, you may want to use empty() instead (as it removes all event handlers of children and not just from the elem it's called on - not sure if it applies in this case though)

Related

Triggering change on input

I have written some code that changes an input quantity on a magento 1.9 ecommerce website.
jQuery("input.qty").val("10");
The problem is the javascript that triggers the total to update doesn't fire. I have found the code responsible and it looks like this:
(function() {
var qtyFields = $('super-product-list').select('input.qty');
qtyFields.each(function(el) {
el.observe("change", updateGroupedPrice);
});
Event.observe(window, "load", updateGroupedPrice);
function updateGroupedPrice() {
//do stuff
}
})();
I think this is using prototype.js but I tried to isolate it in a codepen but couldn't get it working.
I have tried to trigger the change event like so:
jQuery("input.qty").trigger("change")
But it does not work. I also ran through a load of other events but in the dev tools it shows the code listening on "change".
Does anyone know why I can't trigger the change?
Since the page is using Prototype.js, you ought to keep using that to trigger your change. If you introduce jQuery into this, you're a) loading another complete duplicate of what Prototype already does, and b) asking for a lot of trouble isolating the fact that $() is a method in both libraries.
Your jQuery is a little fishy to me, too. You're setting the value of one picker (I imagine) and yet you are addressing it with a classname, so potentially there is more than one select.qty in the page, and all of them will change to value 10, firing off (potentially) multiple callback functions.
The Prototype code you see here is setting up a "listener" for changes on what you would address in jQuery as$(#super-product-list input.qty) inputs.
jQuery always treats $() as returning an array of objects, and thus all of its methods act on the array, even if it only contains one member. Prototype has two different methods for accessing elements in the DOM: $('some_id'), which always returns one element (or none, if no match), and $$('some css selector'), which always returns an array (of zero or more matching elements). You would write (or use native) callback methods differently, depending on which accessor you used to gather the element(s).
If you want to change one of these inputs, you will need to isolate it before you set its value.
Let's say there are three select pickers with the classname qty in your #super-product-list element. You want to change the third one to 10:
$('super-product-list').select('input.qty').last().setValue('10');
Or, much smarter than this, you add an ID to the third one, and then your code is much shorter:
$('quantity_3').setValue('10');
In either case, this will send the "change" event from your select, and the updateGroupedPrice method will observe that and do whatever you have coded it to do.
You won't need to (and should not ever) trigger the change event -- that's a "native" event, and the browser owns it. jQuery's trigger() (which is fire() in Prototype, is used exclusively for "synthetic events", like you see in Bootstrap: show.bs.modal, hide.bs.modal, etc. You can spot these by the punctuation in their names; usually dots or colons to namespace the events and avoid collisions with other code.
Finally, if you really, really, really wanted to change every single #super-product-list select.qty element on the whole page to '10', you would do this in Prototype.js:
$$('#super-product-list select.qty').invoke('setValue', 10);

Destroying Selectize.js instances

I am loading an ajax form with inputs that I apply .selectize() to. I run into issues when I close and reopen the form because there are instances of the Selectize constructor function that are still around.
Is there a way to remove these instances when I close the form? I can see these objects build up as I open and close the form by looking through firebug in the DOM under Selectize.count. How do I access these instances and destroy them?
I have tried this:
instance1[0].selectize.destroy();
instance2[0].selectize.destroy();
Assigned the variables like this:
instance1 = $('#preferences_sport').selectize({
//custom code
});
instance2 = $('#preferences_sport').selectize({
//custom code
});
The Selectize.count continues to build up and I am not sure where to go from here.
Here is a JSFiddle where I show the objects building up
So I see what you are saying now that the fiddle was added. I began by searching the documentation for that count property. I couldn't find it. So next I searched the source code since it seems this is some undocumented thing. The only count I could find in source code was this line:
eventNS : '.selectize' + (++Selectize.count),
So basically this explains it. That count while it does increase for every element this is called on is not a current count of running widgets. Its an internal property the guy who wrote this uses as a GUID for event namespaces. So for instance when you call destroy he can only remove the events specific to that instance of the widget.
I would not use this property to tell you anything. I think its safe to assume that your destroy is working fine. If you are unfamiliar with event namespacing you can read more about it here:
https://api.jquery.com/event.namespace/
You can see he uses that eventNS throughout the code to attach events if you search for it. jQuery does this in their code too for lots of stuff like in their event and data code. They have a GUID variable they use so anyone who loads more than one instance of jQuery on a page, the instances won't step on each others toes.
So I guess the only thing I would now ask you is where did you learn about this count property? If you just found it and assumed that it meant this was a count of running instances try to remember to always check with the documentation. If you found it in the docs then please point me towards now so I can take a look and see if it verifies what I found or requires more looking into.
Also as a bonus heads up, I saw this in your fiddle, input is a self closing tag or also known as void elements.
<input type="text" value="Calgary, Edmonton" class="selectize_this"></input>
Should just be:
<input type="text" value="Calgary, Edmonton" class="selectize_this" />
From the spec:
Void elements can't have any contents (since there's no end tag, no
content can be put between the start tag and the end tag).
Void elements: area, base, br, col, embed, hr, img, input, keygen,
link, meta, param, source, track, wbr
The Selectize API does expose the following method:
destroy()
Destroys the control and unbinds event listeners so that it can be garbage collected.

What is best practice in javascript (JQuery), binding same event type to every element or bind to their parent?

For instance, you have a list of DOM elements, say, li (or table cell td).
Suppose you are processing a click event, when user clicks on any element in the list (or cell).
I considered two approaches:
1. binding 'click' event to each element or
2. binding event to the parent ul, then find a target from the 'event.target'.
I chose the first, because it is straightforward, a bit more reliable and easier to maintain by everyone in the future. By reliability I mean there is no extra code that can introduce a bug.
I understand that the first is less optimal.
So questions are:
1) I don't know how to measure the performance hit or how much more memory the script will consume in the first approach comparing to the second.
How much overhead approximately I could have in the first case?
2) I asked colleagues and some people advised me to do the second approach. I was told that it is a good practice in itself, called an 'event delegation', while the first one is a bad code practice.
So I was intrigued.
Is it really binding same event type to every element is a bad code practice?
For a simple click event on a simple site you need not worry too much about performance. But technically there are differences between
$('li').on('click', function(){ ... });
and
$('ul').on('click', 'li', function(){ ... });
The first one will not work with dynamically added content (e.g. after an ajax call). But if you add dynamically new li's to your ul the second script will work.
If it's important for you that it's easy to maintain then you can keep it simple like this:
var $doc = $(document);
$doc.on('click', 'li', function(){ ... });

knockout.js: update bindings?

when I inject any new elements into the DOM after ko.applyBindings(); was called, then knockout won't recognize these new elements.
I can understand why this is happening - they are just not indexed by knockout.
So, at first I thought this would be solved by just calling ko.applyBindings() again, after adding my new elements, BUT then I realized that for every ko.applyBindings() call you make, the according events get fired multiple times. So after applying five times, a click: binding will be fired five times, so this is not a desireable solution ;)
Is there anything like ko.updateBindings() or something else, to tell knockout to, well... update the element bindings?
greetings,
Chris
Each time you invoke ko.applyBindings the entire DOM is inspected for bindings. As a result you will get multiple bindings for each element if you do this more than once. If you just want to bind a new DOM element you can pass this element as a parameter to the applyBindings function:
ko.applyBindings(viewModelA, document.getElementById("newElement"));
See this related question:
Can you call ko.applyBindings to bind a partial view?
Without knowing what you're up to exactly, it seems like you're going the wrong way about this. Your view should be driven by your view model. So you shouldn't be directly adding DOM elements you then need to apply knockout bindings to.
Instead you should be updating your view model to reflect the change in the view, which then causes your new element to appear.
So for example, for your $('body').append('Click me!');, rather than adding the DOM element when the button should be visible, control the button visibility using the view model.
So your view model includes
var viewModel = { clickMeAvailable: ko.observable(false) }
And your HTML includes
Click me!
When the application state changes so the click me button is available, you then just viewModel.clickMeAvailable(true).
The point of doing this, and a big part of knockout, is to separate business logic from presentation. So the code that makes click me available doesn't care that click me involves a button. All it does is update viewModel.clickMeAvailable when click me is available.
For example, say click me is a save button that should be available when a form is filled in validly. You'd tie the save button visibility to a formValid view model observable.
But then you decide to change things so after the form is valid, a legal agreement appears which has to be consented to before saving. The logic of your form doesn't change - it still sets formValid when the form is valid. You would just change what occurs when formValid changes.
As lassombra points out in the comments on this answer, there are cases when direct DOM manipulation may be your best approach - for example a complex dynamic page where you only want to hydrate parts of the view as they are needed. But you are giving up some of the separation of concerns Knockout provides by doing this. Be mindful if you are considering making this trade-off.
I just stumbled upon a similar problem. I tried to add new elements to container and give those a onclick function.
At first tried the things you did, and even tried the approach ColinE recommended. This wasn't a practical solution for me so I tried SamStephens approach and came up with that, which works perfectly for me:
HTML:
<div id="workspace" data-bind="foreach:nodeArr, click:addNode">
<div class="node" data-bind="attr:{id:nodeID},style:{left:nodeX,top:nodeY},text:nodeID, click:$parent.changeColor"></div>
</div>
JavaScript:
<script>
function ViewModel() {
var self = this;
var id = 0;
self.nodeArr = ko.observableArray();
self.addNode = function (data, event) {
self.nodeArr.push({
'nodeID': 'node' + id,
'nodeX' : (event.offsetX - 25) + 'px',
'nodeY' : (event.offsetY - 10) + 'px'
})
id++;
}
self.changeColor = function(data, event){
event.stopPropagation();
event.target.style.color = 'green';
event.target.style.backgroundColor = 'white';
}
}
ko.applyBindings(new ViewModel());
</script>
You can play with it in the JS Fiddle I made.

Preferred way of modifying elements that have yet to be created (besides events)

There are a lot of questions about binding future manipulations to non-existent elements that all end up answered with live/delegate. I am wondering how to run an arbitrary callback (to add a class or trigger a plugin, for example) to all existing elements that match a selector and all future elements that match that same selector that are yet to be created.
It seems that the main functionality of the livequery plugin made it into the core but the other part, attaching arbitrary callbacks got lost along the way somehow.
Another common answer is event delegation but what if one doesn't have access to all of the vendor code that is creating the elements to have it trigger the events?
Here is some real-world code:
// with livequery
$('input[type=text], input[type=password], textarea, .basic_form .block select, .order_form .form_item select, .order_form .form_item input')
.livequery(function(){
$(this)
.focus(function(){
$(this).addClass('active');
})
.blur(function(){
$(this).removeClass('active');
})
.addClass('text');
});
// with live
$('input[type=text], input[type=password], textarea, .basic_form .block select, .order_form .form_item select, .order_form .form_item input')
.live('focus', function(){
$(this).addClass('active');
})
.live('blur', function(){
$(this).removeClass('active');
});
// now how to add the class to future elements?
// (or apply another plugin or whatever arbitrary non-event thing)
One approach would be to monitor when new nodes are added/removed and re-trigger our selectors. Thanks to #arnorhs we know about the DOMNodeInserted event, which I would ignore the cross-browser problems in the hope that those small IE patches could someday land upstream to jQuery or knowing the jQuery DOM functions could be wrapped.
Even if we could ensure that the DOMNodeInserted fired cross-browser, however, it would be ridiculous to bind to it with multiple selectors. Hundreds of elements can be created at any time, and making potentially dozens of selector calls on each of those elements would crawl.
My best idea so far
Would it maybe be better to monitor DOMNodeInserted/Deleted and/or hook into jQuery's DOM manipulation routines to only set a flag that a "re-init" should happen? Then there could just be a timer that checks that flag every x seconds, only running all those selectors/callbacks when the DOM has actually changed.
That could still be really bad if you were adding/removing elements in great numbers at a fast rate (like with animation or ____). Having to re-parse the DOM once for each saved selector every x seconds could be too intense if x is low, and the interface would appear sluggish if x is high.
Any other novel solutions?
I will add a bounty when it lets me. I have added a bounty for the most novel solution!
Basically what I am getting at is a more aspect-oriented approach to manipulating the DOM. One that can allow that new elements are going to be created in the future, and they should be created with the initial document.ready modifications applied to them as well.
JS has been able to do so much magic lately that I'm hoping it will be obvious.
In my opinion, the DOM Level 3 events DOMNodeInsertedhelp (which fires only for nodes) and DOMSubtreeModifiedhelp (which fires for virtually any modification, like attribute changes) are your best shot to accomplish that task.
Of course, the big downside of those events is, that the Internet Explorers of this world don't support them
(...well, IE9 does).
The other reasonable solution for this problem, is to hook into any method Which can modify the DOM. But then we have to ask, what is our scope here?
Is it just enough to deal with DOM modification methods from a specific library like jQuery? What if for some reason another library is modifying the DOM or even a native method ?
If it's just for jQuery, we don't need .sub() at all. We could write hooks in the form of:
HTML
<div id="test">Test DIV</div>
JS
(function(_append, _appendTo, _after, _insertAfter, _before, _insertBefore) {
$.fn.append = function() {
this.trigger({
type: 'DOMChanged',
newnode: arguments[0]
});
return _append.apply(this, arguments);
};
$.fn.appendTo = function() {
this.trigger({
type: 'DOMChanged',
newnode: this
});
return _appendTo.apply(this, arguments);
};
$.fn.after = function() {
this.trigger({
type: 'DOMChanged',
newnode: arguments[0]
});
return _after.apply(this, arguments);
};
// and so forth
}($.fn.append, $.fn.appendTo, $.fn.after, $.fn.insertAfter, $.fn.before, $.fn.insertBefore));
$('#test').bind('DOMChanged', function(e) {
console.log('New node: ', e.newnode);
});
$('#test').after('<span>new span</span>');
$('#test').append('<p>new paragraph</p>');
$('<div>new div</div>').appendTo($('#test'));
A live example of the above code can be found here: http://www.jsfiddle.net/RRfTZ/1/
This of course requires a complete list of DOMmanip methods. I'm not sure if you can overwrite native methods like .appendChild() with this approach. .appendChild is located in Element.prototype.appendChild for instance, might be worth a try.
update
I tested overwriting Element.prototype.appendChild etc. in Chrome, Safari and Firefox (official latest release). Works in Chrome and Safari but not in Firefox!
There might be other ways to tackle the requirement. But I can't think of a single approach which is really satisfying, like counting / watching all descendents of a node (which would need an interval or timeouts, eeek).
Conclusion
A mixture of DOM Level 3 events where supported and hooked DOMmanip methods is probably the best you can do here.
I was reading up on the new release of jQuery, version 1.5 and I immediately thought of this question.
With jQuery 1.5 you can actually create your own version of jQuery by using something called jQuery.sub();
That way you can actually override the default .append(), insert(), .html(), .. functions in jQuery and create your own custom event called something like "mydomchange" - without it affecting all other scripts.
So you can do something like this (copied from the .sub() documentation with minor mod.):
var sub$ = jQuery.sub();
sub$.fn.insert = function() {
// New functionality: Trigger a domchange event
this.trigger("domchange");
// Be sure to call the original jQuery remove method
return jQuery.fn.insert.apply( this, arguments );
};
You would have to do this to all the dom manipulation methods...
jQuery.sub() in the jQuery documention:
http://api.jquery.com/jQuery.sub/
Great question
There seems to be a custom event you can bind:
http://javascript.gakaa.com/domnodeinserted-description.aspx
So I guess you could do something like:
$(document).bind('DOMNodeInserted',function(){ /* do stuff */ });
But I haven't tried so I don't have a clue..
btw.: related question:
Can javascript listen for "onDomChange" on every Dom elements?
There is no simple obvious way to do it. The only surefire approach is active polling, which causes there to be a render hiccup between when the new element is created and when the polling notices it. That can also make your page take a lot of resources depending on how frequently you poll the page. You can also couple this, as you observed, with binding several browser-specific events to at least make things work out better in those browsers.
You can override jQuery's DOM modification functions to trigger a custom change event (and use $.live to catch those events for manipulation), but when I've tried this in the past, it's subtly broken various jQuery plugins (my guess is some of those plugins do something similar). In the end I've given up on doing so reliably since I don't want to give up the performance and render hiccups to active polling, and there is no other comprehensive way to do it. Instead I have an initialization event I make sure to trigger for each DOM change I make, and I bind my manipulation events to those instead.
Be careful, it's easy to get stuck in an infinite event loop if you don't think things through, and this can also be subtle and difficult to track down; and worse yet may happen for a corner case your unit testing didn't allow for (so your users experience it instead of just you). The custom manually triggered initialization event is easier to diagnose in this sense since you always know exactly when you're triggering it.
Well, first of all, you shouldn't even be using selectors like this if you're worried about perf.
$('input[type=text], input[type=password], textarea, .basic_form .block select, .order_form .form_item select, .order_form .form_item input')
Any browser that doesn't have native implementations of xpath (more than just IE iirc) or getElementsByClassName (IE 7 and below) could easily spend a few seconds chewing on that on a big site so polling would of course be completely out of the question if you want it that broad.

Categories

Resources