I've read somewhere that when you capture a jQuery object in a variable, say:
div = $("#someDiv");
A screenshot of that element is captured, so if you do something like this:
div2 = $("#someDiv"); // another handle
div2.css('background-color', 'rgb(12,75,54)');
and then do:
div.css('background-color'); // should NOT output "rgb(12, 75, 54)"
And yet, this is what happens, the div handle is aware of any changes that happen to the element. So I was thinking: maybe this behavior is introduced to a newer version of jQuery? Was this always true for all jQuery versions?
Even though div = $("#someDiv"); creates a different jQuery object than div2 = $("#someDiv"); the actual dom element within those jQuery objects is still the same
There can only be one of those and objects are passed as reference not by copying them.
Anything that happens within the dom node represented by div will be reflected in div2's dom node....they are one and the same
Related
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")
jQuery (all versions tested up through 2.1.0) allows me to call .val("some value") on a DIV object to set a value on the DIV. It is not displayed and doesn't show up as an HTML5 data property in Chrome Developer Tools. And yet I can fetch the result later with a call to .val().
For example (from http://jsfiddle.net/X2nr6/ ):
HTML:
<div id="mydiv" style="display: none;">Some text</div>
<div id="debug"></div>
Javascript:
$('#mydiv').val('A value attached .');
$('#debug').text( $('#mydiv').val() );
Displayed result:
A value attached.
Where is the value stored? Not knowing where it is stored makes me worry that I am relying on a hack.
jQuery is just assigning to a value property on the div object (the HTMLDivElement instance for that div), even though it doesn't normally have one. Creating new properties on elements is allowed in every browser I've ever seen, so it works. I wouldn't use val with divs on a regular basis, though.
Here's a non-jQuery example:
var div = document.createElement('div');
console.log('value' in div); // false, divs don't normally have a value property
div.value = 42;
console.log('value' in div); // true, we've created a property on the element
console.log(div.value); // 42
Or the same sort of thing using jQuery:
var $div = $("<div>");
display(typeof $div.prop('value'));
$div.val(42);
display(typeof $div.prop('value'));
display($div.prop('value'));
This business of creating new, custom, non-standard properties on elements is called creating "expando" properties. They can be very handy. (jQuery uses them internally, for instance, to manage the data cache and a few other things — if you look closely at a DOM element you've set data on using data, you'll see a property with a name like jQuery1110028597884019836783; that's the key jQuery uses to find the element's data in jQuery's internal data cache. jQuery doesn't store the data in an expando on the element, because of IE garbage collection issues; it stores the key there, and the data in a JavaScript object.)
It stores it on a value property on the DOM object. You can see if by running your code and then inspecting the element in a DOM inspector. In Chrome, the value property will be listed under div#mydiv in the properties tab.
HTMLDivElement objects don't officially support such a property, so you are relying on a hack.
Use data() to store arbitrary data on an element.
$('#mydiv').data("myCustomValue", 'A value attached .');
Although the above answers are accurate, I'd like to complete something out.
jQuery is designed around the concept of wrapping all HTML elements in the jQuery object. That jQuery object happens to be an array that can hold more than one element.
jQuery also goes out of its way to hide this fact from you so that the average jQuery developer never has to worry about exactly what he has -- simply call the right method and the magic happens.
(You see this if you do a $(".someClassYouHaveLotsOf").hide() or $(".someClassYouHaveNoneOf").hide()`.)
jQuery's val() method is just a wrapper for accessing an HTML input element's value property. Since jQuery doesn't throw errors unless there is really no way what-so-ever, it silently helps you by accessing the value property on whatever HTML element it happens to have. div span or whatever.
In most browsers, this works -- mostly enough.
If you are really interested in setting values on HTML elements for use later, the data() method is far better suited. Straight HTML would use <element>.setAttribute("data-key", "value");
And that is about the only time you'll see me using the HTML attributes over properties, BTW.
html segment:
<div class="container">
<div id="one">Div #1</div>
<div id="two">Div #2</div>
</div>
if I set $p as a jQuery object, as below:
$p = $('<p>whatever you like</p>');
//$p = '<p>whatever you like</p>';
$('#one').after($p);
$('#two').after($p);
then the result would be:
Div #1
Div #2
whatever you like
While I set $p as htmlString, as below:
//$p = $('<p>whatever you like</p>');
$p = '<p>whatever you like</p>';
$('#one').after($p);
$('#two').after($p);
then the result would be:
Div #1
whatever you like
Div #2
whatever you like
Seems while I use object, jQuery doesn't clone the object, but just move it; while I use htmlString, it create a new object accordingly each time.
I want to know the exact reason. It would be more appreciated if any reference could be provided as well.
Many thanks!
Seems while I use object, jQuery doesn't clone the object, but just move it; while I use htmlString, it create a new object accordingly each time.
Correct. The first time you call after, you put the elements inside the jQuery object into the DOM. The second time, they get moved. This is the standard DOM behavior.
It's documented, somewhat indirectly, on the after page:
If an element selected this way is inserted into a single location elsewhere in the DOM, it will be moved rather than cloned:
This echoes the standard behavior of the DOM methods appendChild and insertBefore.
Note that if you call after on a jQuery set that has multiple elements in it, the elements you're passing in will get moved to after the first target element and then cloned to go after the remaining target elements:
Important: If there is more than one target element, however, cloned copies of the inserted element will be created for each target except for the last one.
Example
This second behavior is jQuery-specific (although with sufficient hand-waving one might argue it's similar to how the DOM handles document fragments and, on more modern browsers, template elements).
When $p is a string '<p>whatever you like</p>' and you append it using .after(), the string is added to the HTML. It's never a jQuery object.
When $p is a jQuery object $('<p>whatever you like</p>'); and you're appending it using one of the append functions like .after() you're moving it around the page. If you don't want to move it, clone it.
$('#one').after($p.clone());
$('#two').after($p.clone());
Demo
My map application page has a div dedicated to containing a widget, such as a help window or a list of search results, floating above the map. Either a single widget or nothing is shown at any given time. There's a button for each widget, and clicking it either opens that widget (replacing what was in the div) or closes it if it's the widget that is currently open.
This is one of the functions that handles these button clicks (the aforementioned div is widgets.main):
_openSearch: function() {
if (widgets._activeWidget == widgets._searchWidget){
domConstruct.empty(widgets.main);
widgets._activeWidget = null;
} else {
widgets._searchWidget.show();
widgets._activeWidget = widgets._searchWidget;
}
},
This is the function that gets called:
show: function() {
domConstruct.empty(this.rootDiv);
this.rootDiv.appendChild(this.widgetDiv);
},
widget.widgetDiv is the div element with the actual content of the widget. My intention was to generate the content once, then keep it stored in widgetDiv if the widget is closed. This works as expected in IE and Chrome, but IE (IE8 in particular) gets rid of everything inside widgetDiv when domConstruct.empty() is called.
How do I get the desired behavior in IE? Also, which behavior is the "right" one, standards-wise?
Dojo's domConstruct.empty() (see docs here) just destroys all children – it actually sets innerHTML to an empty string for non-SVG elements.
removeChild may be a little less performant but it seems to fit your use case a little better, since it returns the removed nodes. Perhaps something like the following would work:
show: function() {
// save a reference to the current children of rootDiv to use later
var oldChild = this.rootDiv.removeChild(this.rootDiv.firstChild);
this.rootDiv.appendChild(this.widgetDiv);
}
Make sure that rootDiv has exactly one child that you want to save. If it doesn't, you could wrap it in a <div>.
I create a new window when i press a button. This window contains html input elements that i want to manipulate with jquery, but i can't catch the elements. Normally i would use the live function because the html is first added to the dom when the button is pressed, but its not working.
jQuery(document).ready(function () {
var opretKnap = jQuery("input[value='Open window']");
jQuery(opretKnap).live('click', function () {
var inputsDate = jQuery("input[vdfDataType]");
});
});
jQuery("input[vdfDataType]");
What's vdfDataType? That's not a standard HTML attribute. Are you meaning to use custom attributes? (It's a generally questionable strategy, especially when you want to select on them.)
Is the element you're trying to get in the current document? You say it's a ‘new window’, but if you actually mean a new window and not an in-page DOM pop-up, you won't be able to select it from the document that opened it.
jQuery(opretKnap).live
should be avoided:
the .live() method should always be called directly after a selector
That is, the argument in the jQuery() wrapper immediately before the live() call should be a selector string, such as "input[value='Open window']" directly. opretKnap is a jQuery wrapper already, so opretKnap.live() would be OK.
jQuery("input[value='Open window']")
Avoid using value as an attribute selector. Apart from it not really being very specific (what happens if a text field somewhere happens to have that string in it?), it's also unreliable for many cases in jQuery.
The value HTML attribute and the value DOM property are two different things for text inputs (and some others); the property gives the current field value, whereas the attribute gives the original value that was specified in the HTML before any user input. This attribute maps to the DOM defaultValue property, not value.
However, a bug in the Sizzle selector engine used by jQuery means it'll read the value property in preference. This would give the ‘wrong’ (but possibly desired) results... but not consistently, because in many cases the browser's own querySelectorAll call will be used for speed, short-cutting Sizzle's bug.
Whilst this might not affect you (eg. if you're selecting a button, whose value will never change), you should consider the [value=...] selector to be highly suspect, and avoid it whenever possible. Find another way to pick the particular input you want, such as a .class, #id, [name="something"] or [type=submit] if that's what it is.