34

My requirements are the following:

  • I've got a rich webpage that at a certain moment loads a bunch of HTML in a div, via AJAX.
  • The HTML I retrieve does have javascript (<script>...</script>)
  • The retrieved javascript contains $('document').ready( ... ) parts
  • I can not modify the retrieved javascript; it comes from an external lib
  • I've got a javascript function that is called when the AJAX is loaded. I'm trying to "trick it" into executing by doing:

    function AjaxLoaded() {
      $('document').trigger('ready');
    }
    

That doesn't cut it, I'm afraid.

I've seen several responses on Stack Overflow that "evade" this question by changing the code that is returned on the AJAX (make it a function and call it after loading, or just remove the $(document).ready()). I need to stress out that I can't change the retrieved code on this case.

Community
  • 1
  • 1
kikito
  • 51,734
  • 32
  • 149
  • 189
  • this was an interesting problem, i had to look at the jquery code to see what was happening to the ready events. – John Boker Feb 10 '10 at 17:04

6 Answers6

21

Afer some research i created a way to get it to work.

here is my test that shows it working: http://www.antiyes.com/test/test2.php

here is the relevant code:

<script>
    // easy copy of an array
    Array.prototype.copy = function() {
        return [].concat(this);
    };

    // this function is added to jQuery, it allows access to the readylist
    // it works for jQuery 1.3.2, it might break on future versions
    $.getReadyList = function() {
        if(this.readyList != null)
            this.myreadylist =  this.readyList.copy();      
        return this.myreadylist;
    };

    $(document).ready(function() {
        alert("blah");
    });

</script>

<script>

    // this should be added last so it gets all the ready event
    $(document).ready(function() {
        readylist = $.getReadyList();
    });

</script>

then in the body I have:

<input type="button" onclick="$(readylist).each(function(){this();});" value="trigger ready" />

basically what i did was add a function to jQuery that copies the readyList before it's cleared out, then it will be available to be used by you.

it looks like the code below doesnt work:

function AjaxLoaded() {
    $(document).trigger('ready');
}

drop the quotes around document.

Timo Tijhof
  • 10,032
  • 6
  • 34
  • 48
