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

November 2008 - Posts

Instantiating components on template markup

All client-side template engines enable you to create HTML (feel free to go ‘duh’). What they don’t all allow is the creation of event handlers and components over the markup they generate. The general approach with those engines is to do a second pass of code over the markup to create handlers and components. This is quite unfortunate as this generally requires some knowledge of the markup (which plays against separation of concerns) or something like the introduction of marker CSS classes into the markup.

For our own template engine, we wanted event handler creation and component instantiation to be first class scenarios, and we wanted both to be possible from imperative code as well as declarative code.

Imperative code blocks

Let’s start with the imperative approach. The template engine enables the introduction of code blocks right inside the template and also exposes a $element that is a reference to the last created element from the point in the template where it is referenced. So a first approach might be to just hook events and create components from there. Sure enough, that works:

<img sys:id="{{ $id('photo') }}"
     sys:src="{{ 'Images/' + Photo }}"
     alt="{binding FirstName}" />
<!--*
$addHandler($element, "click", function() {
    alert("You clicked " + FirstName + "'s photo.");
});
*-->
<br />
<input type="text" id="{{ $id('firstName') }}"
       class="editInPlace name"
       value="{binding FirstName}" />
<!--*
$create(Bleroy.Sample.EditInPlace,
    { cssClass: "editing" }, {}, {}, $element);
*-->

Please note that the comment-based code block syntax is on its way out and will be replaced in the next preview with an attribute-based alternative. I will give more details when that is available. Suffice it to say for now that this mixing of component instantiation code into markup is not what we intended code blocks for. Code blocks are there to make simple scenarios like conditional rendering and looping over markup as easy as possible.

Unobtrusive and imperative

The better imperative approach looks very much like how you would do things outside of a template if you’re into unobtrusive JavaScript. The way you add event handlers and components over the page’s markup is by subscribing to some page ready event (pageLoad or Sys.Application.add_init in Microsoft Ajax, $(document).ready in jQuery). From that handler, you query the DOM for specific elements and add event handlers and components on those.

To do the same over a template, you handle the itemCreated event of DataView, query the DOM that was just created for the data item and then add event handlers and components.

One problem with repeating markup is to create unique IDs for the generated elements. This is relevant to the problem at hand because referencing elements by ID is by far the most common way. Wouldn’t it be nice to be able to just use getElementByID? Well, in client templates, we provide you with an easy way to both generate unique IDs and to reference elements by ID.

Unique IDs can be generated by the $id function that is part of the execution environment of templates (along with $dataItem, $element, etc.). $id takes a string parameter, which is an ID that is unique within the template, and combines it with the current data item’s index to generate an ID that can be unique within the page:

<img sys:id="{{ $id('photo') }}" sys:src="{{ 'Images/' + Photo }}"
     alt="{binding FirstName}" />

To reference those elements -even if you don’t know the pattern $id uses to globalize the id-, you can use the getElementById method that is provided by the template context, which is conveniently available from the event arguments of itemCreated:

args.get_templateContext().getElementById("photo")

Here’s what the code to add an event handler and a behavior looks like:

function onItemCreated(sender, args) {
    var context = args.get_templateContext(),
        dataItem = args.get_dataItem();
    $addHandler(context.getElementById("photo"), "click", function() {
        alert("You clicked " + dataItem.FirstName + "'s photo.");
    });
    $create(Bleroy.Sample.EditInPlace,
           { cssClass: "editing" }, {}, {},
           context.getElementById("firstName"));
}

 

Note: there is a known bug in Preview 3 that prevents getElementByID from working correctly outside of IE. We fixed that bug already for the next preview.

Fully declarative

Of course, if you prefer a fully declarative approach, we allow that too. The template engine understands DOM-0 event handlers in pretty much the same way that the browser does outside templates (we tried to apply the principle of least surprise here). This means that if you specify for example an onclick attribute on an element, it is understood as a string that is the source code for the body of a function that will act as a handler for the click event. The template engine also supports binding expressions on attributes and this is no exception. That means that you can actually build that string expression that will become the body of the handler dynamically using the current data item:

<img sys:id="{{ $id('photo') }}" sys:src="{{ 'Images/' + Photo }}"
  alt="{binding FirstName}"
  onclick="{{'alert(\'You clicked '+FirstName+'\\\'s photo.\');'}}"/>

