35

I'm writing tests with QUnit and using $.ajax() to pull in HTML for some of the tests from the the dev site running on my local:

add_elements = function(location, selector) { 
  $.ajax(location, {async: false}).done(function(data) {
    stor.$els = $(selector, $.parseHTML(data));
    stor.$els.appendTo($('body'));
  })
}

Using this function at a certain location, I get the following data passed to my .done() callback:

<!DOCTYPE html>
<html lang="en">

<head>
    <title>Home</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="/static/css/bootstrap.min.css" rel="stylesheet" media="screen">

</head>

<body>
<div id="app-container" class="container-fluid">
    <div class="page-header">
        <h1>
            <a href="/">Home</a>
            <small>Text</small>
        </h1>
    </div>

    <div id="hero-units" class="carousel-inner slide">

        <div class="hero-unit home item active">
            <h1>
                Text text text
            </h1>
            <p>
                More text!
            </p>
            <div id="app-nav">
                <a id="lets-go" class="btn btn-primary btn-large nav-forward" href="/what-up/">
                    Let's go
                </a>
            </div>
        </div>

    </div>
</div>

<script src="/static/js/jquery.js"></script>
<script src="/static/js/underscore.js"></script>
<script src="/static/js/backbone.js"></script>
<script src="/static/js/bootstrap.js"></script>
<script src="/static/js/site-fiddle.js"></script>
<script src="/static/js/site.js"></script>

</body>
</html>

Everything works if selector is #hero-units or .hero-unit, but $(selector, $.parseHTML(data)) returns nothing if selector is #app-container! And I want a jQuery object for the div#app-container element.

And here is what kills me:

  • $.parseHTML(data) does contain the div#app-container element. It's just $.parseHTML(data)[7].
  • Yet, $('#app-container', $.parseHTML(data)) is an empty array.
  • $('div', $.parseHTML(data)) includes all the divs inside of div#app-container, but not div#app-container itself.

What's going on here? It appears that what's happening is that $ is not looking at any of the top-level elements returned by $.parseHTML(data) or $($.parseHTML(data))), and just their children.

How can I get a jQuery object for div#app-container from this $.parseHTML(data)?

ANSWER

The $(selector, $.parseHTML(data))-style lookup uses $.find. Since I'm looking for an element that's top-level in this jQuery object, I should use $.filter instead. Voila.

Dmitry Minkovsky
  • 36,185
  • 26
  • 116
  • 160
  • 1
    thanks for your update with `filter` much nicer than the answer below. although that would also have worked :) – MMachinegun Jun 02 '14 at 15:00
  • 1
    Can you provide an example of `$.filter` working to get the outermost node of the output from `$.parseHTML`? How do you pass the output of parseHTML to `$.filter` ? It only accepts one argument. – Ade Nov 20 '14 at 17:33

7 Answers7

41

You need to create a DOM element to append the results of .parseHTML() first so that it creates a DOM tree before jQuery can traverse it and find div#app-container.

var tempDom = $('<output>').append($.parseHTML(str));
var appContainer = $('#app-container', tempDom);

I used your example and got it working: http://jsfiddle.net/gEYgZ/

The .parseHTML() function seems to choke on the <script> tags, so I had to remove them.

PS. Obviously <output> is not a real HTML tag, just using it for the example

jcubic
  • 61,973
  • 54
  • 229
  • 402
