Tales from the Evil Empire

Bertrand Le Roy's blog

News


Bertrand Le Roy

BoudinFatal's Gamercard

Tales from the Evil Empire - Blogged

Blogs I read

My other stuff

Archives

April 2006 - Posts

.NET 2.0 Book recommendation
I've been meaning to do that for a while. A few months ago, I had the pleasure to review the ASP.NET 2.0 part of Patrick Smacchia's excellent Practical .Net2 and C#2. The book is really excellent and is the most complete I know on .NET 2.0. It covers with unparallelled accuracy everything about .NET 2.0. This book is an almost 900 page masterpiece and reference book that I highly recommend.

Practical .Net2 and C#2
Conference schedule for next week in France

I'll be in France next week for a series of conferences.

Visual Studio Express editions is now free (forever)

It's official: Visual Studio Express editions are now free to download and use indefinitely. That means that Visual Web Developer 2005 ExpressVisual C# 2005 Express, Visual Basic 2005 Express, Visual C++ 2005 Express and Visual J# 2005 Express are completely free.

That is good news (in case you didn't notice). Download all this from here:
http://msdn.microsoft.com/vstudio/express/

I should probably remind everyone reading this that SQL Server 2005 Express is also completely free and that it is a fully enabled SQL Server engine with absolutely no connection number limit, and now full-text search and reporting services (the limitations when compared to the full version are the number of CPUs, database size, mirroring, data mining and other high-end stuff). There is also a very useful free management tool in addition to the Visual Studio integration.

w00t

Did you mean: hgdgf?

I was testing the network connection on my laptop a few minutes ago and typed some random characters in Google (hgjhgk), just scrambling fingers on the keyboard. To my surprise, I got ten pages of results (of which this post will soon be part):
http://www.google.com/search?hl=en&q=hgjhgk

More surprisingly, Google is asking me if I really meant "hgdgf". Err, I mean, not exactly, but whatever. So I clicked on the link out of curiosity and it seems like there is a gangsta rap singer named hgdgf who authored an album named "Jhehjhrfjkd". The lyrics are even more interesting (I hope I'm not infringing any copyright here):
"hjgdjgfdhfghjsdgfjhegfhjegjhgfduzfusrdjksfgdjfgjdsghfuifj
ghuzfuzgfgrzzrzrhhrhhhrrjjkfurirjjfufurjrfurujrufufhrhfifzrhjrfhrfufhjrifurhfiuh
rufzrjfifurjfufjrjfurjruffjjfujrjruuuuuuuufroriuf8iek8ir8rie8f8f".

From this I can conclude two things:

  • Search engines and the Web are endless sources of amazement.
  • I do not understand rap music.
XmlHttpRequest now a W3C standard

Contrary to what I said in my previous post, XmlHttp is now a W3C standard (draft):
http://www.w3.org/TR/XMLHttpRequest/

(found via Christian Wenz, who is the author of an excellent book on Atlas)

On a side note, it's also natively implemented in IE7 which means no more ActiveX.

Let me explain one more time why the Atlas compatibility layer works this way...

It's the second time in just a few days that I see blog comments attack Atlas on its compatibility layer. I've tried to explain a few days ago why we made this design choice but I think it deserves an even more detailed explanation...

There are basically three ways you can make a library cross-browser.

  • You can detect capabilities every time you need them. If you do that, all new code must take all browsers into account. Adding a new browser means touching all of the code base.
  • You can write an abstraction layer that creates another API that abstracts the concrete APIs of each browser. This is no more standard than the other two ways as you're really introducing a new proprietary API on top of the existing standard and proprietary ones.
    Furthermore, this is making the code path longer as each use of the API must go through an additional function call and must redo the capability check every time it's called.
  • Finally, and this is what Atlas is doing, you can extend one or several of the browsers to make all browsers consistently callable. In this case, the code path is the most direct and the capability detection happens only one time, when the compatibility layer is created. An additional advantage of this technique is that you can add a new browser by simply writing its compatibility layer, without having to touch the actual library.

Now, why did we extend Firefox and Safari and not IE? Simply because Firefox and in a lesser extent Safari are far more extensible than IE. For example, you can modify the prototype of HTML elements and thus act once to extend all instances of all elements. Firefox goes even further by allowing the definition of getters and setters for element properties. If IE had the same extensibility, we would happily have made it converge to the standard instead of bringing Firefox and Safari to implement the IEisms. We would have absolutely loved that but it's simply not possible.

There is no malice there, purely technical reasons.

Furthermore, let me point something out. It's very well that Safari and Firefox follow the standards when they exist and I'm glad they do but it's not enough for Ajax applications because they *all* rely on non-standard capabilities. XmlHttp is not a standard for example.

Here's another example where despite its excellent adherence to standards, Safari is making things difficult: how do you dynamically load arbitrary script and get notified when the script is done loading? Well, Safari has only recently implemented dynamic script loading but as far as I know (and the documentation is far from sufficient on this subject) there is no way you can get notified when it's done.

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 7 comment(s)
Filed under:
Check out the new "Atlas" Control Toolkit

Shawn and his team just released a set of Atlas controls that you might want to check out. It contains some useful controls and extenders such as TextBoxWatermark or ToggleButton (I really like how this one is implemented because it's really using an HTML checkbox despite appearances which is the right semantics and makes it really transparent to use). The controls come with full source code that the license enables you to modify and use as you wish.
http://atlas.asp.net/default.aspx?tabid=47&subtabid=477

Live examples are available so that you can check how the controls really work before you decide to use them.

The package also contains a toolkit that facilitates the creation of Atlas controls and extenders with some helper classes and templates:
http://atlas.asp.net/atlastoolkit/Walkthrough/CreatingNewExtender.aspx

Posted: Apr 12 2006, 04:56 PM by Bertrand Le Roy | with no comments
Filed under:
Atlas compatibility layer: why did we extend Firefox to implement IE-isms?

Microsoft does not enjoy the best reputation when it comes to respecting standards. So when seeing that Atlas has a compatibility layer that extends Firefox and Safari APIs to implement IE-isms, some have seen it as an attempt to discard the standard.

The reason why Atlas extends Firefox and Safari to implement the IE APIs and not the other way around is in fact purely technical. Firefox and in a lesser extent Safari allow the extension of HTML element prototypes whereas IE does not. Standards have nothing to do with this decision. There is a need to have a consistent API cross-browser to avoid having the rest of the libraries checking for the existence of an API and branching the code all the time. For example, instead of constantly doing if (obj.attachEvent) {obj.attachEvent(...)} else {obj.addEventListener(...)}, we can just do attachEvent and it works cross-browser. We (and the MSN team before us) just chose the simplest possible way to achieve that. The way people used to do it before was to implement helper methods to encapsulate the branching, which if you think about it is not any more standard than what we're doing, it was just introducing a third way to do it in addition to the standard one and to the IE-ism.

I'd like to hear your constructive thoughts about that but please don't see any malice where there is none. ;)

Mix06 demo part 2: building the accordion control

In the previous post, I've shown how to use the accordion control. Today, I'm going to explain how to build such a control. I'll try to give as much background as possible on the different patterns in this sample control. This article is going to be fairly technical, so please keep in mind that you don't need to know any of this to use the control. This post is mainly for people who want to build their own Atlas client-side controls.

The first thing you need to do is to create a namespace. In Atlas, everything is namespaced and we've tried to keep the number of global-scoped objects to a minimum to avoid conflicts between components (exactly like in managed code). Namespaces are not a native JavaScript concept, so we simulated that using plain objects. Here, we're using a simple namespace called Dice because the demo it was developed for was called that way, but you can have as deeply nested a namespace as you want:

Type.registerNamespace('Dice');

Now we can define the accordion class. In JavaScript, classes are expressed as functions which are really the constructor of the class. When you new up an object using a constructor function, the function is run and the result is an object of this type. Technically, the constructor becomes a field of the namespace but you don't really need to be aware of that and by just following the convention you'll get something that's really close to real namespaces and classes. To get object-oriented semantics that are closer to managed code, we provide a few helper methods and implement our classes following a pattern:

Dice.Accordion = function(associatedElement) {
    Dice.Accordion.initializeBase(this, [associatedElement]);
    // ...
}
Dice.Accordion.registerClass('Dice.Accordion', Sys.UI.Control);

The above code is roughly equivalent to the following C# declaration:

namespace Dice {
  public class Accordion : Sys.UI.Control {
    public Accordion(associatedElement) : base(associatedElement) {
    }
  }
}

If you want your class to implement interfaces in addition to inheriting from a base class, you can just add them as additional parameters in the registerClass after the base class (Sys.UI.Control here).
All controls in Atlas take the associated HTML element as a constructor parameter. To enable our control to work declaratively, we also need to declare it as a tag to the global type descriptor and we need to expose a description of all properties, events and methods that need to be accessible from xml-script. The global type declaration is done by adding this line after the registerClass:

Sys.TypeDescriptor.addType('dice', 'accordion', Dice.Accordion);

The first parameter here is the tag prefix, the second is the tag name and the last is the type itself. For the moment, there is no way for the page developer to redefine the tag prefix that you chose so choose it well like you would for a namespace, with conflict minimization in mind (you don't want to use "controls" or "library" or something as generic but more something along the lines of "myCompanyMyProject"). Now that this is done, xml-script can instantiate the control using a <dice:accordion id="someElement"/> element if the dice namespace has been added to the page tag (xmlns:dice="http://schemas.microsoft.com/xml-script/2005/dice"). The description of the declaratively accessible members is done by adding this method to the class:

this.getDescriptor = function() {
    var td = Dice.Accordion.callBaseMethod(this, 'getDescriptor');
   
    td.addProperty('viewIndex', Number);
    td.addProperty('transitionDuration', Number);
    return td;
}
Dice.Accordion.registerBaseMethod(this, 'getDescriptor');

Here, registerBaseMethod declares the method as virtual. The method calls its base class implementation using callBaseMethod which enables it to inherit all the declarative members from Sys.UI.Control. We're adding two properties of type Number: viewIndex and transitionDuration. Actually implementing these properties is easy once you know the convention that property getters and setters are declared using the get_ and set_ prefixes:

var _viewIndex = 0;
var _duration = 0.5;

this.get_transitionDuration = function() {
    return _duration;
}
this.set_transitionDuration = function(value) {
    _duration = value;
}

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

The underscored variables here are limited to the scope of the function which means that they are inaccessible to code outside of the function (which is the class). So this is equivalent to private fields. We have two different patterns here. The transitionDuration property is implemented with trivial accessors whereas the viewIndex property is implemented with side effects in the setter when the set value is different from the current one. We're not really interested in monitoring the changes of the transition duration so we just don't do anything special as there would be an unnecessary cost associated with that. Now the viewIndex property is the center of the behavior of our accordion control. Whenever this is set, we want the control to transition from its current pane to the one that's being set so we need to call the private function that sets the current pane (we'll come to that in a moment) and we need to raise a change notification so that other components binding to this property can pick up the changes automatically.
At this point, we've built the scaffolding of our control but we need to implement its actual behavior. Let's start with the method that will show the current pane and hide the others. This method is implemented as a private function (notice there is no this. in the declaration, which will limit its scope to the class). The underscore in front of the property is a convention to indicate a private member.

function _ShowCurrentPane(animate) {
    for (var i = _viewPanes.length - 1; i >= 0; i--) {
        var pane = _viewPanes[i];
        if (animate) {
            var anim = _getAnimation(i);
            if (anim.get_isPlaying()) {
                anim.stop();
            }
            anim.set_startValue(pane.offsetHeight);
            anim.set_endValue((i == _viewIndex) ? pane.scrollHeight : 1);
            anim.play();
        }
        else {
            pane.style.overflow = 'hidden';
            if (i != _viewIndex) {
                pane.style.height = '1px';
            }
        }
    }
}

The function is looping over the view panes and hides or shows them. It has two modes of operation. One just uses overflow:hidden styles and sets a height of one pixel to hide a pane while the other uses an animation. The reason why we need an unanimated mode is that during initialization we won't want the animation. When we do want the animation, for each panel we stop the current animation, reinitialize it to the new parameters (from its current height to the full scrollHeight of the pane or one pixel depending if it's the current one or not) and play it. It uses another private method to get the animation for a given panel:

function _getAnimation(index) {
    var anim = _animations[index];
    if (!anim) {
        _animations[index] = anim = new Sys.UI.LengthAnimation();
        var pane = _viewPanes[index];
        pane.style.overflow = 'hidden';
        anim.set_target(pane);
        anim.set_property('style');
        anim.set_propertyKey('height');
        anim.set_duration(_duration);
        anim.initialize();
    }
    return anim;
}

This function is basically building a LengthAnimation which is a type of animation that is defined in the Atlas Glitz library that animates a length style property such as height from one value to another. It's also setting the overflow style of the pane to hidden so that changing the height will actually hide the overflowing contents.

What we now need to do is to wire up the click events on the pane headers so that they trigger a pane transition. By the way, that's an important pattern in Atlas: you never wire up your events from the HTML elements, but do it from control initialization code instead (or xml-script as a page developer). This helps to keep a good separation of layout and behavior and it also prevents bugs where the event is wired and fired before the handler function is defined.

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

    _viewClickHandler = Function.createDelegate(this, _onViewClick);
   
    var children = this.element.childNodes;
    var isHead = true;
    for (var i = 0, p = 0; i < children.length; i++) {
        var child = children[i];
        if (child.nodeType == 1) {
            if (isHead) {
                _viewHeads.add(child);
                child.viewIndex = p++;
                child.attachEvent('onclick', _viewClickHandler);
            }
            else {
                _viewPanes.add(child);
            }
            isHead = !isHead;
        }
    }
    _ShowCurrentPane.call(this, false);
}
Dice.Accordion.registerBaseMethod(this, 'initialize');

