Tales from the Evil Empire

Bertrand Le Roy's blog

News

Ads Via DevMavens

ASP.NET AJAX UpdatePanel Control: Add Ajax interactivity to your ASP.NET 2.0 web pages


follow bleroy at http://twitter.com


Add to Technorati Favorites

Blogs I read

My other stuff

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!

Posted: Apr 13 2006, 02:20 PM by Bertrand Le Roy | with 14 comment(s)
Filed under:

Comments

Dflying said:

very cool!
# April 13, 2006 9:21 PM

Andrey Skvortsov said:

Excuse me,I'm not impressed,look at http://jquery.com/demo/accord/-it's much,much more convenient and simple.I don't see clear benefits Atlas bring to this example,except obfuscate things a little enforcing "framework" burden;-)
It seems to me you're not solving real problems.Atlas binded to server side more than other JS frameworks and tring to adhere "server side" principles using .NET BCL guidlines,asp.net controls model etc.,and it dosen't looks very nice because you think on the "other" side and approximate server to client and not vise versa,that is more natural IMHO:-)

Regards.
# April 14, 2006 8:11 AM

Bertrand Le Roy said:

Andrey: reusable components, declarative syntax and a consistent framework.
# April 14, 2006 3:12 PM

Andrey Skvortsov said:

You mean components written for Atlas and declarative syntax for binding that components to BOM/DOM?Why you need another client markup beside of DOM?To support declarative part of framework?But what if framework not so consistent as you think?I always thought "consistent" means easy and jquery is more easier IMHO(in this example)-all other JS frameworks has their own components and level of consistency too.
By the way,I prefer client DOM counterpart implementation on server side instead of ASP.NET control model,why not other way around-DOM on server?;-)

P.S.
I don't say that xml marup syntax is bad-it's beautiful solution by itself,but it's simply declarative part of framework and framework in turn looks not so beautiful compared to other frameworks.
# April 15, 2006 12:56 PM

Bertrand Le Roy said:

Andrey: you're entitled to your preferences, choose whichever framework you find the most elegant. The xml-script declarative syntax expresses concepts that are entirely different from the XHTML DOM. We're not replacing XHTML here or even extending it. We're keeping it as is (it is adapted to what it's for: expressing the semantics and structure of your document). What we're doing with xml-script is attaching JavaScript behavior to the existing markup. This is exactly similar to what any well-designed component framework is doing with the difference that Atlas can do it declaratively with a clean separation between document structure (XHTML) and behavior (xml-script).
To be more precise about the example you show, it does reproduce the accordion behavior, but in a way that is not encapsulated and easily reusable.
# April 16, 2006 3:10 AM

AO said:

This was posted on forums.asp.net a little under a month ago... http://forums.asp.net/1242119/ShowThread.aspx

Some browser modes will need testing in lines such as: if (child.nodeName == 'UL') for child != null or some browsers (Opera, NS if I remember correctly) will give some errors at times.
# April 17, 2006 8:49 AM

Kalimuthu govindasamy said:

Hi., plz, give the demo version controls.,
# July 5, 2006 6:27 AM

Bertrand Le Roy said:

Kalimuthu: I don't understand what you mean by "demo version controls".

# July 5, 2006 2:34 PM

Nico Oosthuizen said:

I cant get this to work could somebody please help
# July 7, 2006 8:40 AM

Bertrand Le Roy said:

Nico, please give me more details by mail. bleroy (at microsoft).

# July 7, 2006 2:22 PM

Lee said:

Thanks for the code. Because Atlas is still so new it really is frustrating when no working example of the code is displayed.
# July 11, 2006 10:17 PM

Alan Le said:

Thanks for posting this. I put up a demo of the code here: http://blogs.vertigosoftware.com/alanl/archive/2006/08/14/Atlas_TabStrip.aspx
# August 14, 2006 6:38 PM

Marc said:

Cant get this work with Masterpages and script manager in Master... :s could not reference to object named "_PageRequestManager" for "datacontext" property on object of Type "sys.Binding" any help ? its working on standalone but in masterpages :s
# October 13, 2006 1:51 PM

Bertrand Le Roy said:

Marc: I don't think this has anything to do with the code sample in this post, which only uses client-side logic and thus will not be affected by anything server-side. I'm suspecting that removing the accordion from the page entirely you would still have the problem.

Anyway, you should probably start using the Atlas Toolkit's accordion control, which is open-source and more advanced than this sample.

# October 13, 2006 3:08 PM
Leave a Comment

(required) 

(required) 

(optional)

(required)