An Atlas TabStrip control based on the Accordion control

I love that. Kwang Suh sent me this new control he built a few days ago. From his own words:

"I saw your accordian example and used it as the basis for a client side Atlas tabstrip control. It even works with Firefox. I don't really have anywhere to post this so I thought that maybe you could have some use for it. Feel free to do what you want with it. What was amazing is that it took me about 3 hours to create it.
As with your accordian control, there is a complete separation between markup and style. You can use CSS to format the tabs any way you want."

I think this is a great tribute to what Atlas enables in terms of widget creation. So here's how you use the new tabStrip:

<div id="productTabset">
<ul>
<li>Tab 1</li>
<li>Tab 2</li>
<li>Tab 3</li>
<li>Tab 4</li>
</ul>
<div id="firsttab" class="pane">First Tab</div>
<div id="secondtab" class="pane">SecondTab</div>
<div id="thirdtab" class="pane">Third Tab</div>
<div id="fourthtab" class="pane">Fourth Tab</div>
</div>


<script type="text/xml-script">
<page xmlns:script="http://schemas.microsoft.com/xml-script/2005" xmlns:dice="dice">
<components>
<dice:tabset id="productTabset" selectedTabStyle="selected"/>
</components>
</page>
</script>


Here's some CSS you can use with that markup:

/* Tabset */
div#productTabset ul {
border-left: solid 1px black;
display: block;
margin: 0;
padding: 0;
text-align: left;
width: 600px;
}


div#productTabset li {
background-color: #ece9d8;
border: solid 1px black;
border-left: none;
border-bottom: none;
cursor: pointer;
display: block;
float: left;
font-weight: bold;
list-style: none;
margin: 0;
padding: 3px;
position: relative;
width: 100px;
z-index: 97;
}

div#productTabset li:first-child {
border-left: solid 1px black;
}

div#productTabset li.selected {
background-color: White;
border-bottom: none;
position: relative;
z-index: 100;
}
div#productTabset .pane {
border: solid 1px black;
clear: both;
overflow: auto;
padding: 5px;
position: relative;
width: 588px;
height: 300px;
top: -1px;
z-index: 99;
}

And here's the source code as provided by Kwang Suh:

 

// Tabset view
Dice.Tabset = function(associatedElement) {
  Dice.Tabset.initializeBase(this, [associatedElement]);

  var _viewIndex = 0;
  var _selectedTabStyle = '';

  var _viewHeads = [];
  var _viewPanes = [];

  var _viewClickHandler;

  this.get_viewIndex = function() {
    return _viewIndex;
  }

  this.set_viewIndex = function(value) {
    if (_viewIndex != value) {
      _viewIndex = value;
      _ShowCurrentPane.call(this);
      this.raisePropertyChanged('viewIndex');
    }
  }

  this.get_selectedTabStyle = function() {
    return _selectedTabStyle;
  }

  this.set_selectedTabStyle = function(value) {
    _selectedTabStyle = value;
  }

  this.dispose = function() {
    if (_viewClickHandler) {
      for (var i = _viewHeads.length - 1; i >= 0; i--) {
        var head = _viewHeads[i];
 
        if (head) {
          head.detachEvent('onclick', _viewClickHandler);
        }
      }

      _viewClickHandler = null;
      _viewHeads = null;
      _viewPanes = null;
    }
    Dice.Tabset.callBaseMethod(this, 'dispose');
  }
  Dice.Tabset.registerBaseMethod(this, 'dispose');

  this.initialize = function() {
    Dice.Tabset.callBaseMethod(this, 'initialize');

    _viewClickHandler = Function.createDelegate(this, _onViewClick);

    // Get tabstrip first.
    var tabstrip;
    var panes;

    var children = this.element.childNodes;

    for (var i = 0; i < children.length; i++) {
      var child = children[i];

      if (child.nodeName == 'UL') {
        tabstrip = child.childNodes;
        break;
      }
    }

    for (var i = 0, p = 0; i < tabstrip.length; i++) {
      var child = tabstrip[i];

      if (child.nodeName == 'LI') {
        _viewHeads.add(child);
        child.viewIndex = p++;
        child.attachEvent('onclick', _viewClickHandler);
      }
    }

    for (var i = 0; i < children.length; i++) {
      var child = children[i];

      if (child.nodeName == 'DIV') {
        _viewPanes.add(child);
      }
    }

    _ShowCurrentPane.call(this);
  }
  Dice.Tabset.registerBaseMethod(this, 'initialize');

  function _onViewClick() {
    var pane = window.event.srcElement;
    while (pane && (typeof(pane.viewIndex) == 'undefined')) {
      pane = pane.parentNode;
    }
    this.set_viewIndex(pane.viewIndex);
    return false;
  }

  function _ShowCurrentPane() {
    for (var i = _viewPanes.length - 1; i >= 0; i--) {
      var selectedTab = _viewHeads[i];
      var pane = _viewPanes[i];

      if (i != _viewIndex) {
        selectedTab.className = '';
        pane.style.display = 'none';
      } else {
        selectedTab.className = _selectedTabStyle;
        pane.style.display = 'block';
      }
    }
  }

  this.getDescriptor = function() {
    var td = Dice.Tabset.callBaseMethod(this, 'getDescriptor');

    td.addProperty('viewIndex', Number);
    td.addProperty('selectedTabStyle', String);
    return td;
  }
  Dice.Tabset.registerBaseMethod(this, 'getDescriptor');
}
Dice.Tabset.registerClass('Dice.Tabset', Sys.UI.Control);
Sys.TypeDescriptor.addType('dice', 'tabset', Dice.Tabset);

I have a few comments on it though. I would use A tags for the tab headers (like I did in the accordion control) for accessibility as well as to get rid of the non-standard cursor CSS styles. I also corrected a bug I fixed a few days ago on accordion with a bad usage of the delete statement. I'm also not a big fan of hard-coding the UL/LI and DIV tags as the ones that are expected for the headers and panes (I would prefer a convention based on position or an explicit declaration as a control property), and I also regret that the contents pane are not more closely associated in the HTML markup, for example by making them subnodes of the headers. But these are all minor things that I leave as an exercise to the reader ;)

Thanks for the excellent control, Kwang Suh!

7 Comments

Comments have been disabled for this content.