Important note: you should be super-careful about building such handler strings on the fly with embedded data: there is potential for injection here, if the FirstName data came from the user or an uncontrolled source. In a real application, you'd want to encode FirstName to escape any quotes. You may use Sys.Serialization.JavaScriptSerializer.serialize(FirstName) for example. 

Then, to instantiate the components, you can use our declarative syntax (which will also be the subject of a future post):

<input type="text" id="{{ $id('firstName') }}" class="editInPlace name"
       value="{binding FirstName}"
       sys:attach="inplace" inplace:cssclass="editing"/>

What it looks like

Here’s what the page looks like (no matter which of the three versions of the page you choose):

Conclusion

There are plenty of options to add event handlers and components over template-generated markup in Microsoft Ajax, catering to different coding styles, but we hope we succeeded in keeping the system as simple as possible while keeping all relevant scenarios possible.

Download the full code fro the demo here:
http://weblogs.asp.net/blogs/bleroy/Samples/EventsAndBehaviorsInTemplates.zip

Should HTML be considered as a data format?

As HTML is becoming more and more semantic, at least in intent, and all styling is moving into CSS, one has to wonder what it is now representing. It seems like it is now a format for unstructured data (a.k.a. rich text), in the same sense that XML and JSON are formats for semi-structured and structured data and CSV is a format for tabular data.

If that is the case, it should become commonplace that this data gets rendered by a variety of clients, not just browsers. This has already begun of course: an RSS feed reader for example consumes HTML, word processors can read and write HTML, e-mail clients use HTML for rich text. Naturally most of the times, these applications work by embedding a web browser but it doesn’t need to be the case.

If HTML becomes truly semantic (and if we can ignore the huge majority of existing contents that is less than ideally written), you could imagine it being rendered in many different ways. For example, you could collapse it to outlines, you could consume it as a repository or even display it in a completely different, non CSS-driven rendering engine. The point here is that there is an opportunity to take this decoupling of data and its graphical representation that semantic HTML and CSS promise and use HTML to its full potential as a data format.

I realize these thoughts might seem a little vague. This post really is a call for comments and ideas. Does this make sense or are we in the middle of Obviousland?

Posted: Nov 26 2008, 09:00 AM by Bertrand Le Roy | with 6 comment(s) |
Filed under:
Is English speaking necessary for developers?

There’s been quite a few blog posts and tweets lately around the following (slightly distorted) quote:

“If you don’t know English, you’re not a programmer”

The original quote was a little different but almost as offensive so I won’t dignify it with a link. The best discussion on this quote can probably be found on Scott’s blog. It quickly morphed into a discussion on whether you needed to speak English to be a *great* developer. I still disagree with this simply because I know several excellent developers who speak little or no English. Of course I won’t disagree that it’s harder, and that knowing English certainly is a (big) plus.

But rather than rehashing various arguments I’d like to bring some perspective to this topic.

Please try to imagine for just a minute how different your world would be if Chinese developers were the overwhelmingly dominant community in the field. This is actually not that far fetched and might actually happen at some point in the future. After a while, wouldn’t a good part of the literature be available in Chinese only? So in this hypothesis, let me ask you…

Would you then be willing to learn Chinese? Would you then agree with the following?

“如果你不会英文, 你就不是一个程序员”

(Thanks to Hong Li for the translation)

Simplifying the edit in place behavior

Last week, I wrote about building a simple behavior to edit text in place. Almost at the same time, Nikhil was building a similar component for Silverlight, but it was considerably simpler because instead of substituting a label for the textbox on blur, he was just changing the border so that the textbox resembles a label. This is a lot simpler, cleaner and more stable. The textbox always behaves according to what one would expect from a textbox because it never ceases to be a textbox. No focus to manage, nothing to hide and show, just styles to change.

I had actually already done a similar simplification from Nikhil’s original behavior that he wrote a few years ago by not handling the hover effect from the component but from CSS. Changing a style on hover is now best done by adding a “:hover” style, which all modern browsers can handle on any element.

We could in principle take this simplification all the way and get rid of the behavior altogether to rely only on CSS if all modern browsers could handle CSS 2.1’s “:focus” pesudo-class. Unfortunately as of today, even in version 8, Internet Explorer does not. It should in RTM as it is supposed to fully support CSS 2.1 but that won’t be usable for a while as previous versions will remain prevalent for some more time.

