Executing a js file returned inside an ajax response

  • A+

I have a HTML5 application which is using jquery 3.2.1.

In part of the application - a search feature - I make an ajax request. The response from the ajax request is HTML, and this includes a <script> tag which links to a js file which is hosted on the same server as the application.

So the ajax code looks like this - for making the ajax request and writing the response to a div with the ID #ajaxContent:

$.ajax({     url: $('#searchRegulations').attr('action'),     type: 'post',     cache: false,     data: $('#searchRegulations').serialize()  }).done(function (response, status, xhr) {         if (response) {             $('main .content').hide();             $('#ajaxContent').html(response).show();             return false;         }     } }); 

If I inspect #ajaxContent I can see that the <script> tag is included in the ajax response:

Executing a js file returned inside an ajax response

I have also checked my Network tab to make sure /js/search_regulations.js is being loaded correctly, and it's giving a 200 response:

Inside search_regulations.js there is some jquery which toggles some filters that are present in #ajaxContent.

The problem is that this code only seems to be working about 50% of the time. When it works it will toggle the state of some filter buttons by adding/removing a class .active to elements inside .browse-ctp__filters-data and then writing them to a hidden form with the ID #tmpFilters.

To ensure the script was "firing" I put in the line console.log('search_regulations.js firing'); and sure enough this is shown in the Console every time irrespective of whether the script functions or not.

What's stranger is that if I cut/paste the code into my Console after the ajax response has been written to the page, it always works as expected.

Is this some issue with the way the script is being brought into the page?

I've pasted the script below, but I don't think it's an issue with the code in it, rather the way the browser/ajax response is being handled:

$(function() {    console.log('search_regulations.js firing');  /* toggle the active (applied) state on browse by filters */ /* @link https://stackoverflow.com/questions/48662677/switch-active-class-between-groups-of-include-exclude-buttons */ $(document).on('click', '.browse-ctp__filters-data .include, .exclude', function(){      var $this = $(this);      // Split name into array (e.g. "find_355" == ["find", "355"])      var arr = $this.attr('name').split('_');       // Toggle active class     $this.toggleClass("active");     if ($this.siblings().hasClass("active")) {       $this.siblings().removeClass("active")     }      // Remove any existing instances of the filter from hidden form     $('#tmpFilters input[value="exclude_'+arr[1]+'"]').remove();     $('#tmpFilters input[value="find_'+arr[1]+'"]').remove();      // If a filter has been applied then add it to hidden form     if ($this.hasClass('active')) {         $('#tmpFilters').append('<input type="hidden" name="tmpFilter[]" value="'+$this.attr('name')+'">');     } });     });  

Notes about the Bounty offered:

I've offered a bounty because it's not a trivial problem to solve - demonstrated by the fact nobody has given a workable answer. I expect the correct answer to:

  1. Be demonstrable with jsfiddle or equivalent.
  2. Explain how/why it works.
  3. Understand that the ajax response is HTML and js. The js acts on HTML elements in the response. Therefore both the HTML and js need to be included in the response - as opposed to saying "just add the js to a global file" (I don't want the js to be global, because it's specific to the HTML response given, and can vary in different parts of the application).
  4. Should not use a timeout (setTimeout or whatever). If the user interacts with the UI elements - such as buttons - returned in the HTML response before the timeout and therefore js is fired... that just leads to the same problem we have now. So that isn't a valid solution as far as I'm concerned.
  5. If this problem is impossible to solve in HTML5/jquery explain why and suggest any alternative ways to handle it.

jsfiddle showing the HTML and script tag returned via ajax:

Several people have asked for a fiddle or demo. No 2 people would get the same outcome - which is very much the point of the question - so I didn't make one originally. The HTML returned that gets written to #ajaxContent is shown here: http://jsfiddle.net/v4t9j32g/1/ - this is what dev tools in the browser shows following the ajax response. Please note that the length of the content returned can vary, since it's the response to a keyword search facility that brings back a load of filter buttons. Also note that this HTML response contains the line <script src="/js/search_regulations.js"></script>. That is where the problematic js is located and the full contents of that are shown above in this question - the bit that includes console.log('search_regulations.js firing')


You need to strip the < script > tag out of the HTML and create a script tag manually to add to the DOM. Essentially, you can convert the Html to JS via:

var $html = $(data.Html) 

Then, pull out all the script tags like this:

 var scripts = [];         $html.each(function(i, item) {             if (item instanceof HTMLScriptElement) {                 scripts.push(item);             }         }); 

Then you need to remove all the < script > tags from the html before appending it to the DOM.

$html.find("script").remove();  $("placeToAddHtml").append($html); 

Once the HTML, stripped of the tags is added, you can then manually add each script to the DOM at the end:

           for (var i = 0; i < scripts.length; i++) {                 var script = document.createElement('script');                  $(scripts[i]).each(function() {                     $.each(this.attributes, function(j, attrib) {                         var name = attrib.name;                         var value = attrib.value;                         script[name] = value;                     });                 });                  script.text = scripts[i].innerHTML;                 document.body.appendChild(script);             } 

Hope this works how you intend it!

Edit: You may need to build up the script tag here in a way that appends all of the pulled out scripts at the same time if they depend on each other. That way, you only add one giant script tag that has all the other tags internally at once.


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: