Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Querying for script elements from HTML text using jQuery

Tags:

jquery

ajax

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

like image 358
bkconrad Avatar asked Jul 09 '12 17:07

bkconrad


2 Answers

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);
like image 131
Kevin B Avatar answered Sep 19 '22 18:09

Kevin B


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.

like image 26
bkconrad Avatar answered Sep 23 '22 18:09

bkconrad