3

I've been trying to GET a HTML file and assign it to a variable as a jQuery object. To no avail. I'm not sure if Stack Snippets allow GET requests, so here is a JSFiddle link as well.

    var html = '<!DOCTYPE html><html lang="en"><head><title>Template</title></head><body itemscope itemtype="http://schema.org/WebPage"><main><header itemscope itemtype="http://schema.org/Country" itemprop="about"><h1 itemprop="name">Dummy heading</h1><p><span class="capital" title="Capital" itemprop="containsPlace"></span><span title="Dummy title" itemprop="additionalProperty" itemscope itemtype="http://schema.org/PropertyValue"><meta itemprop="name" content="Member of the EU since"><span itemprop="value" class="member-since">Dummy year</span></span></p></header><div itemprop="mainEntity" itemscope itemtype="http://schema.org/ItemList"><meta itemprop="description" content=""><article class="recipe loading" itemprop="itemListElement" itemscope itemtype="http://schema.org/Recipe"><meta itemprop="position" content=""><aside class="media"><div class="img-gallery" itemscope itemtype="http://schema.org/ImageGallery"></div><div itemscope itemtype="http://schema.org/VideoObject" class="youtube"><a itemprop="contentUrl" href="#" title=""><meta itemprop="name" content=""><meta itemprop="uploadDate" content=""><meta itemprop="description" content=""><img itemprop="thumbnailUrl" src="#" alt=""></a><div class="youtube-player"></div></div></aside><div class="text"><div class="wiki-text"><h1 itemprop="name">Dummy heading</h1><p itemprop="description"></p><p class="read-more">For more information about <span class="recipe-name"></span>, read the <a href="#" title="" itemprop="sameAs">Wiki</a>.</p></div><div class="rating" itemprop="aggregateRating" itemscope itemtype="http://schema.org/AggregateRating">Rated <span itemprop="ratingValue">3.5</span>/5 based on <span itemprop="reviewCount">11</span> customer reviews</div><div class="cooking"><h2>Bake it yourself!</h2><div><meta itemprop="cookTime" content=""><span>Bake time: <span class="bake-time"></span></span></div><div class="ingredients-wrapper"><h3>Ingredients <small>for <span itemprop="recipeYield"></span></small></h3><div class="ingredients"><h4>Dummy heading</h4><ul></ul></div></div><div class="how-to"><h3>Steps</h3><ol></ol></div></div></div></article></div></main></body></html>';
    
    $.ajax({
      type: 'post',
      url: "/echo/html/",
      dataType: "html",
      data: {
        html: html,
        delay: 1
      }
    }).done(function(data) {
      // string
     console.log(data);
      // array
      console.log($(data));
      // array
      console.log($.parseHTML(data));
      // array
      console.log($($.parseHTML(data)));
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

The HTML is valid. Yet, I can't get it in an object. The returned data is, as expected, a string. But when I try to parse that string as HTML using $.parseHTML() or putting it in a jQuery selector $() or even try both I always get the same result: an array containing the title and main element.

So some way some how jQuery still parses it and makes an array of the element in the head and the one element in the body. But why? And how can I remedy this, and transform it into a jQuery object? I am only interested in the contents of the body.

There is a similar question, but the accepted solution doesn't help as it goes into JSFiddle and I'm also experiencing this problem locally with XAMPP.

Community
  • 1
  • 1
Bram Vanroy
  • 27,032
  • 24
  • 137
  • 239

4 Answers4

2
document.querySelector("body").innerHTML = + 
  '<object type="text/html" data="'+html+'" ></object>';

...just works. So I'd have expected:

let parsedHtml = $('<object />', {
  type:'text/html',
  data:html
}).html()

to also just work, But I'm guessing some magic happens when one actually adds it to DOM. It parses it, builds CSSOM and DOM for it and loads all dependencies, basically what you'd expect a browser to do with an .html resource.

So here's a way to do it:

  1. Create a dummy placeholder,
  2. place the <object> inside,
  3. append dummy to DOM,
  4. get .contents() of <object> (on it's onload event),
  5. and delete the dummy.
let html = '// your valid html...',
    dummyDiv = $('<div />',{
      html: '<object type="text/html" data="'+html+'" ></object>',
      style:"height:0;overflow:hidden;"
    });

$('html').append(dummyDiv);
console.log(dummyDiv.find('object').contents());

Note Chrome is already smart enough to detect a display:none on a parent and not load the <object>. That's why I used height:0;overflow:hidden;. You will notice the contents of this element doesn't have a typical stucture of a jQuery object, because it's a document. you'll find the juice in

dummyDiv.find('object').contents()[1].innerHTML

When the html string is already in a variable, it loads instantly, but in reality you'll need to place an onload listener on the <object> and only run 4. and 5. when the listener triggers.

tao
  • 82,996
  • 16
  • 114
  • 150
  • Hm, is there any way to do this without adding the element to the DOM first? The thing is I'd like to load the contents in memory, do some modifications on it (cloning, detaching, the whole deal) and then add it to the DOM. In memory, because this ought to be faster than doing DOM modifications. – Bram Vanroy May 10 '17 at 19:16
  • Not that I know of. Thing is this way it gets properly parsed. Otherwise, you can run a few `jQuery` tricks on your string, but it's not a `DOM` element, so not all will be available. For example, running `.find()` on your `$(data)` and selecting by class will work, while selecting by tag will not, because it's not DOM. It's a string. With the above method, you not only wait for ajax to load, but for the resulting DOM to load properly (just in case it has some DOM manipulation scripts). – tao May 10 '17 at 19:21
  • I see. Smart. So, the best idea might be to append it to html/body and then do a `.detach()` into a VAR, effectively putting the parsed contents back into memory, removing the dummy. Manipulate VAR, and add VAR back to the DOM? – Bram Vanroy May 10 '17 at 19:23
  • Yes. jQuery will work on it like it does on normal DOM, because it is normal DOM, even when detached from the existing DOM. However, I don't know how to trigger it's parsing/loading without placing it in DOM. I'm sure it's possible (after all the DOM does it, right?) I just don't know enough about the internal processes. And frankly, as long as I get the result, I don't care much, lol. – tao May 10 '17 at 19:25
  • 1
    I'll leave this question open for a while, add a small bounty, and perhaps some jQuery gurus can enlighten us. `:-)` – Bram Vanroy May 10 '17 at 19:26
1

Set jQuery.ajax() processData option to false. Use DOMParser() to parse string to an html document; XMLSerizlizer() to get DOCTYPE declaration; document.write() to write DOCTYPE and parsed document .documentElement.outerHTML to current or other html document.

processData (default: true)

Type: Boolean

By default, data passed in to the data option as an object (technically, anything other than a string) will be processed and transformed into a query string, fitting to the default content-type "application/x-www-form-urlencoded". If you want to send a DOMDocument, or other non-processed data, set this option to false.


I'm not sure if Stack Snippets allow GET requests

Yes. It is possible to echo GET request by setting url to data URI or Blob URL representation of resource at $.ajax(), XMLHttpRequest() or fetch(), see Does Stack Overflow have an “echo page” to test AJAX requests, inside a code snippet?

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

<head>
  <title>Template</title>
</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <main>
    <header itemscope itemtype="http://schema.org/Country" itemprop="about">
      <h1 itemprop="name">Dummy heading</h1>
      <p><span class="capital" title="Capital" itemprop="containsPlace"></span><span title="Dummy title" itemprop="additionalProperty" itemscope itemtype="http://schema.org/PropertyValue"><meta itemprop="name" content="Member of the EU since"><span itemprop="value" class="member-since">Dummy year</span></span>
      </p>
    </header>
    <div itemprop="mainEntity" itemscope itemtype="http://schema.org/ItemList">
      <meta itemprop="description" content="">
      <article class="recipe loading" itemprop="itemListElement" itemscope itemtype="http://schema.org/Recipe">
        <meta itemprop="position" content="">
        <aside class="media">
          <div class="img-gallery" itemscope itemtype="http://schema.org/ImageGallery"></div>
          <div itemscope itemtype="http://schema.org/VideoObject" class="youtube">
            <a itemprop="contentUrl" href="#" title="">
              <meta itemprop="name" content="">
              <meta itemprop="uploadDate" content="">
              <meta itemprop="description" content=""><img itemprop="thumbnailUrl" src="#" alt=""></a>
            <div class="youtube-player"></div>
          </div>
        </aside>
        <div class="text">
          <div class="wiki-text">
            <h1 itemprop="name">Dummy heading</h1>
            <p itemprop="description"></p>
            <p class="read-more">For more information about <span class="recipe-name"></span>, read the <a href="#" title="" itemprop="sameAs">Wiki</a>.</p>
          </div>
          <div class="rating" itemprop="aggregateRating" itemscope itemtype="http://schema.org/AggregateRating">Rated <span itemprop="ratingValue">3.5</span>/5 based on <span itemprop="reviewCount">11</span> customer reviews</div>
          <div class="cooking">
            <h2>Bake it yourself!</h2>
            <div>
              <meta itemprop="cookTime" content=""><span>Bake time: <span class="bake-time"></span></span>
            </div>
            <div class="ingredients-wrapper">
              <h3>Ingredients <small>for <span itemprop="recipeYield"></span></small></h3>
              <div class="ingredients">
                <h4>Dummy heading</h4>
                <ul></ul>
              </div>
            </div>
            <div class="how-to">
              <h3>Steps</h3>
              <ol></ol>
            </div>
          </div>
        </div>
      </article>
    </div>
  </main>
</body>
</html>`;


$.ajax({
    type: "GET",
    url: "data:text/html," + html,
    processData: false
  })
  .then(function(data) {
    // string
    console.log(data);
    var parser = new DOMParser();
    // document
    var d = parser.parseFromString(data, "text/html");
    // `document`, `document` as jQuery object
    console.log(d, $(d));
    // get elements having `itemscope` attribute
    console.log($(d).find("[itemscope]"));
    // do stuff
    var dt = new XMLSerializer().serializeToString(d.doctype);
    document.write(dt, d.documentElement.outerHTML);
  })
  .fail(function(jqxhr, textStatus, errorThrown) {
    console.log(errorThrown);
  });
  
  
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

It is also possible to import an html document to current document using <link> element with rel attribute set to "import", see How to append a whole html file with jquery.

Community
  • 1
  • 1
guest271314
  • 1
  • 15
  • 104
  • 177
  • You can alternatively use `XMLHttpRequest()` with `.responseType` set to `"document"` to get `#document` as `XMLHttpRequest.response` at `load` or `readystatechange` event, or `fetch()`, `Response.blob()` to get `html` as `Blob`. – guest271314 May 16 '17 at 04:51
  • What is the difference with using dataType: html? Isn't that what it's supposed to do? Process the retrieved content as html? – Bram Vanroy May 16 '17 at 06:03
  • Using `dataType:"html"` returns same result. – guest271314 May 16 '17 at 14:13
  • @BramVanroy That is, the same result should be returned when also using `DOMParser`. – guest271314 May 16 '17 at 14:27
0

Problem is you are trying to access direct html but actually its a string.

So you have to render html to access data.

For that store response data into any html element and access through it.

I changed your jsfiddle so you can see.

Asav Vora
  • 61
  • 6
0

I always get the same result: an array containing the title and main element.

What exactly is the problem?

jQuery still parses it and makes an array of the element in the head and the one element in the body. But why?

What are you expecting?

That seems to be the behavior of parseHtml.

Here's another example:

$.parseHTML("<div>Hello<span>World</span></div>")

It returns an array with one element: div

If you look at the docs for parseHTML it says:

Parses a string into an array of DOM nodes.

So it seems to be doing exactly what it's supposed to.

I am only interested in the contents of the body.

The content of the body is main which as you have noted is the second element.

What do you want to do with it?

You can wrap it in a jQuery object by passing it to the jQuery constructor.

var html = '<!DOCTYPE html><html lang="en"><head><title>Template</title></head><body itemscope itemtype="http://schema.org/WebPage"><main><header itemscope itemtype="http://schema.org/Country" itemprop="about"><h1 itemprop="name">Dummy heading</h1><p><span class="capital" title="Capital" itemprop="containsPlace"></span><span title="Dummy title" itemprop="additionalProperty" itemscope itemtype="http://schema.org/PropertyValue"><meta itemprop="name" content="Member of the EU since"><span itemprop="value" class="member-since">Dummy year</span></span></p></header><div itemprop="mainEntity" itemscope itemtype="http://schema.org/ItemList"><meta itemprop="description" content=""><article class="recipe loading" itemprop="itemListElement" itemscope itemtype="http://schema.org/Recipe"><meta itemprop="position" content=""><aside class="media"><div class="img-gallery" itemscope itemtype="http://schema.org/ImageGallery"></div><div itemscope itemtype="http://schema.org/VideoObject" class="youtube"><a itemprop="contentUrl" href="#" title=""><meta itemprop="name" content=""><meta itemprop="uploadDate" content=""><meta itemprop="description" content=""><img itemprop="thumbnailUrl" src="#" alt=""></a><div class="youtube-player"></div></div></aside><div class="text"><div class="wiki-text"><h1 itemprop="name">Dummy heading</h1><p itemprop="description"></p><p class="read-more">For more information about <span class="recipe-name"></span>, read the <a href="#" title="" itemprop="sameAs">Wiki</a>.</p></div><div class="rating" itemprop="aggregateRating" itemscope itemtype="http://schema.org/AggregateRating">Rated <span itemprop="ratingValue">3.5</span>/5 based on <span itemprop="reviewCount">11</span> customer reviews</div><div class="cooking"><h2>Bake it yourself!</h2><div><meta itemprop="cookTime" content=""><span>Bake time: <span class="bake-time"></span></span></div><div class="ingredients-wrapper"><h3>Ingredients <small>for <span itemprop="recipeYield"></span></small></h3><div class="ingredients"><h4>Dummy heading</h4><ul></ul></div></div><div class="how-to"><h3>Steps</h3><ol></ol></div></div></div></article></div></main></body></html>';

var parsed = $.parseHTML(html)
var main = parsed[1]
var $main = $(main)
// $main is a jQuery object
console.log("h4 content:", $main.find('h4').text())
ハセン
  • 377
  • 3
  • 5