Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Executing a js file returned inside an ajax response

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:

enter image description here

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')

like image 512
Andy Avatar asked Jul 20 '18 11:07

Andy


People also ask

Does ajax return anything?

ajax returns immediately and the next statement, return result; , is executed before the function you passed as success callback was even called.

How do I return a response from ajax?

What you need to do is pass a callback function to the somefunction as a parameter. This function will be called when the process is done working (ie, onComplete): somefunction: function(callback){ var result = ""; myAjax = new Ajax.

Does ajax return a promise?

ajax returns, which is a jqXHR object that conforms to the promise interface. If there is a failure, the outer fail function is invoked. The outer fail function is also invoked if the processData function fails. When both the getData and processData functions are successful, the outer done method is invoked.

What type of data does ajax return?

In the jQuery ajax() function we are not providing any content type or data type. So by default it will return JSON data as expected.


2 Answers

One of the problems that I see is that you're binding the onclick to the same elements multiple times...

Since the js can be loaded multiply times via ajax requests, it is important to first detach, before attaching again events.

The other problem, is that you're running the code in $(document).ready event (that is when HTML-Document is loaded and DOM is ready), however you'd probably be better off to run the code in the $(window).load event (which executes a bit latter, when complete page is fully loaded, including all frames, objects and images)

Example:

$(window).load(function() {  

    console.log('search_regulations.js firing');

    //off: detach the events
    //on: attach the events
    $('.browse-ctp__filters-data .include, .exclude').off('click').on('click', function(){
        ...
    }
}); 
like image 123
bastos.sergio Avatar answered Sep 25 '22 00:09

bastos.sergio


In addition to what everybody were talking in the comments, I'll try to put it into an answer.

Event driven

JS is event driven, that doesn't mean you have to load scripts on the fly to make them execute functions when an event is fired. A better approach would be to load everything you need on the page load, and add event listeners, this will case much better user experience(less http requests) and much better code matinability.

TL;DR

In general I would do something like that in you html file:

<script src="/js/main.js">  
<script src="/js/search_regulations.js" async>  <!-- bonus tip! google "async script tag" -->

This will load all the js you need.

Keep in mind that search_regulations.js should add the event listener you want with jQuery on method, but since the html didn't exist when the script added the event listener, you might want to check this.

Why this is better?

  • Now you had pretty easy case where one file loads another. In the future you might want to add more features and more complex code. This will be painful to debug when you have chains of scripts that load each other.
  • The user has to wait until the request for the search_regulations.js file to be loaded, if they're on the a mobile network/the file will get bigger in size that might be a poor user experience.
  • You can still reuse your code over the application with search_regulations.js
like image 38
Or Duan Avatar answered Sep 24 '22 00:09

Or Duan