Anyway, I modified the behavior to use that method of restyling instead of the label substitution used before and was able to reduce the code size further down to 70 lines. You can download the updated behavior from here:

http://weblogs.asp.net/blogs/bleroy/Samples/EditInPlace/EditInPlace.js.txt

Getting a reference to a behavior

In the last post, I showed how you can instantiate multiple behaviors on a single input element, through server extenders or directly through client behaviors (which themselves can be created imperatively or declaratively). In this post, I want to show how to get a reference to these behaviors.

Of course, the simplest is to grab that reference when you create the behavior and hold on to it until the next time you need it:

var tb1EditInPlace =
    $create(Bleroy.Sample.EditInPlace, { cssClass: "editInPlace" },
            {}, {}, $get('tb1'));

Now of course, the code that needs to use a reference might not be from the same source or area of responsibility as the one that created it. If you created the behavior through declarative markup, for example (the framework creates the instance in this case, not your application directly).

Fortunately, getting a reference to a behavior is very simple and can be done in a number of ways. First, you can give it an explicit id:

$create(Bleroy.Sample.EditInPlace,
        { id: "tb1EditInPlace", cssClass: "editInPlace" },
        {}, {}, $get('tb1'));

You can then retrieve it by just calling Sys.Application.findComponent, or $find for short:

var ref = $find('tb1EditInPlace');

Even if you don’t define an explicit id though, we’ll define one for you. The default id for a behavior is the id of the element concatenated with a ‘$’ character and the type name of the behavior. So if you defined the behavior this way:

$create(Bleroy.Sample.EditInPlace,
        { cssClass: "editInPlace" }, {}, {}, $get('tb1'));

You can then reference it like so:

var ref = $find('tb1$EditInPlace');

You can also define a name for the behavior instead of an id and this will pretty much replace the default (which we just saw is the type name):

$create(Bleroy.Sample.EditInPlace,
    { name: "inplace", cssClass: "editInPlace" },
    {}, {}, $get('tb1'));

And then reference the behavior this way:

var ref = $find('tb1$inplace');

The behavior is also added to the element as an expando attribute that has the same name as the behavior. This means that in the previous case, this will also work:

var ref = $get('tb1').inplace;

And in the ones before that:

var ref = $get('tb1').EditInPlace;

The key takeaway here is that it is always possible to get a reference to a behavior no matter how or where it was created.

Putting more than one behavior on one element

Microsoft Ajax has the interesting ability to combine more than one component onto a single element. In the previous talk, I alluded to this possibility and one of the commenters (Tiamat) asked me to show how this is done.

Here is an example that combines the new EditInPlace behavior I showed yesterday with an AutoComplete and a Watermark from the Ajax Control Toolkit:

<%@ Page Language="C#" %>
<%@ Register Assembly="AjaxControlToolkit"
Namespace="AjaxControlToolkit" TagPrefix="act" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <
html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>Many behaviors on a textbox</title> <link href="template.css" rel="stylesheet" type="text/css" /> </head> <body> <form id="form1" runat="server"> <act:ToolkitScriptManager runat="server" ID="sm1"
EnablePartialRendering="false"> <Scripts> <asp:ScriptReference Path="~/Script/EditInPlace.js" /> </Scripts> </act:ToolkitScriptManager> <script type="text/javascript"> Sys.Application.add_init(function() { $create(Bleroy.Sample.EditInPlace,
{ cssClass: "editInPlace" }, {}, {}, $get('<%= tb1.ClientID %>')); }); </script> <div> <h1>Many behaviors on a textbox</h1> <asp:TextBox runat="server" ID="tb1" /> <act:TextBoxWatermarkExtender runat="server" TargetControlID="tb1" WatermarkText="Click me..."/> <act:AutoCompleteExtender runat="server" TargetControlID="tb1" ServicePath="Words.asmx" ServiceMethod="GetWords" BehaviorID="AutoCompleteEx" ID="autoComplete1" MinimumPrefixLength="3" CompletionInterval="500" EnableCaching="true" CompletionSetCount="20" CompletionListCssClass=
"autocomplete_completionListElement"
CompletionListItemCssClass="autocomplete_listItem" CompletionListHighlightedItemCssClass=
"autocomplete_highlightedListItem"
DelimiterCharacters=";, :" ShowOnlyCurrentWordInCompletionListItem="true" > <Animations> <OnShow> <Sequence> <OpacityAction Opacity="0" /> <HideAction Visible="true" /> <FadeIn /> </Sequence> </OnShow> <OnHide> <FadeOut /> </OnHide> </Animations> </act:AutoCompleteExtender> </div> </form> </body> </html>

