Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force re-firing of all (scripts / functions) on ajaxed content

tl;dr

I use ajax to fetch new content. The content is fetched and added to the page. However, scripts don't "re-fire" because their calls are outside of the ajaxed div.

The scripts load and fire without any problem on initial page load but not when I add new content via ajax. I get no console errors and there are no issues if I visit the URL directly.


Related:

  1. Forcing Script To Run In AJAX Loaded Page - Relates to one specific script. I want to fix (refire) all scripts including dynamic ones from Cloudflare apps
  2. Using jQuery script with Ajax in Wordpress - Same as above, this relates only to one specific script
  3. ajax loaded content, script is not executing

Intro:

I use Ajaxify WordPress Site(AWS) on a WordPress website.

The plugin lets you select a div by its id and then fetches new content inside that div using ajax

html markup

<html>

<head></head> 

<body>
  <div id="page"> 
    <header></header>
    <main id="ajax"> <!-- This is what I want to ajaxify -->
      <div class="container">
        main page content
      </div> 
    </main> <!-- end of ajaxfied content  -->
    <footer></footer>
  </div> <!-- #page -->
</body>

</html>

Problem

The plugin works and I get fresh content loaded and styled but there is an issue. Some of my page scripts and function calls are outside of the div that I use ajax on. I have two examples

1- Masonry is loaded and called in the <footer>

<html>

<head></head> 

<body>
  <div id="page"> 
    <header></header>
    <main id="ajax"> <!-- This is what I want to ajaxify -->
      <div class="container">
        main page content
      </div>  
    </main>   <!-- end of ajaxfied content  -->
    <footer></footer> <!-- Masonry script is loaded and called here -->
  </div> <!-- #page -->
</body>

</html>

2- Google maps call is in the <head>

<html>

<head></head>  <!-- Google maps is called here -->

<body>
  <div id="page"> 
    <header></header>
    <main id="ajax"> <!-- This is what I want to ajaxify -->
      <div class="container">
        main page content
      </div>  
    </main>  <!-- end of ajaxfied content  -->
    <footer></footer> 
  </div> <!-- #page -->
</body>

</html>

These are just two examples. There are others in other locations. As you can tell, such scripts won't be re-called as the only thing that reloads on the page is <main id="ajax">. While the new content inside <main> is there, some of the scripts required to render it properly are not re-called and so I end up with missing elements / broken layout.


I am not the only one who has faced this problem; a quick look at the plugin's support forum on wordpress.org shows that this issue is common.

Note: I wouldn't try to fix this if the plugin had many other issues. It works for me I just need the scripts to re-fire.

The official response is that it's possible to reload / re-fire scripts by adding the following into the plugin's php files:

$.getScript(rootUrl + 'PATH TO SCRIPT');

Which works. It works well. for example if I add

$.getScript(rootUrl + '/Assets/masonry.js');

Then the masonry function calls get re-fired when the ajaxed content is fetched even if masonry.js is loaded outside of the ajaxed div

I refer you to the plugin's files on github for more clarity on what the fix actually does (I can't make sense of what happens when $.getScript is used)


In summary

The official fix works fine if you have 3-4 scripts that need to be re-fired on ajaxed content.

This does not work for my goal because

  1. it's too rigid and hard-coded
  2. Some of the scripts are added to the page dynamically via Cloudflare apps

A possible solution might involve adding an event mimics the trigger that causes the scripts to fire at the bottom of the ajaxed div

Question:

How do I force all scripts - including dynamically added ones - to re-fire when only a certain part of the page has been reloaded via ajax?


