Getting jQuery Goodness into ASP.NET AJAX

If you have been following along with me over the past few days you have seen that I am looking into ways to incorporate some of the goodness of jQuery into ASP.NET AJAX.  Of course the obvious question is why don't I just switch over to jQuery completely?  Well ...

The team I work on is not switching from WebForms to MVC any time soon. And I am fine with that - we have a bunch of good dev's all with plenty of years of WebForms experience so we understand all of the quirks and can get past any of the weirdness that sometimes pops up.

But I don't really want to use 3 ajax libraries (jQuery, ASP.NET AJAX and the Toolkit). But I like how easy it is to author and distribute jQuery plugins and I wish there was a similar pattern for ASP.NET AJAX.

I posted that bit as a follow up to comment George left to my previous post.  If I was starting from scratch using ASP.NET's MVC engine I would definitely just use jQuery as my client side library and be done with it.  But that just doesn't make sense for my project.  So in the mean time, I would like to try to get some jQuery-like features into my WebForms application.  Specifically what I want is a better selector function, a more fluent programming model and a simple framework for authoring and distributing plug-ins.

Sound interesting?  Read on to find out how I am going to get there ...

Live Demo | Download

 

What's on my Wish List

This is some of the stuff I want ...

 

1. A Better Selector

$get fetches elements by ID.  And that's OK, but it would be great if there was a more robust selector method that allowed me to find elements by any CSS selector.  So I could do stuff like this (see below). 

   1: //  Find me the alternating rows of the #orders table
   2: var alternatingRows = $select('#orders TR.alt');
   3:  
   4: //  ... or even better
   5: var alternatingRows = $select('#orders TR:odd');   

 

2. Programming Model - Events

Next, I would want a slightly different programming model for the DOM elements that match a selector.  I would like to be able to do things like this (see below).  So much of AJAX is navigating the DOM then doing something with the resulting elements (like attaching event handlers).  I think it would be nice if the programming model had better support for these common tasks.

   1: //  Find me the rows of the #orders table
   2: $select('#orders TR').each(function(e) {
   3:     //  add the over class while the mouse is hovering
   4:     e.mouseover(function() { this.addCssClass('over'); });
   5:     //  remove the class when the mouse leaves
   6:     e.mouseout(function() { this.removeCssClass('over'); });
   7: }

 

3. Programming Model - Extend Elements without using the TargetControlID

ExtenderControl's use the TargetControlID property to wire the extension logic to a specific control via the extender control's markup.  This works well, but in many cases I would actually prefer doing something like this instead ...

   1: $select('watermark').each(function(e){
   2:     //  add the watermark to all of the elements tagged
   3:     //  with the watermark CSS class
   4:     e.watermark());
   5: }); 

 

I use the TextBoxWatermarkExtender in a lot of the pages I build, but I have never once needed to access the WatermarkText or WatermarkCssClass from the server.  For my tastes I would really prefer to use the above syntax to apply watermarking.  I would rather fetch the elements I want watermarked using a selector and than provide these elements to the watermarking extender using the above syntax.  I think watermarking falls nicely into the 'find some DOM elements and do something useful with the resulting elements'.   

 

4. JavaScript only plug-ins

And finally, I would like to see a simple way to author and distribute JavaScript only plug-ins.  There are plenty of Toolkit users, but not too many people are building Toolkit controls.  I can't help but think that part of this is because there is a bit of a learning curve for creating Toolkit controls.  I think it would be neat if we could create JavaScript only plug-ins for ...

  • Some of the cool jQuery, DoJo, MooTools, Prototype, [Pick your favorite JavaScript library] stuff that is already out there.  I realize that because of the WebForms programming model some of this stuff won't port directly, but I would think much of it will.  It would be cool if we could create a simple JavaScript plug-in framework that so us WebForms developers could make use of some of these AJAX widgets.
  • The existing Toolkit extender controls that fit naturally into plug-in programming model.  For starters I think the following controls would make good candidates ...
    • RoundedCorners, MutuallyExclusiveCheckBox, ToggleButton, DropShadow and DragPanel

 