The sample page here is using the extenders that the toolkit provides to instantiate the AutoComplete and Watermark behaviors but you really could include the script references and $create yourself. The edit in place behavior doesn’t have an associated server-side extender (but it would be quite easy to build one) so I’m just including the script and calling $create from client script. If you run this sample, you’ll see something like this:

If you view source in the browser, you’ll see that the code that the extender generates for the browser to run isn’t magical in any way, it’s just script tags and $create like we’d do manually.

One thing to note is that I hit a bug in the Toolkit while building that sample: AutoComplete assumes the element it attaches to is visible when it calls focus on it, which is usually OK but fails here when used with the edit in place behavior. I fixed this bug by surrounding the focus call with a try/catch block. A build of the toolkit with this fix is included in the zip file for this sample.

Bottom line is that more than one behavior on a single tag works great and makes for a formidable composition model.

Download the code for this sample:
http://weblogs.asp.net/blogs/bleroy/Samples/ManyBehaviors.zip

Building a neat edit in place behavior

For the purposes of my next post, I built a neat little edit in place behavior and I thought it deserved its own post. It does a pretty good job at showing how easy it is to build a clean behavior using ASP.NET Ajax. It’s always good to go back to the basics… In this post, I’ll show you how the behavior works, but more importantly how I built it.

The behavior is pretty similar to the in-place editing behavior Nikhil built a while ago (but it works against the latest Ajax framework). It attaches to any text input or text area. In the absence of JavaScript, nothing happens and the input is directly usable, which makes for a graceful degradation story. When JavaScript is enabled, the behavior hides the input and replaces it with a span that has the same dimensions and the same contents. Clicking on the span hides it, brings back the input and gives it focus, enabling edition of the value. When focus moves away from the input, the behavior hides the input again, copies the new value into the span and brings it back. Please note that for accessibility, you can also tab to the span instead of clicking on it. Check out the video, it shows that much better than any text description ever could:

The EditInPlace behavior does all that in 80 lines of readable code, doc-comments included. Before we look at some key parts of the code, let’s look at how you can use the control:

<input type="text" id="email" value="bleroy@example.com"/>
<script type="text/javascript" src="Script/MicrosoftAjax.js"></script>
<script type="text/javascript" src="Script/EditInPlace.js"></script>
<script type="text/javascript">
    Sys.Application.add_init(function() {
        $create(Bleroy.Sample.EditInPlace,
        { cssClass: "editInPlace" }, {}, {}, $get("email"));
    });
</script>

Include the library and behavior scripts, throw in some input or text area, $create the behavior over the input, setting properties such as cssClass if you have to. In the example, I’ve defined the style “editInPlace:hover” in the CSS so that the user gets this nice hint something might happen if you click when you hover over the control. This is the effect you see in the first few seconds of the video. Pretty simple.

Of course, you can also use the new declarative syntax once you’ve included MicrosoftAjaxTemplates.js:

<body xmlns:sys="javascript:Sys" sys:activate="*"
      xmlns:inplace="javascript:Bleroy.Sample.EditInPlace">
    <input type="text" id="email" value="bleroy@example.com"
           sys:attach="inplace" inplace:cssclass="editInPlace" />

Here, the xmlns are the equivalent of server-side @Register directives in ASP.NET, sys:activate asks the framework to instantiate all declarative controls on the page and sys:attach does the actual instantiation. The properties of the behavior are set by using attributes with the prefix that we defined earlier for it. This declarative snippet is equivalent to the previous one that was using $create.

Let’s now look at a few aspects of building this behavior. First, let’s look at the skeleton of the code:

