Pergunta

I'm follwoing a book which talks about SPA - single page web application. It is very interesting but I get blocked on a piece of code. So far I understood what I was doing so it looks very strange that this isn't woking.

I have an event that is triggered on the chat click, which is at the bottom right of the page. This event triggers the change of the URL hash but.. the hashchange event is not triggered even though I binded it to the window with $(window).bind('hashchange', onHashchange()).trigger('hashchange');

I need to refresh manually the page to see the chat changing from open to close, but this is not supposed to happen, since I want it to be triggered by onhashchange. Can you help me spotting out what's going on?

It looks like I'm missing something, but everyone around complains about not working in compatibility with other browsers, while in this case is just not working :S

Any hint is appreciated =)

I have the following html code

<html>
    <head>
        <title>SPA starter</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <link rel="stylesheet" href="css/spa.css" type="text/css">
        <link rel="stylesheet" href="css/spa.shell.css" type="text/css">
        <!-- 3rd party lib -->
        <script id="jquery_js" src="js/libs/jquery/jquery.js"></script>
        <script id="janchor_js" src="js/libs/jquery/jquery.uriAnchor.js"></script>
        <!-- my lib -->
        <script id ="spajs" src="js/spa.js"></script>
        <script id="spashelljs" src="js/spa.shell.js"></script>
        <script id="unloader" src="js/js_unloader.js"></script>

        <script id="starter">
            $(function() {
                spa.initModule($('#spa'));
            });
        </script>
    </head>
    <body>
        <div id="spa"></div>
    </body>
</html>

and 3 scripts + jquery clearly. First script can be found here and is provided by the author of the book, the other two are the following

spa.js

var spa = (function() {
    var initModule = function($container) {
        spa.shell.initModule($container);
    };
    return {initModule: initModule};
}());

spa.shell.js

