Given the following HTML structure:
<div class="wrap">
<div id="a"></div>
<div id="b"></div>
</div>
the following is false:
($('#a').parent() == $('#b').parent()); //=> false
even though:
$('#a').parent().children('#b').length; //=> 1
Could anyone explain why? Thanks!
I'm not 100% on exactly why it doesn't work, but I believe it is because the elements are wrapped in jQuery objects which are inherently different per element.
As a workaround, you can compare the native DOM object, like this:
($('#a').parent()[0] == $('#b').parent()[0]); // true
Example fiddle
Because of the same reason that $('#a) == $('#a') is false
Each time jQuery builts a set of elements, it returns a new object (even if the jQuery object wraps the same elements as another). In JavaScript, the only time an object is equal to another, is if it's exactly the same object;
var a = {
foo: 1
};
var b = {
foo: 1
};
(a == b) // false;
To fix this, you can either compare the DOM objects directly (either by using .get(i) or using the jQuery object like an array ([i])), or you get use the is() method;
if ($('.foo').get(i) == $('.bar').get(i));
if ($('.foo')[0] == $('.bar')[0]);
if ($('.foo').is($('.bar')); // or even...
if ($('.foo').is('.bar'));
Related
I'm trying to figure out the vanilla equivalent of the following code:
$(document).attr('key', 'value');
So far I've looked into
document - it's not an element so you cannot call setAttribute on it
document.documentElement - returns the html tag. This is not the same "element" that jquery is targeting
$(document)[0] seems to return a shadow element in Chrome Inspector
$(document).attr('key', 'somethingUnique') doesn't exist in the Chrome Inspector
Is jQuery creating it's own shadow element mock of the document so it can treat it like a real element? What element is jQuery actually referencing when you do $(document)?
A jQuery results set is an array like object that in general holds DOMElement, but jQuery does not really care about what type the objects in the result set have. Neither the DOMElements nor any other element that is stored within the jQuery result set is somehow mocked/wrapped, they are directly stored in the result set. jQuery tries to figure out what it has to do to those objects by looking at their available functions.
When you call .attr, jQuery checks for each object in the set if it has the function getAttribute if this is the case it assumes that it also has a function setAttribute.
If it does not have a function getAttribute, then it will forward the function call to the .prop() function, and prop will internally just do:
elem[name] = value
So if you pass a plain object to jQuery, then it will just set its property.
var a = {
}
$(a).attr('test', 'hello world');
console.dir(a) // for `a` the property `test` is set
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
If you add a getAttribute and setAttribute function to that object then jQuery will call those:
var a = {
getAttribute() {
},
setAttribute() {
console.log('setAttribute was called')
}
}
$(a).attr('test', 'hello world');
console.dir(a);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
However, you should always assume, that the way and order how jQuery does these tests might change. Moreover, only rely on it if it is mentioned explicitly in the documentation.
I believe you're incorrect about $(document) not referring to document, thus, the answer is (quite simply):
document['key'] = 'value'
E.g. In Chrome:
> $(document)[0] === document
true
> $(document).attr('blarg', 'narf')
n.fn.init [document, context: document]
> document.blarg
"narf"
> document.foo = 'bar'
"bar"
> document.foo
"bar"
jQuery is just assigning the value to document directly.
$(document).attr('test', 'hello world');
console.log(document['test']); // prints "hello world"
I really thought jQuery would have wrapped DOM elements, since for some reason, I never write var x = $('#x') to reuse it later but recall $.
That´s why I wrote:
Yes it is wrapped
But after reading #t.niese answer here I tried
var x = $('#x')
var y = $('#y')
var ref = x[0]
x[0] = y[0] // hack jQuery Object reference to DOM element
setTimeout(() => x.html('1'), 1000) // actually writes in #y
setTimeout(() => x.html('2'), 2000) // actually writes in #y
setTimeout(() => { x.push(ref) }, 2500) // hack jQuery Object reference to DOM element
setTimeout(() => x.html('3'), 3000) // actually writes in both #x and #y
And understood I don't write var x = $('#x') not because it is a wrapped object, but exactly because it is not a wrapped object.
I thought the entry point of the API was $, but I may see the API like var api = $(), and the entry point as (el) => api.push(el) or (sel) => api.push(document.querySelector(sel))
I can $().push but I can not $().forEach nor shift nor unshift but yes delete an index, also
In the example
setTimeout(() => { x.map((item) => {console.log(item)}) }, 3500)
logs 0 and 1, not the elements. Tested using jQuery version 3.3.1
I'm trying to obtain some insight in the context of a jQuery object. I've read a ton of questions on SO, but they all want to fix a specific problem, and the answers thus fix the problem, and didn't really provide me with the knowledge I'm hoping for.
So I have two buttons, and I have a click event that listens for both of them like so:
$('#copyRI, #copyIR').click(function () {...}
Now when I try to establish which element was clicked like so:
$('#copyRI, #copyIR').click(function () {
var test = $(this);
var test2 = $('#copyIR');
var compare = (test === test2);
}
compare returns false when I click the button with id = #copyIR
when debugging I noticed that the context of test and test2 are different:
Now I'm not looking for a way to successfully establish which button was clicked, but I want to obtain some insight in the concept of the "context" in a jQuery object.
Thanks in advance!
When you call $(this) you create a new jQuery object, instantiating it with an HTML Element.
When you call $('#copyIR') you create a new jQuery object, instantiating it with a selector. This stores extra information in the object, including the selector itself.
Even if that wasn't the case, you would be creating two different objects and === (and == for that matter) test if two objects are the same object not if they are identical objects.
$(this) === $(this) would also return false.
If you want to test if the elements are the same, then you can compare the underlying DOM nodes (since those will be the same object)
var compare = (test[0] === test2[0]);
Alternatively, you can just test if the object you have in the first place matches the selector:
var compare = test.is('#copyIR');
You should rather use .is() method here.
Check the current matched set of elements against a selector, element, or jQuery object and return true if at least one of these elements matches the given arguments.
CODE:
$('#copyRI, #copyIR').click(function () {
var test = $(this);
var compare = test.is('#copyIR')
}
jQuery contexts (being an Object) are compared by reference, so test === test2 would obviously return false since each variable is pointing to a different jQuery context (the fact that both contexts internally contains a reference to same DOM object doesn't matter).
Try is() instead:
var compare = $(this).is('#copyIR');
See Documetnation
You could simply compare the id.
$('#copyRI, #copyIR').click(function () {
if (this.id === 'copyIR') {
// do something
}
}
I'm building a phonegap project using jQuery mobile.
I have a javascript object that I'm iterating through.
Currently the problem is this:
Below is a method in my model object. It is self recursing, and once called, will recurse through itself to the next level every time a user clicks on a list item generated by the previous level of the object.
What I am battling with is passing the iterated segment, b, into the method itself as an object. For some reason this is returned as a string called [Object], and not the object itself.
This function does work as it's displaying the first level, but something about the "firstString" string I am creating for each child seems to be turning my object into a string named object. I have removed the quotes, placed the object in braces, to no avail.
Would anyone have any idea why this is happening, I'm obviously missing something important regarding passing objects into methods whose call is generated as a string...
My code is below, and line causing the issue is firstString+="model.recurseAppTree('"+b+"');";
recurseAppTree: function(AppTree)
{
$.each(AppTree, function(a,b)
{
var firstString='<li data-role="list-divider" role="heading" data-theme="b">'+b.DisplayValue+'</li>';
if(b.Children != null)
{
$.each(b.Children, function(c,d)
{
firstString+="<li data-theme='c'><a data-transition='slide' id='id-"+d.IdValue+"' href='javascript:void(0);'>"+d.DisplayValue+"</a></li>";
firstString+="<script>";
firstString+="$('#id-"+d.IdValue+"').click(function(){";
firstString+="model.recurseAppTree('"+b+"');";
firstString+="});";
firstString+="</script>";
});
}
$("#selectview").html(firstString);
$("#selectview").listview('refresh', true);
});
},
It's just normal.
You use an object in a string context by the concatenation with +. This tells JS to implicitely cast the object to a string.
b = {}
alert(typeof b) // object
alert(typeof (''+b)) // string
You should use event delegation for your gui
1- Add a (common) class to your '' tags, e.g. unrollLink :
var firstString='<li ...><a class="unrollLink" ...></a></li>"
2- Choose a node in your html, which is a parent of all your "tree" nodes, and will always be present in your html. Delegate the click handler to this node :
$('#selectview').on('click', '.unrollLink', function(){
//this === clicked link - write a function which returns the node you want based on the "id" you set
var myNode = getNode( this.id );
model.recurseAppTree( myNode );
});
3- change your function to produce the adequate html. You don't need to add code for the click events :
recurseAppTree: function(AppTree)
{
$.each(AppTree, function(a,b)
{
var firstString='<li data-role="list-divider" role="heading" data-theme="b">'+b.DisplayValue+'</li>';
if(b.Children != null)
{
$.each(b.Children, function(c,d)
{
// add the class you chose to the clickable items :
firstString+='<li data-theme="c"><a class="unrollLink" data-transition="slide" id="id-'+d.IdValue+'" href="javascript:void(0);">'+d.DisplayValue+'</a></li>';
});
}
$("#selectview").html(firstString);
$("#selectview").listview('refresh', true);
});
},
This question already has answers here:
How can I test if two jQuery wrapped DOM elements are the same? [duplicate]
(3 answers)
Closed 7 years ago.
var a=$('#start > div:last-child');
var b=$('#start > div.live')[0];
alert(a==b)
alert(a==$(b))
It's always false. How can you compare two elements in jQuery?
thanks
For the record, jQuery has an is() function for this:
a.is(b)
Note that a is already a jQuery instance.
You could compare DOM elements. Remember that jQuery selectors return arrays which will never be equal in the sense of reference equality.
Assuming:
<div id="a" class="a"></div>
this:
$('div.a')[0] == $('div#a')[0]
returns true.
Every time you call the jQuery() function, a new object is created and returned. So even equality checks on the same selectors will fail.
<div id="a">test</div>
$('#a') == $('#a') // false
The resulting jQuery object contains an array of matching elements, which are basically native DOM objects like HTMLDivElement that always refer to the same object, so you should check those for equality using the array index as Darin suggested.
$('#a')[0] == $('#a')[0] // true
a.is(b)
and to check if they are not equal use
!a.is(b)
as for
$b = $('#a')
....
$('#a')[0] == $b[0] // not always true
maybe class added to the element or removed from it after the first assignment
Random AirCoded example of testing "set equality" in jQuery:
$.fn.isEqual = function($otherSet) {
if (this === $otherSet) return true;
if (this.length != $otherSet.length) return false;
var ret = true;
this.each(function(idx) {
if (this !== $otherSet[idx]) {
ret = false; return false;
}
});
return ret;
};
var a=$('#start > div:last-child');
var b=$('#start > div.live')[0];
console.log($(b).isEqual(a));
The collection results you get back from a jQuery collection do not support set-based comparison. You can use compare the individual members one by one though, there are no utilities for this that I know of in jQuery.
I always wondered why jQuery returns true if I'm trying to find elements by id selector that doesnt exist in the DOM structure.
Like this:
<div id="one">one</div>
<script>
console.log( !!$('#one') ) // prints true
console.log( !!$('#two') ) // is also true! (empty jQuery object)
console.log( !!document.getElementById('two') ) // prints false
</script>
I know I can use !!$('#two').length since length === 0 if the object is empty, but it seems logical to me that a selector would return the element if found, otherwise null (like the native document.getElementById does).
F.ex, this logic can't be done in jQuery:
var div = $('#two') || $('<div id="two"></div>');
Wouldnt it be more logical if the ID selector returned null if not found?
anyone?
This behaviour was chosen because otherwise jQuery would regularly throw NullReference Exceptions
Almost all jQuery functions return a jQuery object as a wrapper around the Dom elements in question, so you can use dot notation.
$("#balloon").css({"color":"red"});
Now imagine $("#balloon") returned null. That means that $("#balloon").css({"color":"red"});
would throw an error, rather than silently doing nothing as you would expect.
Hence, you just gotta use .length or .size().
This is just how jQuery works.
$("#something")
Object 0=div#something length=1 jquery=1.2.6
$("#nothing")
Object length=0 jquery=1.2.6
You can come close to doing what you want by accessing the length the element, and combine with the ternary operator:
console.log(!!$('#notfound').length); // false
console.log(!!$('#exists').length); // true
var element= $('#notfound').length ? $('#notfound') : $('#exists');
console.log(element.attr('id')); // outputs 'exists'
As to the heart of the question:
Wouldnt it be more logical if the ID
selector returned null if not found?
No, not for the JQuery way of doing things - namely, to support chaining of JQuery statements:
$('#notfound').hide("slow", function(){
jQuery(this)
.addClass("done")
.find("span")
.addClass("done")
.end()
.show("slow", function(){
jQuery(this).removeClass("done");
});
});
Even though notfound doesn't exist this code will run without stopping script execution. If the initial selector returns null, you'll have to add in an if/then block to check for the null. If the addClass, find, end and show methods return null, you'll have to add an if/then block to check the return status of each. Chaining is an excellent way to handle program flow in a dynamically typed language like Javascript.
It returns true because to Javascript it is a defined object therefore not false, and jQuery will always give you a new object regardless of whether the element is found or not - however the array length will be zero, e.g.
$("span").length
If you have no <span>, this will be zero, but it could be 1 or more.
You can write your own plugin to avoid repeated if statements as a Jquery plugin, like I did for this one. It's fairly easy to do:
(function($)
{
/* Checks if a jQuery object exists in the DOM, by checking the length of its child elements. */
$.fn.elementExists = function()
{
/// <summary>
/// Checks if a jQuery object exists in the DOM, by checking the length of its child elements.
/// </summary>
/// <returns type="Boolean" />
return jQuery(this).length > 0;
};
})(jQuery);
Usage:
if ($("#someid").elementExists())
{
}
You could check the .length property of the jQuery object. Like this:
if($("#two").length > 0) { // exists...
} else { // doesn't exist
}
In short, you could think of the jQuery selector return value as a group containing 0..n elements, but never being null.
What you're probably really interested in is $("#two")[0], which will give you the first actual element returned by the selector.