Sounds Good ... Now What?

So with all of this in the back of my mind, last week I stumbled onto Mustafa's post about remembering the scroll position of an element that is contained within an UpdatePanel across partial page loads.  I wanted to use Mustafa's script, but I didn't like that I would have to copy/paste this logic around from page to page.  So I looked into the ASP.NET client side documentation and wrote a Component that handles this task for me.  And I was happy with how this works, but it was still a bit clunky.  So this weekend I took some time and revised things a bit.  In the process I also tried out addressing some of the items mentioned on my wish list above 

  • I added a new shortcut function called $select that finds DOM elements by ID or by CSS class.  Below is a quick example.  The $select function returns all elements with the ID of #someElement or that have a CSS class of dragpanel.  I know this is very simple compared with what jQuery supports, but it is also a bit better than what the $get ASP.NET AJAX function provides.
   1: var elements = $select('#someElement, dragPanel');
  • Next I added an each function to the Array prototype.  Maybe it is just me, but I find this incredibly readable.
   1: $select('#someElement, dragPanel').each(function(e){
   2:     //  do something interesting with the elements that
   3:     //  matched this selector ...
   4: });
  • I updated the getElementsByClassName function (the new $select calls this) and I wrap the resulting DOM elements in the Sys.UI.Control instance.  This is useful for the next two items ...
  • I created a very simple programming model for JavaScript only plug-ins.  To create a ASP.NET AJAX JavaScript plug-in, I create a Sys.Component class that does the following
    • Defines a function that specifies the plug-ins API.  Below is the sample for the RoundedCorners plug-in and the _round function is the plug-ins interface.  It accepts a single parameter - the radius in pixels for the rounded corners.
    • In the plug-in's initialize function I am adding a round function to the Sys.UI.Control's prototype that will allow this plug-in to be called from any instance of the Control class. 
   1: Type.registerNamespace("majax");
   2:  
   3: majax.RoundedCorners = function() {
   4:     majax.RoundedCorners.initializeBase(this);
   5: }
   6:  
   7: majax.RoundedCorners.prototype = {
   8:  
   9:     initialize : function() {
  10:         majax.RoundedCorners.callBaseMethod(this, 'initialize');
  11:  
  12:         //  attach the plugin
  13:         Sys.UI.Control.prototype.round = this._round;
  14:     },
  15:  
  16:  
  17:     dispose : function() {
  18:  
  19:         majax.RoundedCorners.callBaseMethod(this, 'dispose');
  20:     },
  21:  
  22:     _round : function(radius) {
  23:         $create(AjaxControlToolkit.RoundedCornersBehavior, {"Radius":radius}, null, null, this.get_element());        
  24:     }
  25: }
  26:  
  27: //  register the class
  28: majax.RoundedCorners.registerClass('majax.RoundedCorners', Sys.Component);
  29:  
  30: //  create the singleton
  31: $create(majax.RoundedCorners, null, null, null);
  32:  
  33: // Since this script is not loaded by System.Web.Handlers.ScriptResourceHandler
  34: // invoke Sys.Application.notifyScriptLoaded to notify ScriptManager 
  35: // that this is the end of the script.
  36: if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

 