Notes:

  1. I am trying to avoid calling out scripts one by one as that would require knowledge of their calls before hand. I am probably talking way over my head but...

    I am trying to mimic the page load and / or document ready events - at which most conditional scripts are fired (correct me if I'm wrong) - at the end of <main> in my html when new ajaxed content is added but without affecting the document when the page is loaded via using the url directly...or any other similar approach.

  2. Just for a bit of context, here is a list of some the event listeners on the page while the plugin is off. I know there are things in there I won't have to trigger. I just added this for reference. Also, please note that this is a sample taken from one of the pages. other pages may differ.

DOMContentLoaded
beforeunload
blur
click
focus
focusin
focusout
hashchange
keydown
keyup
load
message
mousedown
mousemove
mouseout
mouseover
mouseup
mousewheel
orientationchange
readystatechange
resize
scroll
submit
touchscreen
unload
like image 312
I haz kode Avatar asked Aug 11 '17 03:08

I haz kode


3 Answers

The solution you choose here will have to depend on how the scripts are initialized. There are a couple common possibilities:

  1. The script's actions are evoked immediately upon loading of the script. In this case, the script might look something like this:

    (function() {
         console.log('Running script...');
    })();
    
  2. The script's actions are evoked in response to some event (such as document ready (JQuery) or window onload (JavaScript) events). In this case, the script might look something like this:

    $(window).on('load', function() {
        console.log('Running script...');
    });
    

Some options for these two possibilities are considered below.

For scripts that run immediately on loading

One option would be to just remove the <script> elements you want to trigger from the DOM and then reattach them. With JQuery, you could do

$('script').remove().appendTo('html');

Of course, if the above snippet is itself in a <script> tag, then you will create an infinite loop of constantly detaching and re-attaching all the scripts. In addition, there may be some scripts you don't want to re-run. In this case, you can add classes to the scripts to select them either positively or negatively. For instance,

// Positively select scripts with class 'reload-on-ajax'
$('script.reload-on-ajax').remove().appendTo('html');

// Negatively select scripts with class 'no-reload'
$('script').not('no-reload').remove().appendTo('html')

In your case, you would place one of the above snippets in the event handler for AJAX completion. The following example uses a button-click in lieu of an AJAX completion event, but the concept is the same (note that this example doesn't work well within the StackOverflow sandbox, so try loading it as a separate page for the best result):

<html>
  <head></head>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

  <script class="reload-on-ajax">
    console.log('Script after head run.');
  </script>

  <body>
    <button id="reload-scripts-button">Reload Scripts</button>
  </body>

  <script class="reload-on-ajax">
    console.log('Script after body run.');
  </script>

  <script>
     $('#reload-scripts-button').click( () => $('script.reload-on-ajax').remove().appendTo('html') );
  </script>
</html>

Note that if the scripts are not inline (e.g. fetched via the src attribute), then they will be reloaded from the server or retrieved from browser cache, depending on the browser and settings. In this case, the best approach is probably to remove all the <script>s that operate on the AJAX-loaded content, and load/run them via something like JQuery's getScript() function from an AJAX completion event handler. This way you will only be loading/running the scripts once at the appropriate time (i.e. after the AJAX content is loaded):

// In AJAX success event handler
$.getScript('script1.js');
$.getScript('script2.js');

A potential problem with both variants of this approach is that asynchronous loading of the script is subject to cross-origin restrictions. So if the scripts are hosted on a different domain and cross-origin requests are not allowed, it won't work.

For scripts that run in response to an event

If the scripts are triggered on window load, then you can just trigger this event:

   $(window).trigger('load');

Unfortunately, if the scripts themselves use JQuery's document ready event, then I'm not aware of an easy way to trigger it manually. It's also possible that the scripts run in response to some other event.

Obviously, you can combine the above approaches as necessary. And, as others have mentioned, if there's some initialization functions in the scripts that you could just call, then that's the cleanest way.

like image 157
cjg Avatar answered Nov 19 '22 17:11

cjg


If you can identify a global initialising function or code block in your external scripts, you could take a look at the 'ajaxComplete' event. You can put this code in your page head and put the initialising function calls or code blocks inside the ajaxComplete callback.

$(document).ajaxComplete(function(){
    module1.init();
    $('#my_id').module2_init({
        foo : 'bar',
        baz : 123
    });
});

When the scripts you are talking about don't have such easy-to-use exposed initialising functions, but initialise themselves on scriptload, I think there will be no out of the box method that works for all scripts.

like image 5
Benni Avatar answered Nov 19 '22 19:11

Benni


Here's what you can try -

Most of the scripts like masonry or Google Map are set to re-init on window resize. So, if you trigger the resize event after ajax complete, it will help to re-fire those scripts automatically.

Try the following code -

$( document ).ajaxComplete( function() {
    $( window ).trigger('resize');
} );

This will force the scripts to re-init once ajax is completed as it will now trigger the resize event after the content is loaded.

like image 5
Nitin Yawalkar Avatar answered Nov 19 '22 17:11

Nitin Yawalkar