0

I have a situation where I am getting an HTML response back from our server after an AJAX call. I need to pull a specific element and its children out, and inject it into the DOM.

Currently my AJAX callback looks something like:

var onSuccess = function (rawData) {
    var rawData = $(rawData);
    var data = rawData.find("#foo");
    $("#bar").append(data);
};

This works fine unless #foo contains <script> elements. After much poking around, I've determined the problem arises at the very first step:

var rawData = $(rawData);

Passing the HTML into jQuery pulls all the <script> elements out of their original context and makes them top-level. A subsequent .find("#foo") fails to bring the previously-nested <script> elements along for the ride because they've already been moved elsewhere.

Everything works fine when I inject the raw HTML directly into the DOM, but our system is old and will need a lot of massaging before it is able to limit what it returns to just the element of interest, so for now I need to be able to scan the total response and take only what I need. Which includes those <script> elements.

Any suggestions?

[FOLLOW-UP] Thanks all for the help.

I ended up adapting this solution to my needs, making my AJAX success callback look something like this:

var onSuccess = function (rawData) {
  var data = $(rawData);
  var scripts = data.filter("script[someAttribute='someValue']");
  var elementOfInterest = data.find("#foo"); // scripts above used to live in #foo
  $("#target").append(elementOfInterest);
  $("body").append(scripts);
};

Note, this requires me to add a special attribute to the nested script elements I want to be carried over.

Community
  • 1
  • 1
cmw
  • 855
  • 7
  • 17
  • Have you considered using .load? It has functionality specifically for this scenario. `$("#bar").load("myurl.php #foo")` Edit: of course, it won't append, it will replace. – Kevin B Jun 07 '12 at 20:19
  • I will try that; though I need to do some additional processing on the HTML before I inject any of it, ideally. – cmw Jun 07 '12 at 20:30
  • 1
    if you need to do some additional processing, .load it to an empty detached div and manipulate before you append it to it's final location. `$("
    ").load("myurl.php #foo",function(){$(this).children().appendTo("#bar");})`
    – Kevin B Jun 07 '12 at 20:35
  • Unfortunately this also seems to discard the script elements. It only retains them if you push the whole she-bang of the response HTML in. – cmw Jun 07 '12 at 20:44
  • Right, it only keeps script elements within the element you are appending. That's how it works. If you need to pull script elements from around that, you'll have to get them specifically and execute them. What do the script elements do? – Kevin B Jun 07 '12 at 20:48
  • Well, no, I mean the script elements ARE in #foo; they're being discarded regardless when I use your solution. As to WHAT they do -- it depends on which section of the site I'm on. I'm trying to build a solution that will handle any script that tags along inside the element of interest. – cmw Jun 07 '12 at 20:54

1 Answers1

1

This function takes an html string, strips scripts from it, appends the html to the target, and then executes the scripts. This function combined with ajax to get the html should solve your problem.

function cleanInsert(html, target, filter) {
    var outHTML = html, $content = $(target);
    outHTML = outHTML.replace(/<script/ig, "<div class='iscript'").replace(/<\/script/ig, '</script');
    outHTML = $(outHTML);
    if (filter) {
        outHTML = filter.call(outHTML);
    }
    var scripts = outHTML.filter('div.iscript').detach();
    $content.html(outHTML);
    scripts.each(function() {
        var $this = $(this),
            s = document.createElement("script");
        if ($this.attr('src') != "") {
            s.src = $this.attr('src');
        } else {
            s.nodeValue = $this.text();
        }
        $content[0].appendChild(s);
    });
}​

Usage:

$.get("myurl.php",function(html){
    cleanInsert(html,"#bar", function(){
        return this.find("#foo");
    });
});

The function is adapted from code found in a history.js example.

Note however that the scripts contained in the HTML must be top level, such as in the <head></head> or a direct child of the <body>, or as a top level element. If that's a problem, the function will need to be adjusted to compensate.

Update: Ah, this doesn't take into account you only want html within #foo. Updated.

Update: It's important to note that any scripts that use document.write() will not function properly with this function or any other method of appending html due to how document.write() works.

Kevin B
  • 94,570
  • 16
  • 163
  • 180