And because I am wrapping the DOM elements returned from $select in instances of this class it allows us to do cool things like this ...

   1: $select('#roundedPanel1, #roundedPanel2').each(function(e){
   2:     //  add the rounded corners (6 pixel radius) to the panels
   3:     e.round(6);
   4: });

 

  • And finally, I plan on adding extending the Sys.UI.Control prototype to include functions for adding event handlers like click, mouseover, mouseout, focus, etc ... that would make stuff like this possible.
   1: //  Find me the rows of the #orders table
   2: $select('#orders TR').each(function(e) {
   3:     //  add the over class while the mouse is hovering
   4:     e.mouseover(function() { this.addCssClass('over'); });
   5:     //  remove the class when the mouse leaves
   6:     e.mouseout(function() { this.removeCssClass('over'); });
   7: }

 

Here Come the Plug-ins

And to test this stuff out, I created a 6 ASP.NET AJAX plug-ins.  Five of these are just like the RoundedCorners one I showed previously - they just take the parameters and pass them through to the corresponding Toolkit extender control's JavaScript Behavior.  But I did also create a brand new plug-in that isn't based on the Toolkit.  Here is the rundown of these plug-ins ...

DropShadow

Snippet below shows how to add a drop shadow to all elements on the page that have the dropShadow CSS class applied to them.

   1: //  add the drop shadow plugin to all elements with the dropShadow class
   2: $select('dropShadow').each(function(e){
   3:     //  opacity of 0.75 and a width of 4px
   4:     e.shadow(0.75, 4);
   5: });  

Here is the markup for some elements that will match this selector

   1: <div class="dropShadow">
   2:     <div style="padding:10px">  
   3:         <table>
   4:             <tr><th>First Name </th><td><asp:TextBox ID="TextBox1" runat="server"></asp:TextBox></td></tr>
   5:             <tr><th>Last Name </th><td><asp:TextBox ID="TextBox2" runat="server"></asp:TextBox></td></tr>
   6:         </table>
   7:     </div>
   8: </div>
   9: <br />
  10: <div class="dropShadow">
  11:     <div style="padding:10px">  
  12:         <table>
  13:             <tr><th>First Name </th><td><asp:TextBox ID="TextBox3" runat="server"></asp:TextBox></td></tr>
  14:             <tr><th>Last Name </th><td><asp:TextBox ID="TextBox4" runat="server"></asp:TextBox></td></tr>
  15:         </table>
  16:     </div>
  17: </div> 

And here is a screen shot of what is displayed

image

MutexCheckBox

Snippet below shows how to create checkboxes whose values are mutually exclusive.

   1: //  hook up the mutually exclusive checkboxes
   2: $select('#haveGuestBedroom, #notHaveGuestBedroom').each(function(e){
   3:     e.mutexCheckBox('guestBedroom');
   4: });
   5: $select('#haveSplitPlan, #notHaveSplitPlan').each(function(e){
   6:     e.mutexCheckBox('splitPlan');
   7: });
   8: $select('#haveCoveredPatio, #notHaveCoveredPatio').each(function(e){
   9:     e.mutexCheckBox('coveredPatio');
  10: });
  11: $select('#haveGatedCommunity, #notHaveGatedCommunity').each(function(e){
  12:     e.mutexCheckBox('gatedCommunity');
  13: });

Here is the markup for some elements that will match this selector

   1: <table>
   2:     <tr>
   3:         <td>
   4:             <b>Must Have</b><br />
   5:             <asp:Checkbox runat="server" ID="haveGuestBedroom" Text="Guest Bed Downstairs" /><br />
   6:             <asp:Checkbox runat="server" ID="haveSplitPlan" Text="Split Plan" /><br />
   7:             <asp:Checkbox runat="server" ID="haveCoveredPatio" Text="Covered Patio" /><br />
   8:             <asp:Checkbox runat="server" ID="haveGatedCommunity" Text="Gated Community" /><br />
   9:         </td>
  10:         <td>
  11:             <b>Must Not Have</b><br />
  12:             <asp:Checkbox runat="server" id="notHaveGuestBedroom" Text="Guest Bed Downstairs" /><br />
  13:             <asp:Checkbox runat="server" id="notHaveSplitPlan" Text="Split Plan" /><br />
  14:             <asp:Checkbox runat="server" id="notHaveCoveredPatio" Text="Covered Patio" /><br />
  15:             <asp:Checkbox runat="server" id="notHaveGatedCommunity" Text="Gated Community" /><br />
  16:         </td>            
  17:     </tr>
  18: </table>

And here is a screen shot of what is displayed

image

RoundedCorners

Snippet below shows how to create elements with rounded corners.

   1: $select('#roundedPanel1, #roundedPanel2').each(function(e){
   2:     //  add the rounded corners (6 pixel radius) to the panels
   3:     e.round(6);
   4: });

Here is the markup for some elements that will match this selector

   1: <div id="roundedPanel1" class="rounded" style="height:200px">
   2:     <div style="padding: 10px; text-align: center">
   3:         Rounded Panel #1
   4:     </div>
   5: </div>
   6: <br />
   7: <div id="roundedPanel2" class="rounded" style="height:200px">
   8:     <div style="padding: 10px; text-align: center">
   9:         Rounded Panel #2
  10:     </div>
  11: </div>  

And here is a screen shot of what is displayed

image

ToggleButton

Snippet below shows how to turn a checkbox into a toggle button

   1: $select('toggleButton').each(function(e){
   2:     //  it would have been cool if the
   3:     //  toggle button extender applied the images
   4:     //  with CSS classes instead ;)
   5:     e.toggleButton(
   6:     {
   7:         "CheckedImageAlternateText":"Check",
   8:         "CheckedImageUrl":"_assets/img/ToggleButton_Checked.gif",
   9:         "ImageHeight":19,
  10:         "ImageWidth":19,
  11:         "UncheckedImageAlternateText":"UnCheck",
  12:         "UncheckedImageUrl":"_assets/img/ToggleButton_Unchecked.gif"
  13:     });
  14: });

Here is the markup for some elements that will match this selector

   1: <asp:CheckBox ID="CheckBox1" CssClass="toggleButton" Checked="true" Text="I like ASP.NET" runat="server"/><br />
   2: <asp:CheckBox ID="CheckBox2" CssClass="toggleButton" Checked="true" Text='I like ASP.NET AJAX' runat="server"/><br /><br />
   3: <asp:Button ID="Button1" runat="server" Text="Submit" OnClick="Button1_Click" />
   4: <br /><br />
   5: <asp:Label ID="Label1" runat="server" Text="[No response provided yet]" />

And here is a screen shot of what is displayed

image

CheckGroup

Snippet below shows how to create a check all checkbox that is automatically kept in sync with a group of checkboxes.  This would useful in TABLE where the header row has a check box that checks or unchecks all items in row

   1: $select('checkall').each(function(e){
   2:     //  add checkgroup plugin to the groupclass elements
   3:     e.checkGroup($select('groupclass'));
   4: }); 

Here is the markup for some elements that will match this selector

   1: <table style="border:solid 1px #444;">
   2:     <tr><th><input type="checkbox" class="checkall" /></th><th>Description</th><th>Comments</th><th>More Comments</th></tr>
   3:     <tr><td><input class="groupclass" name="group" type="checkbox"/></td><td>Some Text</td><td>Some Text</td><td>Some Text</td></tr>
   4:     <tr><td><input class="groupclass" name="group" type="checkbox"/></td><td>Some Text</td><td>Some Text</td><td>Some Text</td></tr>
   5:     <tr><td><input class="groupclass" name="group" type="checkbox"/></td><td>Some Text</td><td>Some Text</td><td>Some Text</td></tr>
   6:     <tr><td><input class="groupclass" name="group" type="checkbox"/></td><td>Some Text</td><td>Some Text</td><td>Some Text</td></tr>
   7:     <tr><td><input class="groupclass" name="group" type="checkbox"/></td><td>Some Text</td><td>Some Text</td><td>Some Text</td></tr>
   8:     <tr><td><input class="groupclass" name="group" type="checkbox"/></td><td>Some Text</td><td>Some Text</td><td>Some Text</td></tr>
   9: </table>

And here is a screen shot of what is displayed.  Make sure you play around with the demo for this sample to see how the master check-all box stays in sync with the other checkboxes.

image

 

What Next?

  1. Port over some of the cool jQuery, DoJo, Prototype and MooTools AJAX widgets to see how they work with this model.
  2. Beef up the Sys.UI.Control prototype to include to include other helpful methods for adding event handlers, accessing parent elements as well as for a few of the handy jQuery DOM helper functions.
  3. Put this stuff to use in my current project.

 

That's it.  Enjoy!


TrackBack

TrackBack URL for this entry:
http://mattberseth.com/blog-mt/mt-tb.fcgi/130

Comments


Amusing! and I suggest to build an Open Source Project to build jQuery like functionality into ASP.NET AJAX Library.

You make client scripting so fun.
Great post with great idea Matt

Hi Muhammad -

I was thinking this might go in that general direction. I figured I would spend a few weeks doing the {add features -> blog about it -> get feedback} loop. Then once the code gets past the prototype/poc phase throwing it up on codeplex and see if it is something the community will latch onto.

Matt.

Posted by: Steve on June 16, 2008 12:00 AM

As a jQuery user, you are better off to do what all the other jQuery users do: create plugins.

ie. There is already a rounded plugin.

hundreds/thousands of plugins and widgets available.

I would not goto Codeplex to find your plugins, Id goto to jQuerys website plugin to find it. ie. Search for asp.net plugins

MS would do good to learn from jQuery as far as javascript goes :)

