jQuery plugin instances and remove/destroying them? - javascript

I have a situation in which a plugin that I have on the page will be references more than a few times. The issue is that the elements that will call the plugin will be created on the fly. And then removed etc... for instance.
I have a div structure and it creates some child elements.
i. I add the plugin instance on these children. They do some magic etc..
ii. User clicks a button, new set of child elements are created -- I create the plugin instances.
NOW -- what happens to those created in point "i" above? I am not using "widget" so I don't have the destroy method.
So, can I just call the child elements, and just loop thru them and destroy the data element etc.. ala.
$.each('[some_class_that_represents_all_elements]', function(a,b){
$(b).data('PLUGIN_NAME') = null; // or
delete $(b).data('PLUGIN_NAME'); // or
$(b).data('PLUGIN_NAME', null);
})
If the elements are removed from the DOM, do the plugin instances also get removed/cleaned up?

jQuery by default will cleanup any data you store using $(el).data() when you call the $(el).remove() method.
If you use $(el).detach() the data is preserved.
http://api.jquery.com/jQuery.data/
jQuery ensures that the data is removed when DOM elements are removed
via jQuery methods, and when the user leaves the page.

Related

Reset/Render again element in polymer

Their is option in polymer to reset element/ custom element to the original state. Like render the element again?
I want all the inner variables values to delete and that the element will look the same as I load the page.
Thanks
I see to ways:
First one is just to write 'reset' method which resets all vars so that bound ui gets to the initial state.
The second one is to remove the element with Polymer.dom(parent).removeChild and then create new one of same type with document.createElement and put it at old element's place with Polymer.dom(parent).appendChild/insertBefore.
I'd choose first one - seems to be more testable and confined to the lement.

jQuery events not firing after "cleaning" Knockout bindings

In my project, I have an <div> where I specifically apply my Knockout.js bindings. I have to instantiate different viewmodels in that area depending on what the user clicks.
To prevent getting a cannot call bindings twice on the same element error, I first have to "Clean" the bindings to make the area available again. I call the initial applyBindings() function:
ko.applyBindings(new ViewModel("Planet", "Earth"), document.getElementById("bindings-area"));
Eventually, I will clean the <div> and call the new bindings:
var element = $("#bindings-area")[0];
ko.cleanNode(element);
ko.applyBindings(new ViewModel("NEW", "Bindings"), document.getElementById("bindings-area"));
Problem: When I include an HTML button in the #bindings-area div, it will no longer work after I clean the bindings and instantiate the new model. I'm sure it has to do with the ko.cleanNode() function somehow removing the button bindings as well. How can I re-initiate them or prevent cleanNode() from operating on the button in the first place?
Fiddle: http://jsfiddle.net/jL6L01xs/2/
This issue is nicely described in Knockout documentation. This quote describes what the issue is and what needs to be done:
When removing an element, Knockout runs logic to clean up any data
associated with the element. As part of this logic, Knockout calls
jQuery’s cleanData method if jQuery is loaded in your page. In
advanced scenarios, you may want to prevent or customize how this data
is removed in your application. Knockout exposes a function,
ko.utils.domNodeDisposal.cleanExternalData(node), that can be
overridden to support custom logic. For example, to prevent cleanData
from being called, an empty function could be used to replace the
standard cleanExternalData implementation:
ko.utils.domNodeDisposal.cleanExternalData = function () {
// Do nothing. Now any jQuery data associated with elements will
// not be cleaned up when the elements are removed from the DOM.
};
Here is the updated jsFiddle.

Clone and restore "tooltiped" elements