The initialization code is looping over the child elements of the associated element (this.element). It considers every other child as the head or the pane. References to the heads and panes are kept in private arrays and the click event is wired to the _onViewClick private method. We also set the view index on each head element as an expando property to be able to easily find the index of a view from its header element.

The way the click event is wired is another very important pattern in Atlas: we have partially recreated the concept of a delegate, which is a pointer to an object method. It's not just a function pointer because that would not retain the object's context. In other words, if you passed _onViewClick directly as the event handler, you would be unable to access "this" from the function. Using createDelegate ensures that your function will be able to work exactly as if you were calling it directly, with access to "this" and to private variables. Those who want to understand how it works can look at the implementation of createDelegate, which is using a very simple closure.
The click handler itself is fairly simple as it just needs to find which header was clicked (looping through the parents of the clicked node if necessary) and just sets the view index of the control to that of the clicked header:

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

The last thing that we need to do is some housekeeping. As some of you know, web browsers, and IE6 in particular, can have memory leaks under certain circumstances. One such circumstance is the existence of circular references between Javascript objects and HTML elements. We need to implement dispose for our control and make sure that all such references are broken by detaching events and freeeing all references we may be keeping:

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;
    }
    for (var i = _animations.length - 1; i >= 0; i--) {
        if (_animations[i]) {
            _animations[i].dispose();
            delete _animations[i];
        }
    }
    Dice.Accordion.callBaseMethod(this, 'dispose');
}
Dice.Accordion.registerBaseMethod(this, 'dispose');

This is it, we now have a functional Accordion control with nice animations. In the next posts, I'll show how to create a server control extender.

The complete source code for this control (and the rest of the demo) can be downloaded from Brad's blog:
http://blogs.msdn.com/brada/archive/2006/03/29/563648.aspx

The original post showing how to use the control is here:
http://weblogs.asp.net/bleroy/archive/2006/03/28/441343.aspx

UPDATE: corrected bad use of delete.

More Posts Next page »