I definitely would love to see this out on codeplex. Ive already tried my hand at some extenders and script controls. Id love to play around with this plug-in model because it seems so much less top-heavy. I also love not having to have a compiled DLL to get it working.

@Steve -

Arent we speaking the same language?
- The ASP.NET AJAX programming model could be improved by incorporating some of what jQuerys is already doing
- There are hundreds of jQuery plug-ins available, but ~30 AJCT controls - i.e. ASP.NET AJAX would benefit from a JavaScript based plug-in model

Please followup and set me straight if I have misunderstood you ...

Matt.

@Josh -

Cool. We will see what other kind of feedback I get, but I have a feeling there is a good chance this will make it out there.

Matt.

One thing Im seeing as I look at your mutex checkbox method is a way to clean it up to a single each() function. Since ASP.Net will pass anything it doesnt recognize down to the browser, you could add an element to the checkbox tag for something like "mutexGroup" and then do this:

$select(mutexCheckBox).each(function(e){
e.mutexCheckBox(e.mutexGroup);
});

Posted by: Jonx on June 17, 2008 12:00 AM

Matt, what I really love with all your stuff is that you take the "best" from other technologies and show how to do them with "pure" ASP.Net technologies. Not mentionning the fact that you are filling some big blanks in the incomplete microsoft documentation... Its not that Im a Microsoft fanatic. Its just that the projects Im working on just cannot afford having yet another new dependance toward other stuff like jquery, etxjs, YUI and the like... Sticking to ASP.NET/AJAX makes all your samples directly usable in any of my project. they integrate very well. Otherwise I wont be able to use any of them and I would be very frustrated as you seem to solve all the problems I have just a couple of days before I encounter them ;)