I'm in trouble with restoring DOM structure that has elements passed to Bootstrap's .fn.tooltip() method.
To be specific: $('footer p') is passed to tooltip on document ready event, like this:
$(function(){
$('footer p').tooltip();
$('footer p').on('click', function(){
console.log('Just to test events')
});
})
I check it out, tooltip works, on click console message appears. Now I take backup of what am I about to delete and delete it, from console, by calling function:
function experiment_destroy() {
window.backup = $('footer').clone(true, true);
$('footer p').remove();
}
as expected, footer's p disappears.
Now I restore what is cloned and cached in window.backup variable with:
function experiment_restore(){
$('footer').empty();
$('footer').replaceWith(window.backup);
}
also called from console and here's what happens:
footer p element is back as it should be
footer p on click produces console message 'Just to test events'
message, so this event is restored along with element
no tooltip is restored.
Even if I re-call tooltip method in function experiment_restore I get nothing. Does anyone have some idea?
UPDATE:
I've made one more variation. Tried with different - totally minimal DOM environment with just p for tooltip and parent container element. Results are the same. Definitely there isn't just something in my complex DOM structure that was messing things up.
Here is very simple Fiddle.
You need to call the tooltip() method again. Optionally you should destroy the tooltip before cloning / removing the item for cleaning up the data.
Working Fiddle
$('footer p').tooltip();
$('#destroy').click(function(){
// optionally remove bindings
$('footer p').tooltip('destroy');
window.backup = $('footer').clone();
$('footer p').remove();
})
$('#restore').click(function(){
$('footer').replaceWith(window.backup);
// apply tooltip again
//window.backup.find("p").tooltip();
$('footer p').tooltip();
});
For the scenario you've shown in your question, I would use $().detach() to remove it from the DOM while at the same time keeping the event handlers and the data added to it with $().data() intact. In terms of the fiddle you've put in the question:
$('#destroy').click(function(){
var $footer_p = $('footer p');
window.backup = $footer_p;
$footer_p.detach();
})
$('#restore').click(function(){
var $footer = $('footer');
$footer.append(window.backup);
});
Here's an updated fiddle
What happens behind the scenes is that Bootstrap uses $().data() to add a JavaScript object of class Tooltip to your DOM element, and adds a bunch of event handlers. You need to preserve these.
If for some reason, you cannot use $().detach(), then you would have to recreate the tooltip by calling $().tooltip().
Why is $().clone(true, true) not working?
You call $().clone() with parameters to deep clone the DOM hierarchy and preserve the event handlers and the data set with $().data() so why is it not working? Is it not the case that the clone should have a reference to the Tooltip object created by Bootstrap?
Yes, the event handlers are preserved, and the clone does have a reference to the Tooltip object. However, this object it itself not cloned. More importantly, it is not adapted to refer to the new DOM node created by $().clone(). (So even if jQuery would clone it, it would still not work.) It does receive the event that would trigger the tooltip but Tooltip.prototype.show performs this check:
var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
if (e.isDefaultPrevented() || !inDom) return
The inDom variable will be true if this.$element is in the DOM. However, this refers to the original element for which the tooltip was created, not the clone. Since that element is no longer in the DOM, then inDom is false and the next line returns, so the tooltip is never shown.
For giggles, take a clone of a DOM element on which you created a Bootstrap tooltip, do not remove the original element but add the clone somewhere else on the page. Then trigger the tooltip on the clone. The tooltip will appear on the original element. :)
What I described above is the general way Bootstrap's jQuery plugins work: they use $().data() to add JavaScript objects to the elements on which they operate. There's also a Dropdown class for dropdowns, a Modal class for modals, etc.
As an added answer, I used JQuery's clone method, but I copied all event listeners like .clone(true, true). My issue was that the tooltips were the exact same from the old and cloned elements, but they were in different positions (so hovering over the new one would show a tooltip in the top left corner of my browser).
The easiest fix I could think of that should work for all Javascript, Bootstrap, JQuery forever is:
const target = document.getElementById("random-div-you-want-to-clone-to")
$("selecting").clone(true, true).appendTo(target);
// if you only want .innerHTML of $("selecting")
// you can do $("selecting").children()
const _tooltips = target.querySelectorAll("[data-toggle='tooltip']");
for (const x of _tooltips) {
const build = x.cloneNode(true);
$(build).tooltip();
x.parentNode.replaceNode(build, x);
}
So the .clone(true, true) will grab all the event listeners, including "mousedown" which is the listener for the tooltips. When you use native ECMAScript's cloneNode method, you aren't getting the event listeners, so you need to reset the tooltips.
It's not the most efficient, but I had been working on this for an hour trying to think of something... be my guest in find a more efficient way because this method isn't, but it works. (e.g. use forEach, simply using JQuery directly, etc.).
Edit: you can also use .children() to get the innards of $("selecting") (i.e. its children) when cloning rather than getting it AND its children.
For those who use Bootstrap 4, the method $.tooltip("destroy") was replaced by $.tooltip("dispose")

javascript memory question

Let's say before my webapp starts, I want to create all dom elements initially and store them in some preloaded array. Something like:
for (i = 1...100) { preLoader.push($('<div id="' + i + '" />')); }
and then later, depending on the action, I will take the correct element from the array and append it to the DOM.
Now my question is: if I were to later do:
$(div#i).remove()
will it also affect my preLoader array, or is it a different reference than the one in the DOM?
will it also affect my preLoader array
No, it will not. Object will be removed from memory, only and only if there are no ways to access it, that is no references to it. After $('div#'+i).remove(), you can't access it from the DOM, but you can still access it by preLoader[i-1].So you need to remove the object from preLoader array explicitly:
preLoader.splice(i-1,1);
The object in the DOM is the same. If you want to reuse the same tag again you should call
$(div#i).detach();
From jquery docs:
The .detach() method is the same as .remove(), except that .detach() keeps all jQuery data associated with the removed elements. This method is useful when removed elements are to be reinserted into the DOM at a later time.
If you use .remove() you will lose events and data associated with the element beeing removed. But even using remove() you can reuse the same element after by calling .appendTo() again.
Example on fiddler: http://jsfiddle.net/sKRCF/1
(sorry about the alerts, it's the easy way).

Break up a form with jQuery?

I have a form, which I want to iterate through. I want to show one fieldset at a time, and then show a "next" and "back" button to go to the next section.
I'm assuming that I start with $('fieldset'); but how do I access individual elements thereafter?
$("fieldset")[i] does not seem to work.
How would I accomplish that with jQuery?
I don't necessarily recommend this, but:
$($('.fieldset')[i]).css(...)
Should work.
If you wrap each call to $('.fieldset')[i] in a new JQuery selector, you create a new JQuery object out of that single item. JQuery objects have the method css that you want. Regular dom objects do not. (That's what you get with $('.fieldset')[i])
From the jQuery documentation:
How do I pull a native DOM element from a jQuery object?
A jQuery object is an array-like
wrapper around one or more DOM
elements. To get a reference to the
actual DOM elements (instead of the
jQuery object), you have two options.
The first (and fastest) method is to
use array notation:
$('#foo')[0]; // equivalent to
document.getElementById('foo') The
second method is to use the get
function:
$('#foo').get(0); // identical to
above, only slower You can also call
get without any arguments to retrieve
a true array of DOM elements.
To get a jQuery wrapper back around the DOM element you just extracted, rewrap it like so:
$( $('#foo')[0] ) //now it's ajQuery element again.
$("fieldset").each(function() {
// code, applied for each fieldset
})

Categories