JQuery working wierdly for function 'find': - javascript

I have the following jQuery line:
$('<html>hi</html>').find('a')
I expect the result to be a wrapped set of one element. However the result is an empty array ([]). Why?
-- EDIT --
For some reason the code below works.
$('<html><div>hi</div></html>').find('a');
Why is this happening?

That's because the html element is stripped when the string is parsed:
> $('<html>hi</html>')
[​hi​​]
i.e. the current collection contains an element that you are trying to find(). As the top-level a element doesn't (and can't) have a descendants the find() call will return an empty collection.
From jQuery documentation:
When passing in complex HTML, some browsers may not generate a DOM that exactly replicates the HTML source provided. As mentioned, jQuery uses the browser's .innerHTML property to parse the passed HTML and insert it into the current document. During this process, some browsers filter out certain elements such as <html>, <title>, or <head> elements. As a result, the elements inserted may not be representative of the original string passed.
edit: The second snippet can find() a element as when the html element is stripped the top-level element of the collection is a div element that does have a descendant.

As in the Documentation of .find() descriped
Get the descendants of each element in the current set of matched elements, filtered by a selector, jQuery object, or element.
$('<html>hi</html>')
will just provide an Object of your a-tag.
Demo
If there are multiple anchor-tags inside your html-string you can filter them, e.g.:
var elem = $('<html>hihi</html>');
var filter = elem.filter(function(){
return $(this).attr('href') === "cnn.com";
});
Demo
Edit
When passing in complex HTML, some browsers may not generate a DOM
that exactly replicates the HTML source provided. As mentioned, jQuery
uses the browser's .innerHTML property to parse the passed HTML and
insert it into the current document. During this process, some
browsers filter out certain elements such as <html>, <title>, or
<head> elements. As a result, the elements inserted may not be
representative of the original string passed.
Source: http://api.jquery.com/jQuery/#jQuery2 down to the Paragraph Creating New Elements
So jQuery uses .innerHTML. According to the docs
Removes all of element's children, parses the content string and
assigns the resulting nodes as children of the element.
So the html-string <html>test</html> gets stripped to <a></a>.
When wrapping a div around the anchor, the anchor stays a descendat of an elemnt and therefore gets found by the .find()-function.

You should read the documentation at Jquery docs about find()
$('html').find('a');
Check this jsfiddle

Related

Why the first element of the return value of the jQuery empty() function is the element itself?

I saw a code as bellow in a javascript function:
A.empty()[0].options.add(new Option('', ''));
and A is a dom select element. It seems A.empty()[0] is refering to the same select element. This means that empty returns an array whose first element is the dom object itself (i.e, A here).
Can somebody explain this behavior or link me somewhere I can read about this behaviour? (Or I completely misunderstood this code!?)
update: looking at the API documentation here does say the return value is a jQuery. Is this the expected behavior of the jQuery functions that their return value's first element is always the element the function is called upon (given the return value is of type jQuery)?
The empty() method removes all child nodes and content from the selected elements.
and adding option to that select element with a blank ''.
empty() just removes any child nodes and returns the original element.
[0] just returns the first DOM element in the collection. jQuery objects contain an "array-like" collection of HTML elements.
Followup:
jQuery objects are always a collection of 0 or more HTML elements. This is one of the best features of jQuery and allows code to be written that does not fall over when there are no matching elements (although accessing [0] will fall over if you try to access a property when the jQuery object has 0 length, but [0] is a non-jQuery way of doing things).

Find <style> tag by class not working