John Boker
  • 82,559
  • 17
  • 97
  • 130
  • Makes sense since document is an object and not an element. +1 for research. – Mark Schultheiss Feb 10 '10 at 17:11
  • 2
    John, you totally blew my mind with this one. This is definitively a +1 and correct answer! Just one minor comment: would you mind including the code on the answer, in addition to providing a link to the test page? This is too good to be lost if in two years you change your hosting :) – kikito Feb 10 '10 at 20:50
  • Great job on this work-around; extremely useful. Also, very good job on explaining it and providing a working demo. Thank you. – Jessy Houle Jun 01 '10 at 17:02
  • Just a heads-up, I can't seem to get this working with jQuery 1.4.4. – James Nail Dec 17 '10 at 00:54
  • Just as @JamesNail mentions unfortunately this excellent solution is broken with jQuery 1.4 and later. This is because the readyList is no longer exposed as discussed [here](http://forum.jquery.com/topic/readylist-is-no-longer-exposed-in-1-4). I'll post a rebuilt solution below. – rakaloof Nov 14 '11 at 17:41
12

Since the jQuery readyList is not exposed as of version 1.4 (discussed here) the nice solutions above are broken.

A way around this is by creating your own readyList, through overriding the original jQuery-ready method. This needs to be done before other scripts that use the original ready method are loaded. Otherwise just the same code as John/Kikito:

// Overrides jQuery-ready and makes it triggerable with $.triggerReady
// This script needs to be included before other scripts using the jQuery-ready.
// Tested with jQuery 1.7
(function(){
var readyList = [];

// Store a reference to the original ready method.
var originalReadyMethod = jQuery.fn.ready;

// Override jQuery.fn.ready
jQuery.fn.ready = function(){
if(arguments.length && arguments.length > 0 && typeof arguments[0] === 'function') {
  readyList.push(arguments[0]);
}

// Execute the original method.
originalReadyMethod.apply( this, arguments );
};

// Used to trigger all ready events
$.triggerReady = function() {
  $(readyList).each(function(){this();});
};
})();

I'm not sure whether it is advisable to override the ready method. Feel free to advise me on that. I have not yet found any side effects myself though.

rakaloof
  • 606
  • 1
  • 7
  • 10
  • 1
    Mind that it is `$(readyList).each(function(){this();});`, capital L – digital illusion Nov 30 '11 at 10:12
  • +1, this solution still works with 1.10.1. I've made [a fiddle](http://jsfiddle.net/AndreaLigios/utu7M/) and used it in [an answer](http://stackoverflow.com/a/21330648/1654265), obviously linking this as the source. Great work @rakaloof – Andrea Ligios Jan 24 '14 at 10:53
9

Just in case anyone needs it, I refined John's solution a bit so it could be used directly as an included javascript file.

// jquery_trigger_ready.js
// this function is added to jQuery, it allows access to the readylist
// it works for jQuery 1.3.2, it might break on future versions
$.getReadyList = function() {
  if(this.readyList != null) { this.myreadylist = [].concat(this.readyList); }
  return this.myreadylist;
};

$(document).ready(function() {
  readylist = $.getReadyList();
});

$.triggerReady = function() {
  $(readylist).each(function(){this();});
}

Including this file after including jquery allows for triggering ready by invoking $.triggerReady(). Example:

<html>
  <head>
    <title>trigger ready event</title>
    <script src="test2_files/jquery-1.js" type="text/javascript"></script>
    <script src="jquery_trigger_ready.js" type="text/javascript"></script>
  </head>
  <body>
    <input onclick="$.triggerReady();" value="trigger ready" type="button">
    <script type="text/javascript">
      $(document).ready(function(){
          alert("blah");
      });
    </script>
  </body>
</html>

By the way, I wanted to make it $(document).triggerReady(). If anyone is willing to share some advice on that, ill be appreciated.

kikito
  • 51,734
  • 32
  • 149
  • 189
  • you mean $(document).__proto__.triggerReady = function() { $(readylist).each(function(){this();}); } ? I know proto is depreciated but it is the only thing that works in Opera... – SparK Oct 20 '11 at 17:13
  • 1
    Sorry, I have no idea what you are talking about. Opera doesn't adding functions to $? – kikito Oct 21 '11 at 15:52
  • there's another way of using prototypes, but opera only supports the old way which is __proto__. by the way that line in my other comment: instead of using $.triggerReady you use $(document).__proto__.triggerReady so that you can use $(document).triggerReady(); like you wanted. – SparK Oct 24 '11 at 09:47
  • That's weird. Opera is usually very good at standards and stuff. Do you have a link to a document or example that shows this? I could not find anything. – kikito Oct 24 '11 at 14:00
  • [getPrototypeOf at MDN](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/GetPrototypeOf) here, it's the normal way, but then in the end of the page they say Opera can't handle it, even tough it's a Mozilla link I guess it can be trusted... – SparK Oct 24 '11 at 16:22
  • I've investigated this a bit. I don't have Opera, so I can't test - but it seems that at least the latest Opera supports it. To be sure, there's a way to provide getPrototypeOf in all navigators that don't have it here: http://ejohn.org/blog/objectgetprototypeof/ I suggest you use that if you need getPrototypeOf. Regards! – kikito Oct 24 '11 at 17:08
6

We had the same problem and solved it another way.

Instead of

$(document).ready(function () {
  $('.specialClass').click(....

We used :

$(document).bind('ready', function(event) {
  $('.specialClass', event.target).click(..

jQuery will trigger a "ready" event on the document as usual. When we load the content of a new div via ajax, we can write:

loadedDiv.trigger('ready')

And have all the initialization performed only on the div, obtaining what expected.

sth
  • 222,467
  • 53
  • 283
  • 367
Simone Gianni
  • 61
  • 1
  • 1
  • 1
    I really like this solution, but since it was posted this has been added to the jQuery docs: _"$(document).bind("ready", handler), deprecated as of jQuery 1.8"_ I've tried switching to on() instead and it almost works, except document ready doesn't call it. Could be because of deprecation or this part of doc: _"if the ready event has already fired and you try to .bind("ready") the bound handler will not be executed."_ Any thoughts on how to make that solution work? – John-Philip Nov 30 '12 at 16:50
2

Simone Gianni's Answer I think is the most elegant and clean.

and you can even simplify it to become even more easy to use:

jQuery.fn.loadExtended = function(url,completeCallback){
    return this.load(url,function(responseText, textStatus, XMLHttpRequest) {
        if (completeCallback !== undefined && completeCallback !== null) {
            completeCallback(responseText, textStatus, XMLHttpRequest);
        }
        $(this).trigger("ready");
    });
};

So, now instead of using:

$(".container").load(url,function(responseText, textStatus, XMLHttpRequest) {
    $(this).trigger("ready");
});

you can just use:

$(".container").loadExtended("tag_cloud.html");

or:

$(".container").loadExtended("tag_cloud.html",function(){ 
    alert('callback function') 
});

This has the advantage of only applying the trigger on the div that's being updated.

Uoli
  • 93
  • 6
0

If your new loaded HTML contain <script> elements and you try insert it into main HTML with pure JS (element.innerHTML = newHTML), then $(document).ready handlers at newHTML and wrapped functions like (function() { /* some functions */ })(); - will not execute because JQuery unbind 'ready' event after first triggering and you can not trigger it repeatly. PS. But you can use $.holdReady(true) and trigger when need.

So, try insert code with jquery method, $(element).html(newHTML). This solved similar problem for me, seems jquery handle js before inserting. Using this method you also will not see the <script> elements among DOM nodes (at browser's Elements Inspector for ex.)