As we seen in my last post, declarative is very powerful. Let's now see how simple it is to use his own custom behavior.

First of all, we need to create a behavior. In this example, we will use a simple HoverBehavior. His goal is just to swap a CSS class when mouse is over a DOM element.

To create a behavior, you'll need to:

  • Inherits from Sys.UI.Control
  • Override initialize and dispose methods
  • Implement your behavior logic

Our behavior is really simple, only one public property to define the hoverCssClass, the actual one is internally track into a private member.

Aurelien.UI.HoverBehavior.prototype = {
    _actualCssClass: '',                                            // Private member used to track original css class
    _hoverCssClass: '',                                             // Private member for hoverCssClass Property
    _hoverHandler: null,                                            // Private member used to track hover handler
    _blurHandler: null,                                             // Private member used to track blur handler
    initialize: Aurelien$UI$HoverBehavior$initialize,               // Initialize override
    dispose: Aurelien$UI$HoverBehavior$dispose,                     // Dispose overrive
    _hover: Aurelien$UI$HoverBehavior$_hover,                       // Private method used to handle hover event
    _blur: Aurelien$UI$HoverBehavior$_blur,                         // Private method used to handle blur event
    get_hoverCssClass: Aurelien$UI$HoverBehavior$get_hoverCssClass, // hoverCssClass getter
    set_hoverCssClass: Aurelien$UI$HoverBehavior$set_hoverCssClass  // hoverCssClass setter
}
 
Aurelien.UI.HoverBehavior.registerClass('Aurelien.UI.HoverBehavior', Sys.UI.Control);

If you wonder why I'm using this strange "$" syntax in function name then you should have a look to Bertrand Leroy's post around JavaScript Stack Trace.

In the Initialize override, we define and attach event handlers for "mouseover" and "mouseout":

function Aurelien$UI$HoverBehavior$initialize() {
    if (arguments.length !== 0) throw Error.parameterCount();
    if (this._hoverHandler == null) {
        this._hoverHandler = Function.createDelegate(this, this._hover);
        $addHandler(this.get_element(), 'mouseover', this._hoverHandler);
    }
    if (this._blurHandler == null) {
        this._blurHandler = Function.createDelegate(this, this._blur);
        $addHandler(this.get_element(), 'mouseout', this._blurHandler);
    }
    Aurelien.UI.HoverBehavior.callBaseMethod(this, 'initialize');
}

Don't forget to release them in the Dispose override:

function Aurelien$UI$HoverBehavior$dispose() {
    if (arguments.length !== 0) throw Error.parameterCount();
    if (this._hoverHandler != null) {
        $removeHandler(this.get_element(), 'mouseover', this._hoverHandler);
        this._hoverHandler = null;
    }
    if (this._blurHandler != null) {
        $removeHandler(this.get_element(), 'mouseout', this._blurHandler);
        this._blurHandler = null;
    }
    Aurelien.UI.HoverBehavior.callBaseMethod(this, 'dispose');
}

Last part is around events handling. We track the actual CSS class and swap to hoverCssClass in the "mouseover" event handler to set it back through "mouseout" one:

function Aurelien$UI$HoverBehavior$_hover(eventElement) {
    this._actualCssClass = this.get_element().className;
    this.get_element().className = this.get_hoverCssClass();
}
 
function Aurelien$UI$HoverBehavior$_blur(eventElement) {
    this.get_element().className = this._actualCssClass;
}

Let's now take a look to the declarative part of this example.

We first need some CSS classes:

.HoverCountry
{
    color: Red;
}
.HoverCity
{
    color: Blue;
}

To use our own behavior, we need to include our JavaScript file through ScriptManager:

<asp:ScriptManager ID="ScriptManager1" runat="server" EnableHistory="false" EnablePageMethods="false"
    EnablePartialRendering="false" EnableSecureHistoryState="false">
    <Scripts>
        <asp:ScriptReference Path="~/AjaxPreview2/MicrosoftAjaxTemplates.debug.js" />
        <asp:ScriptReference Path="~/Aurelien.UI.HoverBehavior.debug.js" />
    </Scripts>
</asp:ScriptManager>

We also need to declare a namespace on the <body> tag:

<body xmlns:sys="javascript:Sys" 
      xmlns:dataview="javascript:Sys.UI.DataView" 
      xmlns:hover="javascript:Aurelien.UI.HoverBehavior"
      sys:activate="dataview1">

We're now ready to use our behavior declaratively in our template:

<div id="dataview1" class="sys-template" sys:attach="dataview" dataview:data="{{ arrayCountries }}">
    <ul>
        <li sys:attach="hover" hover:hovercssclass="HoverCountry">{{ CountryName }}
            <ul class="sys-template" sys:attach="dataview" dataview:data="{{ Cities }}">
                <li sys:attach="hover" hover:hovercssclass="HoverCity">{{ CityName }}</li>
            </ul>
        </li>
    </ul>
</div>

And here's the result:

declarativecustombehavior

Tips:

As you can see in the template, the behavior property is lowercased.

Actually, if you want to use hover:hoverCssClass as defined in the behavior, don't use the .debug.js version of the preview because it will throw an InvalidOperationException: "Invalid attribute name 'hoverCssClass'. Declared attribute names must be in lowercase".

Seem's strange? Actually not. For me, there is two reasons for that. First one is technical, if you look at the JavaScript code, all the string comparison around object/method properties are in lowercase. The second reason, for me, is that the Framework ensure the XHTML Compliance Rule: Attributes names should be in lowercase.

Exception is not thrown with the release version of the JavaScript file because compacted version doesn't contains exceptions handling.

Following this post regarding Master-Details implementation by the programmatic way, let's see how to do it through declarative way.

This sample is based on the ASP.NET AJAX CodePlex Preview 2.

Let's first create an .aspx page and add a ScriptManager to link the MicrosoftAjaxTemplates.js :

   1: <asp:ScriptManager ID="ScriptManager1" runat="server" EnableHistory="false" EnablePageMethods="false"
   2:     EnablePartialRendering="false" EnableSecureHistoryState="false">
   3:     <Scripts>
   4:         <asp:ScriptReference Path="~/AjaxPreview2/MicrosoftAjaxTemplates.js" />
   5:     </Scripts>
   6: </asp:ScriptManager>

Let's now create a JavaScript array with some Master-Details datas, in this example, we will use some countries/cities :

   1: <script type="text/javascript">
   2:     var arrayCountries = [
   3:         {
   4:             CountryName: 'France',
   5:             Cities: [
   6:                 { CityName: 'Lille' },
   7:                 { CityName: 'Paris' },
   8:                 { CityName: 'Marseille' }
   9:            ]
  10:         },
  11:         {
  12:             CountryName: 'United States',
  13:             Cities: [
  14:                 { CityName: 'Seattle' },
  15:                 { CityName: 'Washington' }
  16:            ]
  17:         }
  18:     ];
  19: </script>

Goal of the Client Template is to generate some nested unordered lists without writing a single line of JavaScript code.

To use the declarative way, we need to include some Namespaces on the <body> tag.

   1: <body xmlns:sys="javascript:Sys" xmlns:dataview="javascript:Sys.UI.DataView" sys:activate="dataview1">

As you can see, the "sys" Namespace will be used by the "sys:activate" attribute and the "dataview" Namespace will be used in the HTML code.

As we're using local datas, we will use "dataview:data" to set the datasource and don't have to wait an asynchronous call to a webservice. So we can activate the dataview on the page load through "sys:activate".

Regarding the Client Template, we will use 2 DataView, one for the Countries, other one for Cities.

   1: <div id="dataview1" class="sys-template" sys:attach="dataview" dataview:data="{{ arrayCountries }}">
   2:     <ul>
   3:         <li>{{ CountryName }}
   4:             <ul class="sys-template" sys:attach="dataview" dataview:data="{{ Cities }}">
   5:                 <li>{{ CityName }}</li>
   6:             </ul>
   7:         </li>
   8:     </ul>
   9: </div>

This code is really simple, we just attach the DataView control to DOM Elements via sys:attach="dataview" and set the datasource with dataview:data="".

DataSource of the first DataView is the JavaScript array and for the second, we're using the underlying property Cities (Cities is also a JavaScript array).

And here's the result :

masterdetaildataview 

Stay Tuned ...

More Posts