I'm using AJAX to fetch some HTML markup. I want to append some style tags (with a class) from the fetched markup to my own document using find(). However, jQuery does not seem to like the following approach.
(link removed due to lack of reputation)
Could someone shed some light on why this does not work, and point me in the right direction?
Thank you in advance.
Solutions
Making it a native element first (and removing script tags as extra precaution) works. http://jsfiddle.net/T6QCR/5/
Also, a lot simpler, using innerHTML instead of .html() works, as setting innerHTML does not evaluate scripts and allows .find() to function. http://jsfiddle.net/T6QCR/8/
Also, laconbass' answer below.
Thank you for the help!
Parse the HTML chunk rather than just passing it to jQuery
From the jQuery function documentation for the case you are using:
(...) if the string appears to be an HTML snippet, jQuery attempts to
create new DOM elements as described by the HTML. Then a jQuery object
is created and returned that refers to these elements. You can perform
any of the usual jQuery methods on this object.
(...)
If the HTML is more complex than a single tag without attributes, as
it is in the above example, the actual creation of the elements is
handled by the browser's innerHTML mechanism. In most cases, jQuery
creates a new element and sets the innerHTML property of the
element to the HTML snippet that was passed in.
(...)
When passing in complex HTML, some browsers may not generate a DOM
that exactly replicates the HTML source provided. As mentioned, jQuery
uses the browser"s .innerHTML property to parse the passed HTML and
insert it into the current document. During this process, some
browsers filter out certain elements such as , , or
elements. As a result, the elements inserted may not be
representative of the original string passed.
The documentation recomends ussing $.parseHtml()
For explicit parsing of a string to HTML, use the $.parseHTML()
method.
$.filter rather than $.find
As you noted, $.find does not work on this example. I had succeed replacing it with a $.filter call.
// this works
$html.filter('.test');
// this doesn't works
$html.find('.test');
// better if you filter also by tag
// surely you will have more tags other than <style> on the retrieved html
$html.filter('style.test');
See how this applies to your example on this fiddle.
body is not defined, the console gives an indication of this by way of an error, too. If you want to use jQuery to select the markup body and append the style then you will need to use an appropriate selector:
$("body").append($style);
<style> element can't has class attribute, because Uncaught SyntaxError: Unexpected identifier.
This code shoud work:
var html = '<html><head><style>aaa</style></head></html>';
var $html = $.parseHTML(html);
$.each($html, function(i, el) {
if(el.nodeName == "STYLE") {
$("head").append(el.outerHTML);
return false;
}
});
I'm not really sure as to why this is, but it has something to do with the document model and how it works. You can't just hold a temporary var with the html text in it, you need to put it all inside an element (like a div) that is attached to the document in some way. This div could be hidden from view from the user.
<html>
<head>
<div></div>
</head>
<body>
<script>
jQuery(document).ready(function($) {
$('div').hide().html('<html><head><style class="test"></style></head></html>'); // From AJAX request
var $style = $('div').find('.test');
document.body.appendChild($style[0]);
});
</script>
</body>
</html>

Explanation on 'live' elements

I just read this article about NodeLists:
http://www.nczonline.net/blog/2010/09/28/why-is-getelementsbytagname-faster-that-queryselectorall/
If I understand correctly getElementsByTag name is live and querySelectorAll is not.
Then could someone please explain to my why pNotLive has the title 'stackoverflow'? :
var pLive = document.getElementsByTagName( 'p' )[3];
var pNotLive = document.querySelectorAll( 'p' )[3];
pLive.title = "stackoverflow"
console.log( pNotLive.title ); // stackoverflow
//you can run this snippet in your console to verify
As your link explains, the getElementsByTagName method returns a collection that is automatically updated when the DOM changes. So if you call the method, and then a new element is added to the DOM, your collection will automatically be updated with the new element.
Where as, if you use querySelectorAll, you will be given a static list of DOM elements, that will NOT be updated automatically.
I believe the reason for the behaviour you have used in your example is because the list is static, and not the elements themselves. So the static approach just means the list will not change, so adding/removing elements will not change your list. But when you call for the title attribute, your list is simply pointing to the element, and that element is different from when you created the list.
In short, it is more of a list of references (to the DOM nodes), than a list of data objects (with the fixed data).

What is the difference between appendChild, insertAdjacentHTML, and innerHTML