Amy
  • 7,388
  • 2
  • 20
  • 31
  • Yeah, you're right. It needs to be appended inside of something _first_, and then searched. jQuery isn't finding top-level tags in this case. I wonder why. – Dmitry Minkovsky Mar 14 '13 at 07:59
  • Thanks for the fiddle. I was thinking of making one. Should have done that before posting. – Dmitry Minkovsky Mar 14 '13 at 08:00
  • @dimadima It's not about top-level tags, the result of `$.parseHTML(str)` is not a DOM element yet. It's kind of in a purgatory state, if you will, and needs to be brought into the DOM world. Either by `.append()`, `.html()`, etc. – Amy Mar 14 '13 at 08:01
  • 1
    really? i would have thought wrapping it in `$()` would be sufficient. like, from http://jquery.com/upgrade-guide/1.9/: If a string is known to be HTML but may start with arbitrary text that is not an HTML tag, pass it to `jQuery.parseHTML()` which will return an array of DOM nodes representing the markup. A jQuery collection can be created from this, for example: `$($.parseHTML(htmlString))`. This would be considered best practice when processing HTML templates for example." – Dmitry Minkovsky Mar 14 '13 at 08:02
  • 1
    @dimadima Maybe bring this up with the folks at jQuery :P – Amy Mar 14 '13 at 08:03
  • maybe, right? but i think this must be something of a purgatory, something of a design decision. i think the issue is that there's no way to specify that the `.find` context should be inclusive – Dmitry Minkovsky Mar 14 '13 at 08:04
  • @dimadima I would have to say the `$()` function is already very complex as it is, it can handle so many types of arguments. I'd understand why they didn't want to over complicate it. – Amy Mar 14 '13 at 08:06
  • yeah, i agree. i just didn't know about this behavior, if i'm understanding it correctly – Dmitry Minkovsky Mar 14 '13 at 08:09
  • What does $('#container', tempdo); does? I thought you have to this: $('#container').html( tempdo.html() ) – Martin Zvarík Jan 26 '18 at 20:19
13

You can try this:

$($.parseHTML('<div><span id="foo">hello</span></div>')).find('#foo');

for strings that start with < you can shorter that code to just:

$('<div><span id="foo">hello</span></div>').find('#foo');
jcubic
  • 61,973
  • 54
  • 229
  • 402
4

Just ran into this.

Turns out $.parseHTML returns a vanilla Array of DOM elements, and that doesn't support the jQuery find that you want.

I think this is the cleanest solution for converting an HTML string to a jQuery object:

var html = '<div><span id="foo">hello</span></div>';
var $foo = $(html).find('#foo');

Note: You may want to use $.trim around your html strings so jQuery doesn't get confused by whitespaces.

adu
  • 947
  • 1
  • 8
  • 15
4

It doesn't have anything to do with the html not being part of the DOM. It's just that for whatever reason the top level tag(s) in the parsed HTML cannot be found. If the element you are looking for is wrapped in another tag, then the find works as you expect. BTW, wrapping the element in the body tag won't work, but all the others I have tried work fine.

var bad = $.parseHTML('<ul><<li><one/li>/<li>two</li></ul>');
console.log($(bad).find('li').length);  //will be 2
console.log($(bad).find('ul').length);  //will be 0 

var good = $.parseHTML('<div><ul><<li><one/li>/<li>two</li></ul></div>');
console.log($(good).find('li').length); //will be 2
console.log($(good).find('ul').length); //will be 1

This is definitely the case in jQuery 1.11, not sure what happens in later versions. Here is a fiddle that demonstrates: http://jsfiddle.net/ndnnhf94/

Robert Moskal
  • 21,737
  • 8
  • 62
  • 86
2

Above answers didn't work for me, this did:

use jQuery.parseHTML to parse the HTML into an array of elements; then you’ll be able to convert it to a jQuery collection and use filter to restrict the collection to elements matching a selector.

var html =
'<td class="test">asd</td>' +
'<td class="last">jkl</td>';
var obj = $($.parseHTML(html)).filter('.test');
0

i think you should try with this:

$('body', $.parseHTML(data))
Jai
  • 74,255
  • 12
  • 74
  • 103
0

I believe this will work, especially if you specifically need to find the element by id;

function findInParsed(html, selector){
    return $(selector, html).get(0) || $(html).filter(selector).get(0);
}

If you need the jquery version of the object then you can get it with this

function findInParsed(html, selector){
    var check = $(selector, html).get(0);
    if(check)
        return $(check);
    check = $(html).filter(selector).get(0)
    return (check)? $(check) : false;
}