miguel-perez / smoothState.js

Unobtrusive page transitions with jQuery.
MIT License
4.43k stars 508 forks source link

Can new scripts be loaded correctly with smoothState? #304

Closed foo123 closed 8 years ago

foo123 commented 8 years ago

i read all about smoothState and WP (this is what i'm making right now, for smooth page transitions). i even read the part where any new scripts (of the new page) should be inside the content handled by smoothState (done), still the scripts do not seem to be executing at all (my example is a simple contact form script which is only in one page)

And yes, i have even patched the $.fn.ready method and all to mod exec style.

Thanks

PS Note that loading every possible script that may be needed in all pages although doable IS NOT AN EFFICIENT OPTION. So i'm asking for ways (if this can be done correctly) for any extra page scripts to be executed correctly on page load.

foo123 commented 8 years ago

i managed to make this work as following:

The code listens for script onload on snoothState onAfter, when all scripts (if indeed there are any) are loaded, then the jquery.ready mod exec is run (it needs some corrections from the gist) see below

smoothState

    onAfter: function( $container, $newContent ) {
                // other page stuff here..
               // listen for any extra scripts and wait to load before calling onReeady
                var numScripts = 0;
                $('script', $newContent).each(function(){
                    var script = this;
                    if ( script.src )
                    {
                        numScripts++;
                        script.onload = function( ){
                            numScripts--;
                            if ( 0 === numScripts ) onReady( );
                        };
                    }
                });
                if ( 0 === numScripts ) onReady( );
    }

onReady

    function onReady( fn ) {
        setTimeout(function(){
            // trigger ready listeners, if any
            // using jquery ready method as mod exec
            $.readyFn.execute();
            $.readyFn.clear();
            if ( fn ) fn();
        }, 20);
    }

jquery.execReady.js

/**
 * Replace jQuery's $.fn.ready() function with a mod exec
 *
 * Sites that make heavy use of the $(document).ready function
 * are generally incompatable with asynchrounous content. The
 * the $.fn.ready function only runs once. This script replaces
 * the ready function with a module execution controller that
 * let's us register functions and execute all of the functions
 * as we need them. This is useful after HTML gets injected on the
 * page and we want to rebind functionally to the new content.
 *
 * @author  Miguel Ángel Pérez   reachme@miguel-perez.com
 * @note    Should be placed directly after jQuery on the page
 * 
 */
 // https://gist.github.com/miguel-perez/476046a42d229251fec3
;(function($){
    "use strict";

    /** create mod exec controller */
    $.readyFn = {
        list: [],
        register: function(fn) {
            $.readyFn.list.push(fn);
        },
        execute: function(el) {
            el = el || document;
            for (var i = 0; i < $.readyFn.list.length; i++) {
                try {
                   $.readyFn.list[i].call(el, $);
                }
                catch (e) {
                    throw e;
                }
            };
        },
        clear: function() {
            $.readyFn.list.length = 0;
        }
    };

    /** run all functions */
    /*$(document).ready(function(){
        $.readyFn.execute();
    });*/

    /** register function */
    $.fn.jqReady = $.fn.ready;
    $.fn.ready = function(fn) {
        $.readyFn.register(fn);
        return this; // this is needed for .ready to work as expected in some cases
    };

    /** run all functions */
    $(document).jqReady(function(){
        $.readyFn.execute(this);
        $.readyFn.clear();
    });

})(jQuery);
foo123 commented 8 years ago

another way to write this:

onReady

    function onReady( scripts, before, after )
    {
        var execReady = function( ) {
            setTimeout(function(){
                if ( before ) before( );
                // trigger ready listeners, if any
                // using jquery ready method as mpd exec
                $.readyFn.execute();
                $.readyFn.clear();
                if ( after ) after( );
            }, 0);
        };

        var numScripts = 0;
        if ( scripts && scripts.length )
        {
            scripts.each(function( ){
                var script = this;
                if ( script.src /*&& !script.async*/ && (!script.readyState || ('loaded' !== script.readyState) && ('complete' !== script.readyState)) )
                {
                    numScripts++;
                    var loader = function loader( ){
                        if ( !script.readyState ||
                            ('loaded' === script.readyState) ||
                            ('complete' === script.readyState)
                        )
                        {
                            numScripts--;
                            script.onload = script.onreadystatechange = null;
                            if ( loader.prev ) loader.prev( );
                            if ( 0 === numScripts ) execReady( );
                        }
                    };
                    loader.prev = script.onload || null;
                    script.onload = script.onreadystatechange = loader;
                }
            });
        }
        if ( 0 === numScripts ) execReady( );
    }

smoothState

onAfter: function( $container, $newContent ) {
    // other page stuff here..
    // listen for any extra scripts and wait to load before calling onReady
    onReady( $('script', $newContent), beforeFunc, afterFunc );
}

NOTE In order to handle more generic cases (not using jquery, e.g angular or other) one may need to override or simulate/trigger window.load events, but you get the idea.

foo123 commented 7 years ago

a further refinement (to handle cases where scripts are not async or defer and already loaded and.or cached, ..)

onReady

function onReady( scripts, before, after )
{
    var execReady = function( ) {
        // trigger ready listeners, if any
        // using jquery ready method as mpd exec
        setTimeout(function(){
            if ( before ) before( );
            if ( $.readyFn ) $.readyFn.execute( document, true/* and clear list afterwards*/ );
            if ( after ) after( );
        }, 0);
    };

    var numScripts = 0;
    if ( scripts && scripts.length )
    {
        scripts.each(function( ){
            var script = this;
            if ( script.src && (!script.readyState || ('loaded' !== script.readyState) && ('complete' !== script.readyState)) )
            {
                numScripts++;
                var loader = function loader( ){
                    if ( !loader.done && 
                        (!loader.readyState ||
                        ('loaded' === loader.script.readyState) ||
                        ('complete' === loader.script.readyState))
                    )
                    {
                        loader.done = true;
                        loader.script.onload = loader.script.onreadystatechange = null;
                        numScripts--;
                        if ( loader.prev ) loader.prev( );
                        if ( 0 === numScripts ) execReady( );
                    }
                };
                loader.script = script;
                loader.done = false;
                loader.readyState = script.readyState ? true : false;
                loader.prev = script.onload || script.onreadystatechange || null;
                script.onerror = script.onload = script.onreadystatechange = loader;
                // run it manualy as well, since it may be already loaded and never call onload
                setTimeout(loader, 100);
            }
        });
    }
    if ( 0 === numScripts ) execReady( );
}

smoothState

onAfter: function( $container, $newContent ) {
    // other page stuff here..
    // listen for any extra scripts and wait to load before calling onReady
    onReady( $('script[src]:not([async]):not([defer])', $newContent), beforeFunc, afterFunc );
}
foo123 commented 7 years ago

a gist of the scripts for smoothState - wordpress integration and some more details https://gist.github.com/foo123/c404aeeebbd0a26aceba122ac915aee1