/// <reference name="MicrosoftAjax.js"/>
Type.registerNamespace("Bleroy.Sample");
Bleroy.Sample.EditInPlace = function(element) {
    Bleroy.Sample.EditInPlace.initializeBase(this, [element]);
}
Bleroy.Sample.EditInPlace.prototype = {
}
Bleroy.Sample.EditInPlace.registerClass(
"Bleroy.Sample.EditInPlace", Sys.UI.Behavior);

Here, we’re declaring the only dependency of this file using an XML doc-comment. This will point Visual Studio to the Ajax library so that we get IntelliSense when for example we type “Sys.” from within that file.

Then, we’re declaring the Bleroy.Sample namespace, which is where we’ll build the behavior.

The behavior itself is defined on the next line. A type in JavaScript and Microsoft Ajax really is the same objet as its constructor. Something to get used to… The only thing the constructor does for the moment is calling its base constructor using initializeBase. In its complete version, it also does some checking on the target element’s tag name to check it’s either a text area or an input, and it initializes private fields, which is good practice for a better and more predictable debugging experience.

The next block is where the meat of the component is going to go: the prototype. This is where we’re going to define properties, events and methods.

Last, we register the class with the framework using registerClass, pointing it to its base class, Sys.UI.Behavior. A behavior is a component that attaches to an HTML element, like a control, except that there can be only one control per element (DataView is a control for example) whereas there can be as many behaviors as you want per element (which enables for example a watermark, an auto-complete and an edit in place behavior to be combined on the same textbox).

Let’s now look at the initialization phase of the behavior, where the events will get hooked from and the span will be created and added to the DOM:

initialize: function() {
    Bleroy.Sample.EditInPlace.callBaseMethod(this, "initialize");
    var elt = this.get_element(),
        span = this._span = document.createElement("SPAN");
    var bounds = Sys.UI.DomElement.getBounds(elt);
    span.style.position = "absolute";
Sys.UI.DomElement.setLocation(span, bounds.x, bounds.y); span.style.width = bounds.width + "px"; span.style.height = bounds.height + "px"; span.tabIndex = elt.tabIndex; span.className = this._class; Sys.UI.DomEvent.addHandlers(this._span, { click: this.beginEdit, focus: this.beginEdit }, this); elt.parentNode.insertBefore(this._span, elt); this._oldVisibility = elt.style.visibility; this._inputBlur = Function.createDelegate(this, this.endEdit); Sys.UI.DomEvent.addHandler(elt, "blur", this._inputBlur); this.endEdit(); }

The method first calls base using callBaseMethod. Then it creates the span element and sets its position and size to be exactly equivalent to the input tag it will have to replace so that it doesn’t upset the layout of the page. It also sets the CSS class to the current value of the cssClass property (which is stored in the private field this._class). Two handlers are added to the new span: one for click and one for focus and both point to the same method, this.beginEdit. The way we’re creating the events is by using the very handy addHandlers method, which will attach several event handlers at once and will also take care of creating delegates with the specified context (this) so that the handler can have access to all the members of the behavior instance. Finally, the new span is added to the DOM right before the input element. Once that span has been created, we can store the old visibility mode of the input for later use, create a blur delegate for the input element that will call endEdit and then call endEdit to substitute the input for the span. Notice that we created the delegate manually rather than use addHandlers here. I’ll explain why in a moment.

A good practice is to free all the resources and clean up after yourself from dispose. Let’s look how this is done:

dispose: function() {
    if (this._span) {
        Sys.UI.DomEvent.clearHandlers(this._span);
        this._span.parentNode.removeChild(this._span);
        var elt = this.get_element();
        Sys.UI.DomEvent.removeHandler(elt, "blur", this._inputBlur);
        elt.style.visibility = this._oldVisibility;
        this._inputBlur = this._spanClick = this._span = null;
    }
    Bleroy.Sample.EditInPlace.callBaseMethod(this, "dispose");
}

Here we made sure that the method can be called multiple times by testing for the presence of what we’re going to clean. If the method is ever called again it will pretty much be a no-op.

The dispose method starts by clearing all event handlers from the span element. It can do so by calling the handy clearHandlers method. We can afford to do that here because we completely own that element: we created it and managed it so we can safely use a very big hammer and crush all events without having to wonder if somebody else left their fingers on the table. For the input element that is quite different as there may be other behaviors attached to it that may have created their own events. That’s why here we’re targeting our cleaning efforts exclusively at what we created. This is why we created the delegate to endEdit ourselves so that we could keep a reference to it and use that later when removing the handler.

