Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a proper way to handle DOM ready event with require.js? (no, domReady plugin doesn't work)

I've read a lot of Stack Exchange questions on that matter (a couple of dozens, I think); unfortunately, they are either primitive and cover slightly different issues, or are badly formatted and unanswered, or answered incorrectly (though some are accepted). I will try to keep my examples as small as possible to illustrate the problem.

A page with jQuery, a large image and DOM ready event handler

Let's take a simplest example:

<!DOCTYPE html>
<head>
    <script>
        var start = Date.now();
        function log(s) { console.log((Date.now()-start), s); }
        window.addEventListener('DOMContentLoaded', function() {log('Real DOMContentLoaded');});
    </script>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script>
        log('jQuery loaded');
        $(function() { log('jQuery DOM ready'); });
        $(window).load(function() { log('jQuery document loaded'); });
    </script>
</head>
<body>
    <img src="http://upload.wikimedia.org/wikipedia/commons/3/30/Googlelogo.png">
</body>

The code works as expected, jQuery's DOM ready event fires early:

288 "jQuery loaded"
307 "jQuery DOM ready"
314 "Real DOMContentLoaded"
1376 "jQuery document loaded"

The same with require.js

Now let's use require.js:

<!DOCTYPE html>
<head>
    <script>
        var start = Date.now();
        function log(s) { console.log((Date.now()-start), s); }
        window.addEventListener('DOMContentLoaded', function() {log('Real DOMContentLoaded');});
    </script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.10/require.min.js"></script>
    <script>
        require.config({
            paths: {
                jquery: "//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min"
            }
        });
        require(["jquery"], function($) {
            log('jQuery loaded');
            $(function() { log('jQuery DOM ready'); });
            $(window).load(function() { log('jQuery document loaded'); });
        });
    </script>
</head>
<body>
    <img src="http://upload.wikimedia.org/wikipedia/commons/3/30/Googlelogo.png">
</body>

Suddenly, jQuery's DOM ready event doesn't fire until the document is fully loaded:

297 "Real DOMContentLoaded"
607 "jQuery loaded"
1255 "jQuery DOM ready"
1258 "jQuery document loaded" 

Apparently, jQuery misses the browser's DOMContentLoaded event and thus falls back to window.onload to run its own ready event.

The same with require.js domReady plugin

Now let's add require.js'es own domReady plugin to the blend:

<!DOCTYPE html>
<head>
    <script>
        var start = Date.now();
        function log(s) { console.log((Date.now()-start), s); }
        window.addEventListener('DOMContentLoaded', function() {log('Real DOMContentLoaded');});
    </script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.10/require.min.js"></script>
    <script>
        require.config({
            paths: {
                jquery: "//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min",
                domReady: "//cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady"
            }
        });
        require(["domReady"], function(domReady) {
            log('domReady loaded');
            domReady(function() { log('domReady DOM ready'); });
        });
        require(["jquery"], function($) {
            log('jQuery loaded');
            $(function() { log('jQuery DOM ready'); });
            $(window).load(function() { log('jQuery document loaded'); });
        });
    </script>
</head>
<body>
    <img src="http://upload.wikimedia.org/wikipedia/commons/3/30/Googlelogo.png">
</body>

Unfortunately, it doesn't work any different from jQuery's ready event:

341 "Real DOMContentLoaded"
582 "jQuery loaded"
648 "domReady loaded"
1284 "jQuery DOM ready"
1289 "jQuery document loaded"
1299 "domReady DOM ready" 

Question

Is there a proper, beautiful, cross-browser way to attach a DOM ready handler with require.js and jQuery without custom hacks and reinventing the wheel? And what's the point of having require-domReady plugin at all, if it doesn't really work?

like image 239
Ilya Semenov Avatar asked Nov 02 '22 07:11

Ilya Semenov


1 Answers

I will not mark this as "accepted" since I'm not proud of the solution, but here's what I ended up with, for the record (someone was asking in the comments). I've coded my solution as the domReadyEx function that I add to jQuery to make it readily available everywhere jQuery is used:

<!DOCTYPE html>
<head>
    <script>
        var start = Date.now();
        function log(s) { console.log((Date.now()-start), s); }
        window.addEventListener('DOMContentLoaded', function() {log('Real DOMContentLoaded');});
    </script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.10/require.min.js"></script>
    <script>
        require.config({
            paths: {
                jquery: "//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min"
            }
        });
        require(["jquery"], function($) {
            log('jQuery loaded');
            $.domReadyEx = function(handler) {
                // Poor jQuery can't figure it itself if loaded asynchronously and falls back to window.onload which is undesirably late.
                if (document.readyState == 'interactive' || document.readyState == 'complete') {
                    handler();
                } else {
                    $(document).ready(handler);
                }
            }
            $(function() { log('jQuery DOM ready'); });
            $(window).load(function() { log('jQuery document loaded'); });
            $.domReadyEx(function() { log('jQuery.domReadyEx'); });
        });
    </script>
</head>
<body>
    <img src="http://upload.wikimedia.org/wikipedia/commons/3/30/Googlelogo.png">
</body>

The output:

417 "Real DOMContentLoaded"
516 "jQuery loaded"
517 "jQuery.domReadyEx"
1123 "jQuery DOM ready"
1128 "jQuery document loaded"

I'm pretty sure it's not fully cross-browser, but I don't care about the code working slower in older browsers or IE.

like image 117
Ilya Semenov Avatar answered Nov 15 '22 03:11

Ilya Semenov