spa.shell = (function() {
    //----------- BEGIN MODULE SCOPE VARIABLES --------- 
    var configMap = {
        main_html: String() +
                '<div class="spa-shell-head">' +
                '<div class="spa-shell-head-logo"> </div>' +
                '<div class="spa-shell-head-acct"> </div>' +
                '<div class="spa-shell-head-search"> </div>' +
                '</div>' +
                '<div class="spa-shell-main">' +
                '    <div class="spa-shell-main-nav"> </div>' +
                '    <div class="spa-shell-main-content"> </div>' +
                '</div>' +
                '<div class="spa-shell-foot"></div>' +
                '<div class="spa-shell-chat"></div>' +
                '<div class="spa-shell-modal"></div>',
        chat_extend_time: 250,
        chat_retract_time: 300,
        chat_extend_height: 450,
        chat_retract_height: 15,
        chat_extended_title: 'Click to retract',
        chat_retracted_title: 'Click to extend',
        anchor_schema_map: {
            chat: {open: true, closed: true}
        }
    },
    stateMap = {
        $container: null,
        is_chat_retracted: true,
        anchor_map: {}
    },
    jqueryMap = {},
            setJqueryMap, toogleChat, onClickChat,
            copyAnchorMap, changeAnchorPart, onHashchange,
            initModule;

    //----------- END MODULE SCOPE VARIABLES --------- 
    //----------- BEGIN UTILITY METHODS --------- 
    //Return copy of stored anchro map; minimizes overhead
    copyAnchorMap = function() {
        return $.extend(true, {}, stateMap.anchor_map);
    };

    //----------- END UTILITY METHODS --------- 

    //----------- BEGIN DOM METHODS --------- 
    //Begin DOM method /changeAnchorPart/
    changeAnchorPart = function(arg_map) {
        console.log("change anchor part");
        var
                anchor_map_revise = copyAnchorMap(),
                bool_return = true,
                key_name, key_name_dep;
        //BEGIN merge changes into anchor map
        KEYVAL:
                for (key_name in arg_map) {
            if (arg_map.hasOwnProperty(key_name)) {
                //console.log("key_name:= " + key_name);

                //skip dependet keys during iteration
                if (key_name.indexOf('_') === 0) {
                    console.log("key name starts with '_'");
                    continue KEYVAL;
                }
                //update independent key value
                anchor_map_revise[key_name] = arg_map[key_name];
                //update matching dependent key
                key_name_dep = '_' + key_name;
                //console.log("key_name_dep:= " + key_name_dep);
                if (arg_map[key_name_dep]) {
                    //console.log("if");
                    anchor_map_revise[key_name_dep] = arg_map[key_name_dep];
                }
                else {
                    //console.log("else");
                    delete anchor_map_revise[key_name_dep];
                    delete anchor_map_revise['_s' + key_name_dep];
                }
            }
        }
        //END merge changes into anchor map
        //BEGIN ateempt to update URI; revert if not successful
        try {
            console.log("setting anchor");
            $.uriAnchor.setAnchor(anchor_map_revise);
            console.log("set");
        } catch (error) {
            //replace URI with existing state
            $.uriAnchor.setAnchor(stateMap.anchor_map, null, true);
            console.log("changeAnchorPart error :=" + error);
            bool_return = false;
        }
        //END attemp to update URI
        return bool_return;
    };
    //END DOM method /changeAnchorPart/

    //begin DOM method /setJqueryMap/
    setJqueryMap = function() {
        var $container = stateMap.$container;
        jqueryMap = {$container: $container,
            $chat: $container.find('.spa-shell-chat')
        };
    };
    //end DOM method /setJqueryMap/

    //Begin DOM method /toogleChat/
    //
    toogleChat = function(do_extend, callback) {
        var px_chat_ht = jqueryMap.$chat.height(),
                is_open = px_chat_ht === configMap.chat_extend_height,
                is_closed = px_chat_ht === configMap.chat_retract_height,
                is_sliding = !is_open && !is_closed;
        //avoid race condition
        if (is_sliding) {
            console.log('avoid race condition');
            return false;
        }
        //begin chat slider
        if (do_extend) {
            jqueryMap.$chat.animate({height: configMap.chat_extend_height},
            configMap.chat_extend_time, function() {
                jqueryMap.$chat.attr('title', configMap.chat_extended_title);
                stateMap.is_chat_retracted = false;
                if (callback) {
                    callback(jqueryMap.$chat);
                }
            });
            return true;
        }
        //End extend chat slider

        //Begin retract chat slider
        jqueryMap.$chat.animate({height: configMap.chat_retract_height},
        configMap.chat_retract_time, function() {
            jqueryMap.$chat.attr('title', configMap.chat_retracted_title);
            stateMap.is_chat_retracted = true;
            if (callback) {
                callback(jqueryMap.$chat)
            }

        });
        return true;
        //End rectract chat slider  
    };
    //end DOM method /toogleChat/

    //----------- END DOM METHODS --------- 
    //
    //----------- BEGIN EVENT HANDLERS --------- 
    onClickChat = function(event) {
        // console.log(stateMap.is_chat_retracted);
        changeAnchorPart({
            chat: (stateMap.is_chat_retracted ? 'open' : 'closed')
        });
        return false;

    };
    //
    // BEGIN event handler /onHashchange/
    // 
    onHashchange = function(event) {
        console.log("on hash change");
        var
                anchor_map_previous = copyAnchorMap(),
                anchor_map_proposed,
                _s_chat_previous, _s_chat_proposed,
                s_chat_proposed;
        //Attempt to parse anchor
        try {
            anchor_map_proposed = $.uriAnchor.makeAnchorMap();
        } catch (error) {
            console.log("onHashchange error:= " + error)
            $.uriAnchor.setAnchor(anchor_map_previous, null, true);
            return false;
        }
        stateMap.anchor_map = anchor_map_proposed;
        //convenience vars
        _s_chat_previous = anchor_map_previous._s_chat;
        _s_chat_proposed = anchor_map_proposed._s_chat;
        //BEGIN adjust of component if changed
        if (!anchor_map_previous || _s_chat_previous !== _s_chat_proposed) {
            s_chat_proposed = anchor_map_proposed.chat;
            console.log("adjusting components, chat:= " + s_chat_proposed);
            switch (s_chat_proposed) {
                case 'open':
                    toogleChat(true);
                    break;
                case 'closed':
                    toogleChat(false);
                    break;
                default :
                    toogleChat(false);
                    delete anchor_map_proposed.chat;
                    $.uriAnchor.setAnchor(anchor_map_proposed, null, true);
            }
        }
        //END of the adjustment
        return false;
    };
    //END event handler /onHashchange/

    //----------- END EVENT HANDLERS --------- 

    //----------- BEGIN PUBLIC METHODS --------- 
    //Begin Public methods /initModule/
    //
    initModule = function($container) {


        //load HTML and map jQuery collections
        stateMap.$container = $container;
        $container.html(configMap.main_html);
        setJqueryMap();
        //initialize chat slider and bind click handler
        stateMap.is_chat_retracted = true;
        jqueryMap.$chat.attr('title', configMap.chat_retracted_title)
                .click(onClickChat);
        //configure uriAnchor to use our schema
        $.uriAnchor.configModule({
            schema_map: configMap.anchor_schema_map
        });

        //HANDLE URI anchor change events
        //
        if ("onhashchange" in window) {
            console.log('SUPPORTED');
        }

        $(window).bind('hashchange', onHashchange()).trigger('hashchange');


    };
    //End PUBLIC methods /initModule/
    return {initModule: initModule};
    //----------- END PUBLIC METHODS --------- 
}());

