Search code examples
jquery-ui-tabsback-button

JQuery UI Tabs: nested Tabs, AJAX loading and Back button


I'm loading JQuery UI tabs using AJAX.

I have 3 levels of nested UI tabs:

  1. vertical
  2. horizontal
  3. horizontal

Most of the stuff is only 2 levels deep but some are 3. The issue is the 3rd level. I followed this:

http://benalman.com/code/projects/jquery-bbq/examples/fragment-jquery-ui-tabs/

But it does not cover nesting of tabs.

The layout of the page is like this:

<script type="text/javascript">
    var tabs;
    var tab_a_selector;
    var tab_a_vertical_selector;

    $(function() {              

        $("#menuTabs").tabs({
            ajaxOptions: {
                cache: false
            }
        }).addClass("ui-tabs-vertical ui-helper-clearfix"); 
        $("#menuTabs li").removeClass('ui-corner-top').addClass('ui-corner-left');
        $(".ui-tabs-vertical .ui-tabs-nav").removeClass("ui-tabs-nav").addClass("ui-tabs-nav-vert")

        $("#menuItem0").tabs();
        $("#menuItem1").tabs(); 
        //...

        /* -- enables Back button for nested tabs -- */

        // The "tab widgets" to handle.
        tabs = $('.tabs');

        // This selector will be reused when selecting actual tab widget A elements.
        tab_a_selector = 'ul.ui-tabs-nav a';
        tab_a_vertical_selector = 'ul.ui-tabs-nav-vert a';
        // Enable tabs on all tab widgets. The `event` property must be overridden so
        // that the tabs aren't changed on click, and any custom event name can be
        // specified. Note that if you define a callback for the 'select' event, it
        // will be executed for the selected tab whenever the hash changes.
        tabs.tabs({ event: 'change' });

        // Define our own click handler for the tabs, overriding the default.
        tabs.find(tab_a_selector).click(function(){
            var state = {};

            // Get the id of this tab widget.
            id = $(this).closest( '.tabs' ).attr( 'id' );

            // Get the index of this tab.
            idx = $(this).parent().prevAll().length;

            // Set the new state
            // This is done as below to remove any state from deeper levels of nested tabs.
            state ['menuTabs'] = $.bbq.getState('menuTabs');
            state[ id ] = idx;                  
            $.bbq.pushState( state, 2 );
        });
        tabs.find(tab_a_vertical_selector).click(function(){                
            var state = {};

            // Get the id of this tab widget.
            id = $(this).closest( '.tabs' ).attr( 'id' );

            // Get the index of this tab.
            idx = $(this).parent().prevAll().length;

            // Set the state!
            state[ id ] = idx;  

            // 2 -> replaces old state with new state. meaning indexes of nested tabs are removed
            $.bbq.pushState( state, 2 ); 
        });

        // Bind an event to window.onhashchange that, when the history state changes,
        // iterates over all tab widgets, changing the current tab as necessary.
        $(window).bind( 'hashchange', function(e) {             

            // Iterate over all tab widgets.
            tabs.each(function(){

                // Get the index for this tab widget from the hash, based on the
                // appropriate id property. In jQuery 1.4, you should use e.getState()
                // instead of $.bbq.getState(). The second, 'true' argument coerces the
                // string value to a number.
                var idx = $.bbq.getState( this.id, true ) || 0;

                // Select the appropriate tab for this tab widget by triggering the custom
                // event specified in the .tabs() init above (you could keep track of what
                // tab each widget is on using .data, and only select a tab if it has
                // changed).
                $(this).find( tab_a_selector).eq( idx ).triggerHandler( 'change' );
                $(this).find( tab_a_vertical_selector ).eq( idx ).triggerHandler( 'change' );
            });                 
        })

        // Since the event is only triggered when the hash changes, we need to trigger
        // the event now, to handle the hash the page may have loaded with.
        $(window).trigger( 'hashchange' );

        /* -- END enables Back button for nested tabs -- */
    });
</script>

<div id="menuTabs" class="tabs">
    <ul>
        <li><a href="#menuItem0">menuItem0</a></li>
        <li><a href="#menuItem1">menuItem1</a></li>
        <li><a href="#menuItem2">menuItem2</a></li>
    </ul>

    <div id="menuItem0" class="tabs">
        <ul>
            <li><a href="content/menuItem0/Intro.html">Intro</a></li>
        </ul>                       
    </div>
    <div id="menuItem1" class="tabs">
        <ul>
            <li><a href="content/menuItem1/Intro.html">Introduction</a></li>
            <li><a href="content/menuItem1/Guide.html">Guide</a></li>
            <li><a href="content/menuItem1/abc.html">abc</a></li>                       
        </ul>                       
    </div>
    <!--...-->
</div>

The 3rd level of tabs is in the above html pages, as example in abc.html:

<script type="text/javascript"> 

    var rNumberTabs = $("#rNumber").tabs(); 
    rNumberTabs.tabs({ event: 'change' });
    rNumberTabs.find(tab_a_selector).click(function(){
        var state = {};

        // Get the id of this tab widget.
        id = $(this).closest( '.tabs' ).attr( 'id' );

        // Get the index of this tab.
        idx = $(this).parent().prevAll().length;

        // Set the state!
        state[ id ] = idx;                  
        $.bbq.pushState( state );
    });
    tabs = tabs.add(grNumberTabs);
    // If this is triggered it leads to an infinte loop,
    // if not, I can't even browse to any other tab than the first
    // one on he third level, eg. it automatically jumps back
    // to first one.
    $(window).trigger( 'hashchange' );
</script>
<div id="rNumber" class="tabs">
    <ul>
        <li><a href="#layout">Layout</a></li>       
        <li><a href="#prefix">Prefix</a></li>
    </ul>
    <div id="layout">
        <!-- Content here -->
    </div>
    <div id="prefix">
        <!-- Content here -->
    </div>
</div>

Any ideas how I can solve?


Solution

  • So here my current solution. The issue of endless looping seems to be caused by the fact that when using AJAX loading of tabs, the tab is loaded again for every click on it. Since the tabs that contain a 3rd level of tabs also contain JavaScript (see question) re-loading such a tab leads to issues.

    The solution is to cache the tabs:

    $("#menuTabs").tabs({
        cache: true,
        ajaxOptions: {
            cache: false
        }
    })
    

    Note: You must set Ajax cache to false:

    http://docs.jquery.com/UI/Tabs#option-cache

    Same for the 3rd level of tabs. And here also remove the call to hashchange event.

    var rNumberTabs = $("#rNumber").tabs({
        cache: true,
        ajaxOptions: {
            cache: false
        }
    });
    //snipped..
    //$(window).trigger( 'hashchange' ); remove this line