6

I'm loading a page via $.ajax() and inserting parts of the result into the corresponding parts of the page:

$.ajax({
  url: '/whole_page.html',
  success: function (data, status, xhr) {
    $result = $(xhr.responseText);
    $('#menu').html($('#menu', $result).html());
    $('#content').html($('#content', $result).html());
  }
});

Works like a charm, but I've run into a problem.

Now my pages contain some page-specific JS which must be executed when the result is fetched and inserted. As far as I can tell, jQuery won't return script elements if you construct a jQuery object from HTML text and query against it (I've made a fiddle to demonstrate). So, I have no way of selectively inserting only the scripts I want via jQuery.

So it would seem that I've got to pull the script elements from response text myself. Essentially, I'm looking for the right way to do this. Here's what I came up with so far:

function pullScripts(text) {
  var frag = document.createDocumentFragment()
    , div = document.createElement('div')
    , scriptElements
    ;

  // This is roughly how jQuery finds the scripts when cleaning text
  frag.appendChild(div);
  div.innerHTML = text;

  scriptElements = div.getElementsByClassName('class-for-scripts-that-we-want');

  $('head').append(scriptElements);
}

This works well enough, although I haven't tested it on any crappy browsers yet. Regardless, it feels uncomfortable to replicate such basic functionality of jQuery just to avoid one of its features (the special script handling). As can be seen in the fiddle, the jQuery object actually does contain the scripts, but it won't return them with something like .html() or .get(). Am I missing some obvious way of doing this?

Note: this whole topic will be moot once jQuery's internal parseHTML function is exposed

Note 2: I've also read Trying to select script tags from a jQuery ajax get response however the accepted answer is "you can't" followed by "use a regex". Both of which are unsatisfactory

6
  • Just a note, the first parameter passed into your success callback is the data from the request, not an event Commented Jul 9, 2012 at 17:34
  • Doesn't $.load() address this? Commented Jul 9, 2012 at 17:35
  • @Austin, thanks. I typed that freehand, knowing I'd mess it up. Christophe: In a way, but if you add a selector to the URL (as I would need to in order to use it) you run into the same problem outlined above. Commented Jul 9, 2012 at 17:41
  • 1
    In jQuery 1.8.0b1, you can now use the $.parseHTML() method to make this easier. $.parseHTML(xhr.responseText,true) will give you a jQuery object that includes script elements. You can then extract the script tags and append or execute them after you append the html. If upgrading jQuery isn't an option, maybe you could just take the implementation of $.parseHTML and include it as a plugin to your current jQuery. Commented Jul 9, 2012 at 17:56
  • 1
    $.parseHTML() returns an array of DOM nodes, not a jQuery object. api.jquery.com/jQuery.parseHTML Commented Dec 2, 2016 at 19:29

3 Answers 3

4

In jQuery 1.8.0b1, you can now use the $.parseHTML() method to make this easier.

$.parseHTML(xhr.responseText,true) will give you a jQuery object that includes script elements. You can then extract the script tags and append or execute them after you append the html.

$.ajax({
  url: '/whole_page.html',
  success: function (data, status, xhr) {
    var $result = $.parseHTML(xhr.responseText,true),
        $scripts = $result.find("script").add($result.filter("script")).detach();

    $('#menu').html($('#menu', $result).html());
    $('#content').html($('#content', $result).html());
    $('head').append($scripts);
  }
});

You may have to filter the scripts though to avoid double including jQuery on a case by case basis. It will depend on the content you are loading.

If upgrading jQuery isn't an option, you could just take the implementation of $.parseHTML and include it as a plugin to your current jQuery

(function($) {
    if (!$.parseHTML) {
        var rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/;
        // data: string of html
        // context (optional): If specified, the fragment will be created in this context, defaults to document
        // scripts (optional): If true, will include scripts passed in the html string
        $.parseHTML = function( data, context, scripts ) {
            var parsed;
            if ( !data || typeof data !== "string" ) {
                return null;
            }
            if ( typeof context === "boolean" ) {
                scripts = context;
                context = 0;
            }
            context = context || document;

            // Single tag
            if ( (parsed = rsingleTag.exec( data )) ) {
                return [ context.createElement( parsed[1] ) ];
            }

            parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] );
            return jQuery.merge( [],
                (parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes );
        }
    }
})(jQuery);
Sign up to request clarification or add additional context in comments.

Comments

2

While I was playing with jQuery (edge) last night on jsFiddle, I wasn't able to use .find() and friends on the result of $.parseHTML(). The problem was that $.parseHTML returns an array of elements rather than a jQuery object, and if you make one with $() you're back at the original problem. So, you still can't really use selectors. Not much of a hassle though:

$.ajax({
  url: '/some_page.html',
  success: function (data, text, xhr) {
    var result = $.parseHTML(data, true)
      , scripts = []
      , i;

    // filter the scripts somehow
    for (i = 0; i < result.length; i++) {
        if (result[i].getAttribute('class') === 'good') {
            scripts.push(result[i]);
        }
    }

    $('head').append(scripts);
  }
});​

I've made a working fiddle for this.

Comments

0

As mentioned before you should use $.parseHTML that returns an array of nodes.

I have added class 'append-on-ajax-script' on script tag that should be appended on ajax request.

Also I had problems with getAttribute('class') function so my example have some workaround for class checking.

<script type="text/javascript" class="append-on-ajax-script">
$.post(
    $(this).prop('href'),
    {'some_data':'some_value'},
    function(data) {
        var script = $.parseHTML(data, true);
        for (var i = 0; i < script.length; i++) {
            //console.log(i, (' ' + script[i].className + ' ').indexOf(' append-on-ajax-script ') > -1 );
            if ((' ' + script[i].className + ' ').indexOf(' append-on-ajax-script ') > -1) {
                // console.log(i, script[i]);
                $('body').append(script[i]);
            }
        }

    }
);
</script>

<script type="text/javascript" class="append-on-ajax-script">
    alert('repeat me on ajax');
</script>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.