Tabset javascript navigation

  • 1
  • Problem
  • Updated 3 years ago
  • Solved
I thought I was cool and had figured something out here. but alas. I'm humbled.

The goal is to create a next tab and previous tab script that can handle tabs that can be conditionally rendered away.Generally, the expected syntax for 'next tab' would run something like this:
tabset.tabs("option", "active", tabset.tabs("option", "active") +1). But that causes trouble when a tab is conditionally rendered away.

Here's a page to see what's happening:
<skuidpage unsavedchangeswarning="" showsidebar="false" showheader="true">   <models>
      <model id="User" limit="0" query="false" createrowifnonefound="true" sobject="User" doclone="" type="">
         <fields>
            <field id="IsActive"/>
            <field id="UserPreferencesActivityRemindersPopup"/>
            <field id="ReceivesAdminInfoEmails"/>
            <field id="ForecastEnabled"/>
            <field id="UserPermissionsMobileUser"/>
            <field id="UserPreferencesApexPagesDeveloperMode"/>
            <field id="UserPermissionsCallCenterAutoLogin"/>
            <field id="EmailPreferencesAutoBcc"/>
            <field id="EmailPreferencesAutoBccStayInTouch"/>
            <field id="UserPermissionsChatterAnswersUser"/>
            <field id="UserPreferencesContentEmailAsAndWhen"/>
            <field id="UserPreferencesContentNoEmail"/>
         </fields>
         <conditions/>
         <actions/>
      </model>
   </models>
   <components>
      <basicfieldeditor showheader="true" showsavecancel="false" model="User" buttonposition="" mode="edit" layout="">
         <columns>
            <column width="33.3%">
               <sections>
                  <section title="Section A" collapsible="no" showheader="false">
                     <fields>
                        <field id="IsActive" valuehalign="" type="">
                           <label>A</label>
                        </field>
                        <field id="UserPreferencesActivityRemindersPopup" valuehalign="" type="">
                           <label>B</label>
                        </field>
                        <field id="ReceivesAdminInfoEmails" valuehalign="" type="">
                           <label>C</label>
                        </field>
                        <field id="ForecastEnabled" valuehalign="" type="">
                           <label>D</label>
                        </field>
                     </fields>
                  </section>
               </sections>
            </column>
            <column width="33.3%">
               <sections>
                  <section title="Section B" collapsible="no" showheader="false">
                     <fields>
                        <field id="UserPermissionsMobileUser" valuehalign="" type="">
                           <label>E</label>
                        </field>
                        <field id="UserPreferencesApexPagesDeveloperMode" valuehalign="" type="">
                           <label>F</label>
                        </field>
                        <field id="UserPermissionsCallCenterAutoLogin" valuehalign="" type="">
                           <label>G</label>
                        </field>
                        <field id="EmailPreferencesAutoBcc" valuehalign="" type="">
                           <label>H</label>
                        </field>
                     </fields>
                  </section>
               </sections>
            </column>
            <column width="33.3%">
               <sections>
                  <section title="Section C" collapsible="no" showheader="false">
                     <fields>
                        <field id="EmailPreferencesAutoBccStayInTouch" valuehalign="" type="">
                           <label>I</label>
                        </field>
                        <field id="UserPermissionsChatterAnswersUser" valuehalign="" type="">
                           <label>J</label>
                        </field>
                        <field id="UserPreferencesContentEmailAsAndWhen" valuehalign="" type="">
                           <label>K</label>
                        </field>
                        <field id="UserPreferencesContentNoEmail" valuehalign="" type="">
                           <label>L</label>
                        </field>
                     </fields>
                  </section>
               </sections>
            </column>
         </columns>
      </basicfieldeditor>
      <tabset rememberlastusertab="false" defertabrendering="false" renderas="">
         <tabs>
            <tab name="A">
               <components/>
               <renderconditions logictype="and">
                  <rendercondition type="fieldvalue" operator="=" enclosevalueinquotes="false" fieldmodel="User" sourcetype="fieldvalue" nosourcerowbehavior="deactivate" field="IsActive" value="true"/>
               </renderconditions>
            </tab>
            <tab name="B" loadlazypanels="true">
               <components/>
               <renderconditions logictype="and">
                  <rendercondition type="fieldvalue" operator="=" enclosevalueinquotes="false" fieldmodel="User" sourcetype="fieldvalue" nosourcerowbehavior="deactivate" field="UserPreferencesActivityRemindersPopup" value="true"/>
               </renderconditions>
            </tab>
            <tab name="C" loadlazypanels="true">
               <components/>
               <renderconditions logictype="and">
                  <rendercondition type="fieldvalue" operator="=" enclosevalueinquotes="false" fieldmodel="User" sourcetype="fieldvalue" nosourcerowbehavior="deactivate" field="ReceivesAdminInfoEmails" value="true"/>
               </renderconditions>
            </tab>
            <tab name="D" loadlazypanels="true">
               <components/>
               <renderconditions logictype="and">
                  <rendercondition type="fieldvalue" operator="=" enclosevalueinquotes="false" fieldmodel="User" sourcetype="fieldvalue" nosourcerowbehavior="deactivate" field="ForecastEnabled" value="true"/>
               </renderconditions>
            </tab>
            <tab name="E" loadlazypanels="true">
               <components/>
               <renderconditions logictype="and">
                  <rendercondition type="fieldvalue" operator="=" enclosevalueinquotes="false" fieldmodel="User" sourcetype="fieldvalue" nosourcerowbehavior="deactivate" field="UserPermissionsMobileUser" value="true"/>
               </renderconditions>
            </tab>
            <tab name="F" loadlazypanels="true">
               <components/>
               <renderconditions logictype="and">
                  <rendercondition type="fieldvalue" operator="=" enclosevalueinquotes="false" fieldmodel="User" sourcetype="fieldvalue" nosourcerowbehavior="deactivate" field="UserPreferencesApexPagesDeveloperMode" value="true"/>
               </renderconditions>
            </tab>
            <tab name="G" loadlazypanels="true">
               <components/>
               <renderconditions logictype="and">
                  <rendercondition type="fieldvalue" operator="=" enclosevalueinquotes="false" fieldmodel="User" sourcetype="fieldvalue" nosourcerowbehavior="deactivate" field="UserPermissionsCallCenterAutoLogin" value="true"/>
               </renderconditions>
            </tab>
            <tab name="H" loadlazypanels="true">
               <components/>
               <renderconditions logictype="and">
                  <rendercondition type="fieldvalue" operator="=" enclosevalueinquotes="false" fieldmodel="User" sourcetype="fieldvalue" nosourcerowbehavior="deactivate" field="EmailPreferencesAutoBcc" value="true"/>
               </renderconditions>
            </tab>
            <tab name="I" loadlazypanels="true">
               <components/>
               <renderconditions logictype="and">
                  <rendercondition type="fieldvalue" operator="=" enclosevalueinquotes="false" fieldmodel="User" sourcetype="fieldvalue" nosourcerowbehavior="deactivate" field="EmailPreferencesAutoBccStayInTouch" value="true"/>
               </renderconditions>
            </tab>
            <tab name="J" loadlazypanels="true">
               <components/>
               <renderconditions logictype="and">
                  <rendercondition type="fieldvalue" operator="=" enclosevalueinquotes="false" fieldmodel="User" sourcetype="fieldvalue" nosourcerowbehavior="deactivate" field="UserPermissionsChatterAnswersUser" value="true"/>
               </renderconditions>
            </tab>
            <tab name="K" loadlazypanels="true">
               <components/>
               <renderconditions logictype="and">
                  <rendercondition type="fieldvalue" operator="=" enclosevalueinquotes="false" fieldmodel="User" sourcetype="fieldvalue" nosourcerowbehavior="deactivate" field="UserPreferencesContentEmailAsAndWhen" value="true"/>
               </renderconditions>
            </tab>
            <tab name="L" loadlazypanels="true">
               <components/>
               <renderconditions logictype="and">
                  <rendercondition type="fieldvalue" operator="=" enclosevalueinquotes="false" fieldmodel="User" sourcetype="fieldvalue" nosourcerowbehavior="deactivate" field="UserPreferencesContentNoEmail" value="true"/>
               </renderconditions>
            </tab>
         </tabs>
      </tabset>
   </components>
   <resources>
      <labels/>
      <javascript/>
      <css/>
   </resources>