Other tasks in dispose include removing the span from the DOM, resetting the input element’s visibility style and clearing private variables.

And of course, the last thing we do is call base so that we can do our cleaning up without having to wonder if base already destroyed some of the objects we still need.

Now that the initial setup and cleanup are in place, beginEdit and endEdit only have some trivial showing, hiding and focusing of the elements to do:

beginEdit: function() {
    /// <summary>Puts the behavior in edit mode</summary>
    var elt = this.get_element();
    this._span.style.visibility = "hidden";
    elt.style.visibility = this._oldVisibility;
    elt.focus();
    this.raisePropertyChanged("isEditing");
}
endEdit: function() {
    /// <summary>Puts the behavior out of edit mode</summary>
    var elt = this.get_element();
    this._span.innerHTML = elt.value;
    this._span.style.visibility = this._oldDisplay;
    elt.style.visibility = "none";
    this.raisePropertyChanged("isEditing");
}

The only thing worth singling out here is that we’re taking care of triggering change notifications on isEditing every time the editing mode changes.

The rest of the code is pretty trivial property accessors that are implemented using the get_ and set_ prefixes.

I hope this helps.

Get the full source code for the behavior and the sample page:
http://weblogs.asp.net/blogs/bleroy/Samples/EditInPlace/EditInPlace.zip

UPDATE: fixed a bug in the positioning of the span.

UPDATE: Nikhil just blogged about building an edit in place behavior for Silverlight. I feel a little silly now because of the way he implemented that not by replacing the textbox with a label but by just making the border transparent on blur. This should work just as well in HTML and would make the code probably a lot simpler. I may try that when I have time. Anyway, check it out, great read as usual: http://www.nikhilk.net/Entry.aspx?id=214

UPDATE: I rewrote the behavior to use Nikhil's trick of restyling the border: http://weblogs.asp.net/bleroy/archive/2008/11/24/simplifying-the-edit-in-place-behavior.aspx

Deep Zoom without Silverlight

In a move that I wouldn’t have bet a dollar on, Live Labs released a purely JavaScript Deep Zoom client. You read that right, what was so far one of the nice features only found in Silverlight is now available in an open web, standards-based version.

Of course, from a technical standpoint, Deep Zoom is just commoditizing what Google Maps made possible years ago in pure script so there wasn’t really a reason why this couldn’t be done, except smoother transitions and zooming but that’s pretty tenuous.

The great thing about this new library is that the tools to create the Deep Zoom image are exactly the same as with Silverlight: the JavaScript client is pretty much a drop-in replacement for the Silverlight client.

Embedding the viewer into a page is YouTube-easy: give the deep zoom url and it will build the code for you to embed.

MSDN on Deep Zoom:
http://msdn.microsoft.com/en-us/library/cc645050(VS.95).aspx

Everything about the JavaScript client:
http://livelabs.com/seadragon-ajax/

Embedding the viewer:
http://livelabs.com/seadragon-ajax/embed-viewer

UPDATE: Kapil created a Python-based tile-cutting application that is compatible with both deep-zoom clients, to work around the Windows-only nature of the creation tools:
http://blog.kapilt.com/2008/11/30/sharing-large-images-openlayers-gsiv-modestmaps-deepzoom-and-python/

UPDATE: Also check out this TED talk to get a glimpse of the true potential of these technologies (all that you're seeing in this talk is publicly available by the way):
http://www.ted.com/index.php/talks/blaise_aguera_y_arcas_demos_photosynth.html

One thing you didn’t know about ASP.NET unless you’re David Ebbo

David has an excellent post about a pretty cool ASP.NET feature that you almost certainly don’t know about. I had no idea for sure. Check it out.

http://blogs.msdn.com/davidebb/archive/2008/11/19/a-hidden-gem-for-control-builder-writers.aspx

UPDATE: and there he goes again...
http://blogs.msdn.com/davidebb/archive/2008/11/20/creating-a-controlbuilder-for-the-page-itself.aspx

Preview documentation for ASP.NET Ajax 4.0 available

We published some documentation for ASP.NET Ajax 4.0 Preview 3:
http://quickstarts.asp.net/previews/ajax/templates/default.aspx

More Posts Next page »