Keep up the good work and spirit and thanks for sharing all this with us.

@Josh -
I like the syntax, but will that fail xhtml validation?

@Jonx -
Thanks Jonx - I am glad to hear you find this useful. I am not exactly sure where the stuff I mention in this specific post is going - but it sure has been fun playing around with some of this stuff.

Matt.

Matt,
Ive found that most javascript methods that can be used on page elements fail ASP.Net validation as invalid attributes (onfocus and onkeypress are a few common ones that I extend, though they only come up as Warnings for me), so I just dont worry about it. Design view still works.

Posted by: Steve on June 18, 2008 12:00 AM

In my experience, the MS developer crowd is unable to comprehend or use much outside of what MS produces. Which I think is rather sad.

Personally, I think the idea is to use the best tool for the job. "MS only uses a hammer so although its hard to screw a nail with it, I dont want to buy a screwdriver from so-so and so open source project"

So Jonx, I would venture to say that jQuery is one of the most popular and widely used javascript libraries available. If you learn it, use it, you will see that for yourself and get off the MS koolaid :)

Yes, Matt - it could be improved. On the other hand, why does MS insist on building version 1 of everything, even when the development community is already invested in version of these libraries that are far superior than what MS has created?

Sorry, its just a part of MS that really is disapointing. You mention MVC in your post, Webforms are a great example: rather than use a proven architecture, they created a proprietary system that makes it difficult to use outside of demos - and arcane knowledge (you seem to have it - lol) of bending their controls to do what you need them to do! For example, rather than build a new javascript library, how about creating better VS tooling with more javascript support - refactoring, etc...