I want to know what the difference is between appendChild, insertAdjacentHTML, and innerHTML.
I think their functionality are similar but I want to understand clearly in term of usage and not the execution speed.
For example, I can use innerHTML to insert a new tag or text into another tag in HTML but it replaces the current content in that tag instead of appends.
If I would like to do it that way (not replace) I need to use insertAdjacentHTML and I can manage where I want to insert a new element (beforebegin, afterbegin, beforeend, afterend)
And the last if I want to create (not insertion in current tag) a new tag and insert it into HTML I need to use appendChild.
Am I understanding it correctly? Or are there any difference between those three?
element.innerHTML
From MDN:
innerHTML sets or gets the HTML syntax describing the element's descendants.
when writing to innerHTML, it will overwrite the content of the source element. That means the HTML has to be loaded and re-parsed. This is not very efficient especially when using inside loops.
node.appendChild
From MDN:
Adds a node to the end of the list of children of a specified parent node. If the node already exists it is removed from current parent node, then added to new parent node.
This method is supported by all browsers and is a much cleaner way of inserting nodes, text, data, etc. into the DOM.
element.insertAdjacentHTML
From MDN:
parses the specified text as HTML or XML and inserts the resulting nodes into the DOM tree at a specified position. [ ... ]
This method is also supported by all browsers.
....
The appendChild methods adds an element to the DOM.
The innerHTML property and insertAdjacentHTML method takes a string instead of an element, so they have to parse the string and create elements from it, before they can be put into the DOM.
The innerHTML property can be used both for getting and setting the HTML code for the content of an element.
#Guffa did explain the main difference ie innerHTML and insertAdjacentHTML need to parse the string before adding to DOM.
In addition see this jsPerf that will tell you that generally appendChild is faster for the job it provides.
One that I know innerHTML can grab 'inner html', appendChild and insertAdjacentHTML can't;
example:
<div id="example"><p>this is paragraph</p><div>
js:
var foo = document.getElementById('example').innerHTML;
end then now
foo = '<p>this is paragraph</p>';
DOCS:
appendChild
insertAdjacentHTML
innerHtml
innerHTML vs appendChild() performance
insertAdjacentHTML vs innerHTML vs appendChild performance
the main difference is location (positioning) :
(elVar mean element saved to variable)
** elVar.innerHTML: used to sets/get text and tags (like ) inside an element (if u use "=" it replace the content and "+=" will add to the end.
** divElvar.appendChild(imgElVar): to add pure element to the end of another element (or start with prepend) .
** insertedElVar.insertAdjacentElement(beforebegin,targetElvar): it insert element into spicific location before elVar (after it with "afterend").
-innerText: can replace/get/insertOnEnd text.but can read tags and text inside element with display:hidden , cant insert on start .
-innercontent : show all text inc hidden , cant read html tags and it put empty spaces instead of them , cant insert on start
-innerHTML: read all set all , cant insert on start
-prepend : insert text at start of elvar (but cant use to get/replace text or html)
prepend was needed for start, after it made its easy to make append , not for a need , its just bcz lol

jQuery's after method not working with newly created elements

Insofar as I can tell, the following code should work, creating a <div> element, and then creating a <p> element; the expression should result in a jQuery object with two elements:
$("<div>first element's content</div>").after("<p>second element's content</p>");
However, what I get is very different. The documentation (see the heading "Inserting Disconnected DOM Nodes") tells me the above code should result in a jQuery object, grabbing both HTML snippets and building the two DOM elements. But, what I've gotten, in several different versions of jQuery, all above 1.4, is a jQuery object with only 1 node. However, the following code works just fine, returning (what I believe is) the correct jQuery object, two elements inside:
$("<div></div>").after("<p>second element's content</p>");
And this example works as well:
$("<div></div>").after("<p>second element's content</p>").after("<p>third element's content</p>");
It seems the .after() method works fine if the first DOM node being created is empty, but does not when it is not (irrespective of the contents of subsequent DOM nodes being appended to it).
Am I missing something about jQuery's internals, quirky DOM issues and/or JavaScript peculiarities, or is this simply a jQuery bug that's persisted from version 1.4 on through 1.7?
(Here's a meager JSFiddle demonstrating the issue pretty plainly.)
This was a known bug in jQuery < 1.9. See http://bugs.jquery.com/ticket/8759
In jQuery >= 1.9, the following information from the upgrade guide should be noted:
Prior to 1.9, .after(), .before(), and .replaceWith() would attempt to add or change nodes in the current jQuery set if the first node in the set was not connected to a document, and in those cases return a new jQuery set rather than the original set. This created several inconsistencies and outright bugs--the method might or might not return a new result depending on its arguments! As of 1.9, these methods always return the original unmodified set and attempting to use .after(), .before(), or .replaceWith() on a node without a parent has no effect--that is, neither the set or the nodes it contains are changed.
Use add() to add objects to the collection. I use after() more in DOM elements that already exist or that are cached in a variable, but most of the time, if you work with dynamic markup is more practical to use the equivalent insertAfter().
$("<div>first element's content</div>").add("<p>second element's content</p>");
EDIT:
This works...
var $el = $('<div/>', {
text: 'hey there'
}).after('<p>Lorem</p>');
I found I was still sometimes having issues with .add() in place of .after(), so another easy way to get around this bug is to make a throw away wrapper element, .append() the sibling elements, and use .html() to just get the inner contents of the wrapper.
Example:
$('body').append(
$('<span/>').append(
$("<div>first element's content</div>")
).append(
$("<p>second element's content</p>")
).html()
);
This will add the <div> and <p> but discard the outer <span>. I have found this usually works fine with .append() but can have problems with .appendTo()
In jQuery 1.12.4, I found out that using a class selector instead of an ID selector solves this issue.
If you are struggling with
    $("#myelement1").after("<p>test</p>"),
add a unique class to your element and try this:
    $(".myelement1").after("<p>test</p>")

Categories