</skuidpage>


To reproduce the issue:

1. Check C, D, E, F, G, I, and J.

2. Open Console

//define these variables:
 var $ = skuid.$;
 var tabset = $('body').find('.ui-tabs').first();
 var initialTabNumberArray = [],
     secondTabNumberArray = [],
     thirdTabNumberArray = [],
     fourthTabNumerArray = [],
     fifthTabNumberArray = [];

3. Get the tab number values from jQuery ui tabs widget
//Click on each tab and run
initialTabNumberArray.push(tabset.tabs("option", "active"));
As a result, initialTabNumberArray should be
[0, 1, 2, 3, 4, 5, 6], as expected.

4. Uncheck E, G, and I (leaving only C, D, F, and J)
//Click on each tab and run
secondTabNumberArray.push(tabset.tabs("option", "active"));

As a result, secondTabNumberArray is
[0, 1, 3, 6].

This is no problem, as long as it's consistent.

We should be able to do something like
var tabpanels = $(tabset).children('.ui-tabs-panel'), and loop through the tabpanels after the active tab until we find one that has the attribute data-rendered="true", and pass that value back into tabToActivate in tabset.tabs("option", "active", tabToActivate).

5. Now check A E H K L, so your current visible tabs are A C D E F H J K L
//Click on each tab and run
thirdTabNumberArray.push(tabset.tabs("option", "active"));
thirdTabNumberArray returns [0, 1, 2, 3, 4, 5, 6, 7, 8]
Tabs have reset to have 0-8 sequentially.