Ah well, sorry to rant - I really like and appreciate what you post on - great stuff, keep it up. Maybe you can help me see the value in bending Webforms to make them work better :)

For the record, I have written applications with both frameworks (jQuery, MS Ajax, MVC and Webforms) - Im currently using jQuery with MVC preview 3.

Posted by: Deepak Chawla on June 19, 2008 12:00 AM

Matt,
I dont what you cant do with existing Microsoft Ajax?
From what you have suggested above.

You know when you have meat a proper ASP.NET developer.
When he knows about the DropDownList box bug where GroupName does not work.

Ive incorporated the AjaxToolkit code in my project(So that I can fix the bugs when I come across and there have been a few).

Plus I have my own Toolkit. So far I have three extenders
1) ThreeStateCheckbox: In some search scenarios you yes I want it. No I dont. and sometimes you say I really dont care. I have used the Up and down thumb and one going sideways to represent Dont Care.

2) Unique Radio button: Extender to select unique radio button. Which could also be apart of different radio button list.

3) Type ahead text box: Like write up names, Title country name. etc. Learns info via web services

Let me know if some way I can send the code to you to post.

Posted by: MSD on June 20, 2008 12:00 AM

Hi there.
Interesting post, actually I am interested in knowing (since you mention MVC), what is or would be the framework of your choice. Have you used MVC at all in a real project. I ask because I have been doing some research and came across MonoRail from Castle Project (www.castleproject.com), also I have seen something called UIPAB from Microsoft.
Any help and/or advice is appreciated.

Posted by: Ying Jin on July 20, 2008 12:00 AM

Matt, great post.
WRT Steves point of view, one point I am not sure about integrating non-MS js library with MS server technology is application services. For example, how good is jQuery integrate with ASP.NET authentication and profile services? Some of the LOB web apps are heavily dependent on server-side services, and even the most stunning UI cannot make up for a security-broken website.
I am still new to this Ajax stuff, and I confess that I am completely ignorant of jQuery library, so if my concerns are totally out of line, sorry.
Ying

Posted by: scott on July 20, 2008 12:00 AM

if anyone is looking for a good way to make the asp.net validators more jQuery like, check out this article:

http://www.delphicsage.com/home/blog.aspx?d=205

Matt, Im a big fan! Great blog!

Matt, Just wanted to say thanks for the excellent work that you do.. and I modified your code a bit to make it a little more ASP.Net friendly (ie, it works with ASP:CheckBoxes and ASP:RadioButtons).

http://www.clanmonroe.com/Blog/archive/2008/07/31/getting-more-jquery-goodness-into-asp.net-ajax.aspx

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Consulting Services

Yep - I also offer consulting services. And heck, I'll do just about anything. If you enjoy my blog just drop me an email describing the work you need done.

Recent Comments

  • Jason Monroe wrote: Matt, Just wanted to say thanks for the excellent work that you do.. and I modified your code a bit ...
  • Ying Jin wrote: Matt, great post. WRT Steves point of view, one point I am not sure about integrating non-MS js lib...
  • scott wrote: if anyone is looking for a good way to make the asp.net validators more jQuery like, check out this ...
  • MSD wrote: Hi there. Interesting post, actually I am interested in knowing (since you mention MVC), what is or ...
  • Deepak Chawla wrote: Matt, I dont what you cant do with existing Microsoft Ajax? From what you have suggested above. ...
  • Josh M. wrote: Matt, Ive found that most javascript methods that can be used on page elements fail ASP.Net validati...
  • Steve wrote: In my experience, the MS developer crowd is unable to comprehend or use much outside of what MS prod...
  • Jonx wrote: Matt, what I really love with all your stuff is that you take the "best" from other technologies and...