spa.css

*{
    margin : 0;
    padding : 0;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}
h1,h2,h3,h4,h5,h6, p{ margin-bottom: 10px;}
o1,ul,dl{list-style-position: inside;}
/** end reset */

/** begin standard selectors */
body{
    font: 13px 'Trebuchet MS', Verdana, Helvetica, Arial, sans-serif;
    color: #444;
    background-color: #888;
}
strong{
    font-weight: 800;
    color:#000;
}

/** end standard selectors */

/** begin spa namespace selectors */
#spa{
    position: absolute;
    top:8px;
    left:8px;
    bottom:8px;
    right:8px;

    min-height: 500px;
    min-width: 500px;
    overflow: hidden;

    border-radius: 0 8px 0 8px;
    background-color: #fff;

}
/** end spa namespace selectors */

/** begin utility selectors */
.spa-x-select{}
.spa-x-clearfloat{
    height: 0 !important;
    float: none !important;
    visibility: hidden !important;
    clear: both !important;
}
/** */

spa.shell.css

.spa-shell-head, .spa-shell-head-logo, .spa-shell-head-acct, .spa-shell-head-search,
.spa-shell-main, .spa-shell-main-content, .spa-shell-main-nav, .spa-shell-foot,
.spa-shell-chat, .spa-shell-modal {
    position: absolute;
}

.spa-shell-head{
    top:0;
    left:0;
    right:0;
    height: 40px;
    background-color: red;
}
.spa-shell-head-logo{
    top: 4px;
    left: 4px;
    height: 32px;
    width: 128px;
    background: orange;
}
.spa-shell-head-acct{
    top:4px;
    right:0;
    width: 64px;
    height: 32px;
    background: green;
}
.spa-shell-head-search{
    top:4px;
    right:64px;
    width: 248px;
    height: 32px;
    background: blue;
}
.spa-shell-main{
    top:40px;
    left:0;
    bottom:40px;
    right:0;
    background-color: #993300;
}
.spa-shell-main-content, .spa-shell-main-nav{
    top:0;
    bottom:0;
}
.spa-shell-main-nav{
    width:250px;
    background: #eee;
}
.spa-x-closed, .spa-shell-main-nav{
    width:0;
}
.spa-shell-main-content{
    left:250px;
    right:0;
    background: #ddd;
}
.spa-x-closed .spa-shell-main-content{
    left:0;
}
.spa-shell-foot{
    bottom:0;
    left:0;
    right:0;
    height:40px;
    background-color: #99ffff;
}
.spa-shell-chat{
    bottom:0;
    right:0;
    width: 300px;
    height: 15px;
    background: burlywood;
    z-index: 1;
    cursor:pointer;
    border-radius: 5px 0 0 0;
}
.spa-shell-modal{
    margin-top:-200px;
    margin-left:-200px;
    top:50%;
    left:50%;
    width:400px;
    height:400px;
    background: #fff;
    border-radius: 3px;
    z-index: 2;
}
Foi útil?

Solução

Correct me if I am mistaken, but you are executing the function in your bind. You want it like this:

    $(window).bind('hashchange', onHashchange).trigger('hashchange');

Without the '()' after.

Bear in mind, the support of this is limited in some browsers.

Excerpt from: jQuery - hashchange event

if (("onhashchange" in window) && !($.browser.msie)) { 
     window.onhashchange = function () { 
          alert(window.location.hash);             
     }            
     // Or $(window).bind( 'hashchange',function(e) {  
     //       alert(window.location.hash); 
     //   });              
}
else { 
    var prevHash = window.location.hash;
    window.setInterval(function () {
       if (window.location.hash != prevHash) {
          prevHash = window.location.hash;
          alert(window.location.hash);
       }
    }, 100);
}

Adapt according to your needs.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top