6. Check I, uncheck J K E so your current visible tabs are A C D F H I L, and populate fourthTabNumberArray, which  returns [0, 1, 2, 4, 5, 6, 9]

7. Uncheck A and L so your current visible tabs are C D F H I J, and populate fifthTabNumberArray, which returns [0, 1, 2, 3, 4, 5].


In the third and fifth tab number arrays, i can no longer use the tabpanels jQuery method described above to check if the next tab is rendered, because as soon as the ui-tabs widget resets the indexes of the tabs, I have no way to reliably connect those indexes to those of the tabpanels array created with $(tabset).children('.ui-tabs-panel'). I either need a reliable way to connect the indexes, or some other way to determine if the 'next panel' is rendered. Ideally, i could force the ui-tabs widget to reset the indexes of the tabs every time there was a change in rendering, so I could just run tabset.tabs("option", "active", tabset.tabs("option", "active") +1) and not have to check for visiblity.

Thoughts?
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb

Posted 3 years ago

  • 1
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
I know this is a complex one, but I'm committed to making this work and keep hitting my head on the wall.

^^bump^^
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
^^^^^^
Photo of Rob Hatch

Rob Hatch, Official Rep

  • 44,006 Points 20k badge 2x thumb
Oh wow Matt. Most of the support team was out of the office last week.  This item is pretty heavy to pick back up.  I promise though I'll take a look at it later this afternoon. 
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
No worries. And thanks... You all are awesome!
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
Rob, et al.,

Has anyone been able to take a look at this? It would be great to have some kind of solution (or at least a pointer in the right direction) today before we get into the long weekend.
Photo of Andrew Duensing

Andrew Duensing, Employee

  • 740 Points 500 badge 2x thumb
Hey Matt, unfortunately I don't think I'll have the time to give you a complete solution today before the long weekend. However, it seems your troubles are stemming from an unreliable way to access the next tab . You could try giving them all your tabs unique data attribute on pageload with something like this.

var $ = skuid.$;
$(function(){
    $('#myTabs').children().each(function(i, el){
        var $el = $(el);
        $el.attr('tab-index', i);
    });
});

Of course this is assuming you gave your tabset the id "myTabs". You then could access each individual tab, via a jQuery selector similar to this one.

$('#myTabs div[tab-index="4"')
(Edited)
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
Andrew,

I don't have a problem accessing the next tab with jQuery. I don't know how to activate a tab that I've selected with jQuery. I can't seem to find a way to reliably use the .tabs("option","active") method.

How can I activate a specific jQuery selected tab?
Photo of Andrew Duensing

Andrew Duensing, Employee

  • 740 Points 500 badge 2x thumb
How about something like this?

function activateTab($tabset, $tab){
    $tab = $(tab);
    console.log($tabset, $tab);
    var children = $tabset.children();
    var visibleChildrenCount = 0;
    $.each($tabset.find('li'), function(i, el){
        console.log(el);
        $el = $(el);
        if($el.is($tab)){
            console.log('yay', visibleChildrenCount);
            $tabset.tabs('option', 'active', visibleChildrenCount);
            return;
        }
        if($el.css('display') !== 'none'){
            console.log(visibleChildrenCount);
            visibleChildrenCount++;
        }
    });
}

And then you could activate a specific tab (if it's visible) with something like this.

activateTab($('#myTabs'), $('#myTabs li[data-tab="skuid-component-tab-24"]'))
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
Andrew,

Thanks for the reply. That was basically what I attempted, but it doesn't work in the case where the user conditionally renders a tab away.

For example, if I have four tabs, A,B,C,D, all visible. The tabs("option","active") indexes are 0,1,2,3. If the user hides tab C, the indexes are 0,1,3. If I'm reading your code correctly, it would try to activate tab 2, which isn't visible.

Am I reading that wrong?

The troublesome issue is that if the user then hid tab A, and then rendered A again, the tab indexes reset, and for A,B,D would be 0,1,2.

Is that clearer?
Photo of Andrew Duensing

Andrew Duensing, Employee

  • 740 Points 500 badge 2x thumb
Yes, that is clearer, and I think I found the source of your troubles. This piece of code.

// If we are not doing our initial load,
// refresh the jQuery UI tab nature of our DOM elements
if (!component.isInitiallyLoading) {
component.element.tabs('refresh');
}

As you can tell, the tabset is being refreshed only when the tab has been rendered before. This is the sort of behavior you are referencing. Knowing that, the approach I would take would be something like this. 

$('input[type=checkbox]').click(function(){
$('#myTabs').tabs('refresh');
});

There's probably a more elegant way to do this, but refreshing the tabset every time a new tab is rendered or unrendered ensures that the indexing of the tabs matches up with what is being displayed. From there, you can more reliably figure out the index of any given tab.
Photo of Matt Sones

Matt Sones, Champion

  • 31,478 Points 20k badge 2x thumb
Thank you, thank you, thank you! Exactly what I needed.

It's probably overkill, but I'm just calling tabs('refresh') every time I try to change tabs.