Saturday, October 1, 2011

JavaScript with ASP .NET server controls

Introduction

Before ASP .NET, there was ASP; before ASP, there was JavaScript. While ASP has been gradually marginalized by the rapid succession of ASP .NET 1.x, 2.x and now 3.x, JavaScript remains the sole source and foundation of snappy client-side user experience.
If the time of existence is of any indication, the already gigantic and still rapidly growing repertoire of JavaScript is a force we cannot afford to ignore. The successes of various Google applications offer the best testimony (a testimony for Ajax too).
On the other hand, ASP .NET, especially the vastly improved ASP .NET 2.0 is undeniably a powerful and flexible development environment. The suite of systems (such as membership, role and profile, health monitoring), server controls (GridView, DataList, etc.) and master page, skin and theme relieve programmers much of the burden of doing the same plumbing over and over again, and allow them to concentrate on individual site-based business logic instead.
However the flip side of the coin is the server-centric-ness of ASP .NET. It relies almost exclusively on the server to deal with user interactions. With a slow network connection, ASP .NET applications would seem to be slow responding or not responding at all; if you have a deep-colored background, you get very unpleasant fits of white-flashes. Yes, ASP .NET has taken great effort to address the issues, introduced methods and properties to handle common tasks such as popping up alert or confirmation boxes, setting focus on server controls; created ClientScript class to allow JavaScript to be included with a page or server controls. You may refer to JavaScript with ASP .NET Pages for more details. However, its highly encapsulated declarative-based syntax still poses great challenges for programmers to go beyond the pre-cut patterns and practices and inject into server controls individualized client side responses.
So how do we leverage the power of ASP .NET and the nimbleness of JavaScript to create a powerful website with rich UI experience?
You may say: ASP .NET AJAX.
Yes. And No.



ASP .NET AJAX, or AJAX, is the hotly pursued technology. I, like everyone else, has jumped on this train of Mr. popular. However, like any other technology, ASP .NET technology can be misused and abused.
In a blog article entitled "Past the AJAX Hype - Some things to think about" in 2005 (while outdated it still has a grain of truth), Rick Stahl writes about the reasons why AJAX should not be the answer to every prayer.
  1. "Grafting AJAX onto existing applications adds another layer of complexity to your application”
  2. "With AJAX, the client code gets large very quickly".
  3. "You should also consider what impact AJAX has on your application’s scalability. AJAX tends to increase the number of requests on a back end application considerably."
ASP .NET AJAX does not offer a fix in every situation for everyone either. ASP.NET AJAX, formerly code-named Atlas, is a set of extensions to ASP.NET developed by Microsoft for implementing Ajax functionality, including both client-side and server-side components.
There are scenarios where AJAX offers the only acceptable solution. That is when server data retrieval is needed and a whole-page refreshing to be avoided. However in other common-place scenarios, such as show and hide a page section based on user input, zoom in and out of an image in responding to a user's mouse movement, AJAX should not be rushed in an ASP .NET page as the rescue. Client-side only reactions should be left to client-side only scripts.
And there is a whole world of JavaScript for us to take advantage of.
So let's jam with ASP .NET and JavaScript code with examples. To demonstrate how we can take advantage of the tons of snappy JavaScript that is out there, I downloaded most of the script for my examples (JavaScript source for this article is mostly from Dynamic Drive DHTML).
Check out the live demo for this article here.

Greybox with ASP .NET server controls

One rule of thumbs of website design is to try to keep the number of pop up windows to a minimum (normal pop-up windows using window.open() call may not work anyway because of the prevalence of popup blockers), another one is try not to have (too many) links away, i.e., links to external websites channeling away traffic.
There have been some work-around scripts for creating non-intrusive and within-site navigation popup. ASP .NET AJAX ModalPopup is one way to do it in ASP .NET Ajax. However, I much prefer the Greybox created by Orangoo Labs. Greybox can have wide use ranging from login, small gallery and external-web page links, etc.
The installation is very easy and business-as-usual; you can simply download and stick the JavaScript references in the head section of a page.
Turns out that installing a Greybox for use with an ASP .NET page is very simple.
The following example, slightly modified from the original example, attaches to GreyBox to a server-side button and a hyperlink.
Step 1: Reference the Greybox JavaScript source in your header section, as instructed in the original JavaScript.

Listing 1: Step 1: Reference the Greybox JavaScript

01.<head id="Head2" runat="server">
02.<title>GreyBox with ASP .NET Examples</title>
03.<script type="text/javascript">
04.var GB_ROOT_DIR = "./greybox/";
05.</script>
06.<script type="text/javascript" src="greybox/AJS.js"></script>
07.<script type="text/javascript" src="greybox/AJS_fx.js"></script>
08.<script type="text/javascript" src="greybox/gb_scripts.js"></script>
09.<link href="greybox/gb_styles.css" rel="stylesheet" type="text/css"
10.media="all" />
11.<script type="text/javascript" src="static_files/help.js"></script>
12.<link href="static_files/help.css" rel="stylesheet" type="text/css"
13.media="all" />
14.</head>
Step 2: Invoke the GreyBox with a server control, such as a button, a hyperlink, an image, etc. The slight difference is that instead of directly calling the JavaScript function as we would with an HTML input control; we attach it to a server control in the Page_load event by using attributes.add in the code behind page. Please note that GreyBox heavily utilizes the rel attribute of a link tag.
The following example launches a small image gallery on button click and then launches the DotNetSlackers website upon a click of the hyperlink.

Listing 2: The Web Form with a button and a hyperlink

01.<form id="form2" runat="server">
02.<script type="text/javascript">
03.var image_set = [{'caption': 'Flower', 'url':
05.{'caption': 'Nice waterfall', 'url':
07.</script>
08.<asp:Button ID="button1" runat="server" text="Click me!" /><br /><br />
09.<asp:HyperLink runat=server ID="link1" Text="Click me"
10.NavigateUrl="http://www.dotnetslackers.com"></asp:HyperLink>
11.</form>

Listing 3: Code Behind

1.protected void Page_Load(object sender, EventArgs e)
2.{
3.button1.Attributes.Add("onClick", "return GB_showImageSet(image_set, 1)");
4.link1.Attributes.Add("rel", "gb_page_center[640, 480]");
5.}

Figure 1: Screenshot of Greybox with server controls

Screenshot of Greybox with server controls
Check out the demo for the code in action.

JavaScript with List controls and how to show/hide a layer

ASP .NET list controls include RadioButtonList, CheckBoxList, DropDownList and ListBox. In the ASP .NET 1.x times, it was a known bug that the regular attributes.add (key, function) didn’t work for list controls. For example, you cannot apply special CSS styles to a particular checkbox if it is checked; and you cannot attach a JavaScript function to a RadioButtoList’s OnClick event. To make your list controls have individual ListItem specific reactions or behaviors, you have to write custom Web Controls inheriting from ListControl. It is not exactly a trivial task (To read more, please see List Control Items and Attributes).
Luckily, we have ASP .NET 2.0 which has swept away bugs like this. However, how do we style different list items? How do we set up JavaScript functions in response to client-side actions, for example, in a common scenario where you want to display a set of questions only if a user has answered "Yes" to a screening question?
We can dress individual List Items in the code-behind page, since it is not possible to set the style declaratively. For example, the following code sets the background color of each list item of a DropdownList of colors.
1.foreach (ListItem li in
2. 
3.dropdownlist1.Items)
4.li.Attributes.Add("style", "background-color:" + li.Text);
To toggle the visibility of a specific set, we can do it server-centric way or client-centric way. For the former, you can put the set in a server-side panel, set the list control's autopostback to True, and set the property visible to either False or True. To avoid the annoying whole-page refreshing and unpleasant post back flash, you can also use an UpdatePanel.
The following example is directly plucked out of a survey. It displays the Government-related question if a user indicates that she/he works for Government or Civic agency, or the "other" textbox to allow user to specify the type of work he/she works on.

Listing 4: Using UpdatePanel to show/hide a specific set of questions based on users’ response

01.<asp:UpdatePanel ID="UpdatePanel1" runat="server"><ContentTemplate>
02.<p>Which best describes your work? 
03.<asp:RadioButtonList ID="q1" runat="server" AutoPostBack="true"
04.repeatDirection="vertical" OnSelectedIndexChanged="q1_SelectedIndexChanged">
05.<asp:ListItem value="1">Not-for-profit</asp:ListItem>
06.<asp:ListItem value="2">Foundation/corporate philanthropy</asp:ListItem>
07.<asp:ListItem value="3">Private sector</asp:ListItem>
08.<asp:ListItem value="4">Government/civic agency</asp:ListItem>
09.<asp:ListItem value="5">Educational institution/agency</asp:ListItem>
10.<asp:ListItem value="6">Faith-based organization</asp:ListItem>
11.<asp:ListItem value="7">Independent consultant</asp:ListItem>
12.<asp:ListItem value="8">Student</asp:ListItem>
13.<asp:ListItem value="9">Other
14.</asp:ListItem>
15.</asp:RadioButtonList>
16.</p>
17.<div id="divQlgov" runat="server" visible="false">
18.<b>Government/civic agency?
19.</b>
20.<asp:RadioButtonList ID="q1gov" runat="server" RepeatDirection="Horizontal">
21.<asp:ListItem Value="1">Local</asp:ListItem>
22.<asp:ListItem Value="2">State</asp:ListItem>
23.<asp:ListItem Value="3">Federal</asp:ListItem>
24.</asp:RadioButtonList>
25.</div>
26.<div id="divQ1other" visible="false" runat="server">
27.<b> Other (Specify)</b>
28.<asp:TextBox id="q1oth" runat="server" />  
29.</div>
30.</ContentTemplate>
31.</asp:UpdatePanel>
In the code behind page, you can define your q1_SelectedIndexChanged function as such:
1.protected void q1_SelectedIndexChanged(object sender, EventArgs e)
2.{
3.divQ1other.Visible = (q1.SelectedIndex == q1.Items.Count - 1);
4.divQlgov.Visible = (q1.SelectedIndex == 3);
5.}
The above would work.
However, we can do it in a simpler, client-centric way, instead of posting back every user mouse movement to server. First, to each ListItem we add an OnClick attribute; then, rather than wrap the selective set of questions in a server-side control and further wrap it up in UpdatePanel/ContentTemplate tags, we use a client-side layer and adjust its style.display property to either block or none.
For example, we can always rewrite the above server code as follows:

Listing 5: Add JavaScript to server list controls to turn off/on a set of specific questions based on user’s response

01.<p>Which best describes your work? 
02.<asp:RadioButtonList ID="q1" runat="server" repeatDirection="vertical">
03.<asp:ListItem value="1">Not-for-profit</asp:ListItem>
04.<asp:ListItem value="2">Foundation/corporate philanthropy</asp:ListItem>
05.<asp:ListItem value="3">Private sector</asp:ListItem>
06.<asp:ListItem value="4">Government/civic agency</asp:ListItem>
07.<asp:ListItem value="5">Educational institution/agency</asp:ListItem>
08.<asp:ListItem value="6">Faith-based organization</asp:ListItem>
09.<asp:ListItem value="7">Independent consultant</asp:ListItem>
10.<asp:ListItem value="8">Student</asp:ListItem>
11.<asp:ListItem value="9">Other
12.</asp:ListItem>
13.</asp:RadioButtonList>
14.</p>
15.<div id="divGov" style="display:none">
16.<b>Government/civic agency?
17.</b>
18.<asp:RadioButtonList ID="q1gov" runat="server" RepeatDirection="Horizontal">
19.<asp:ListItem Value="1">Local</asp:ListItem>
20.<asp:ListItem Value="2">State</asp:ListItem>
21.<asp:ListItem Value="3">Federal</asp:ListItem>
22.</asp:RadioButtonList>
23.</div>
24.<div id="divOther" style="display:none">
25.<b> Other (Specify)</b>
26.<asp:TextBox id="q1oth" runat="server" />  
27.</div>

Listing 6: JavaScript function to show/hide a layer

01.function ShowHide(divName, OnOff){
02.var ele = document.getElementById(divName);
03.if(ele != null){
04.if(OnOff == "on")
05.ele.style.display = "block";
06.else
07.ele.style.display = "none";
08.}
09.}
In the code behind page, we can do the attributes.add(). Since there are two layers involved in the example, we call the same JavaScript function twice with different parameters.

Listing 7: Add JavaScript function calls to individual ListItem

01.foreach (ListItem li in q1.Items) {
02.//if government selected, show government questions
03.if (li.Value == "4")
04.li.Attributes.Add("Onclick",
05."ShowHide('divGov','on');ShowHide('divOther','off')");
06.//if other selected, show the other textbox
07.else if(li.Value == "9")
08.li.Attributes.Add("Onclick",
09."ShowHide('divGov','off');ShowHide('divOther','on')");
10.else
11.li.Attributes.Add("Onclick",
12."ShowHide('divGov','off');ShowHide('divOther','off')");
13.}
In the before-mentioned article, Scott Mitchell presents an example of a custom CheckboxList control which contains two sets of exclusive checkboxes. For example, a checkbox that says "None of the above" would cause all of the others be unchecked. That was in the pre-2.0 days.
Now let's try to change the logic a little bit. Let’s use a RadioButtonList to check or uncheck all of the checkbox items in a CheckBoxList, which is also a very common scenario.
Insert the following JavaScript function in the web form.

Listing 8: JavaScript: Check or uncheck all of the items in the checkboxList

01.function Check(sender, checklistName, total)
02.{
03.if(sender.value == "1")
04.for (var i = 0; i < total;i++)
05.document.getElementById(checklistName + "_" + i).checked = true;
06.else
07.for (var i = 0; i < total;i++)
08.document.getElementById(checklistName + "_" + i).checked = false;
09.}

Listing 9: The web form with a RadioButtonList and a CheckBoxList

01.From where did you hear about us?
02.<asp:RadioButtonList ID="raioList1" runat="server">
03.<asp:ListItem Value="1">All the Following</asp:ListItem>
04.<asp:ListItem Value="2">None of the Following</asp:ListItem>
05.</asp:RadioButtonList>
06.<asp:CheckBoxList ID="checkList1" runat="server">
07.<asp:ListItem Value="1">TV/Radio</asp:ListItem>
08.<asp:ListItem Value="2">Newspaper</asp:ListItem>
09.<asp:ListItem Value="3">Friends</asp:ListItem>
10.<asp:ListItem Value="4">Internet</asp:ListItem>
11.</asp:CheckBoxList>

Listing 10: Call the JavaScript from code-behind

1.foreach (ListItem li in raioList1.Items)       
2.{
3.li.Attributes.Add("Onclick", "Check(this,'" + checkList1.ClientID + "'," +
4.checkList1.Items.Count+ ")");
5.}
Please note that the client function for check/uncheck all the items in a CheckBoxList takes three parameters: sender, the object that evokes the function; checklistName, the collective name or the prefix of the array of checkboxes in concern; total: the total number of checkboxes in the list.
On the server side, we pass the this keyword to refer to the sender itself; the ClientID of the CheckboxList as ChecklistName; and items.count for the total number of checkboxes.

Popup in a GridView

Popup is the darling of web programmers; we always try to outsmart popup-blockers. Good popup are non-intrusive, informative, nimble and visually appealing. Popup are as popular as GridView is powerful. How then to allow a little popup bubbling up while you mouse over each data item of a GridView? I have seen examples using AJAX for GridView popup. However, I doubt it is absolutely necessary.
How about using one of the pop up JavaScript out there and combine it with our powerful and data-rich GridView?
The result is the following:

Figure 2: Screenshot of information popup with a GridView

Screenshot of information popup with a GridView
Here is how:
First, download the JavaScript file (tooltip.js in the sample code) and reference it in your ASP .NET page;
Second, set up your Gridview. In my example, I created a dummy DataTable as the Gridview's DataSource (I’ve stripped out the database part to make it simpler);
1.<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
2.DataKeyNames="id"
3.OnRowCreated="GridView1_RowCreated" CellPadding="4" ForeColor="#333333" 
4.GridLines="None">
Third, use attribute.add() to add a note to OnRowCreated event the of Gridview to call the JavaScript function whenever a mouse is detected hovering over the magnifier image, to pop up a small information box.

Listing 11: Code for the OnRowCreated event

01.protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
02.{
03.if (e.Row.RowType == DataControlRowType.DataRow)
04.{
05.// Programmatically reference the Image control
06.Image image1 = (Image)e.Row.Cells[1].FindControl("Image1");
07.HyperLink link1 = (HyperLink)e.Row.Cells[1].FindControl("link1");
08.link1.Attributes.Add("onmouseover", "doTooltip(event," +
09.e.Row.RowIndex.ToString() + ")");
10.link1.Attributes.Add("onmouseout", "hideTip()");
11.}
12.}
To the GridView pop up in action, check out the demo here.

Custom Validation with JavaScript

All the ASP .NET built-in validators - RequiredFieldValidator, CompareValidator, RangeValidator and RegularExpressionValidator are provided with JavaScript as default. You have to set the EnableClientScript property of the validator or ValidationSummary control to False to disable client script validation.
For validations that require special logic - for example, a control's input that needs to check against the input of multiple other controls - the CustomValidator class comes into play. CustomValidator allows us to do validation both on the server and client side, and it is a common practice to place identical validation functions on both sides (I do not know why). Here, we will only focus on the side of client script, since it involves some basics of using the mini-client-side validation API and accessing server controls locally.

Listing 12: A custom validator to validate a group of textboxes to ensure their percentage values add up to 100

01.<asp:CustomValidator id="CustomValidator1" runat="server"
02.ErrorMessage="Total Percentages must add up to 100!"
03.ClientValidationFunction="CheckTotal" Display="Dynamic" /><br>
04.Please indicate the percentage of your daily expenditure:<br />
05.Grocery: <asp:TextBox id="txtGrocery" runat="server" Text="0" /><br />
06.Entertainment: <asp:TextBox id="txtEntertainment" runat="server"  Text="0"/><br />
07.Other:<asp:TextBox id="txtOther" runat="server"  Text="0" /><br />
08.<script language="javascript">
09.<!--
10.function CheckTotal(source, args) {
11.var ele1 =document.getElementById("<%=txtGrocery.ClientID%>");
12.var ele2 =document.getElementById("<%=txtEntertainment.ClientID%>");
13.var ele3 =document.getElementById("<%=txtOther.ClientID%>");
14.var val1 = ele1.value;
15.var val2 = ele2.value;
16.var val3 = ele3.value;
17.if (isNaN(val1) ||isNaN(val2) || isNaN(val3) ) {
18.args.IsValid = false;
19.}
20.else {
21.args.IsValid = ((val1/1 + val2/1 + val3/1) == 100);
22.}
23.}
24.// -->
25.</script>
Here are some points to note about using CustomValidator:
  • To perform client-side custom validation, we must indicate the name of function in the ClientValidationFunction property. As in the above: ClientValidationFunction="CheckTotal".
  • The custom function must always have the same two parameters: source and arguments (translated on the server-side: customValidate (object sender, EventArgs e)). Source is the client-side CustomValidator object, and arguments is an object with two properties, Value and IsValid. The Value property is the value to be validated and the IsValid property is a Boolean used to set the return result of the validation.
  • In the case where we need to access the value of other server controls, we use document.getElementById ("<%=server control ID.ClientID% >") to access the server control using its ClientID.
  • The IsValid property is false by default. Always explicitly set IsValid property to true if the values are validated.
  • You can leave out the ControlToValidate property. One CustomValidator can be used for a whole web form, in which case, the custom validation function will be fired only once.

Take a look at the Ajax Control Toolkit

"The ASP.NET AJAX Control Toolkit is a joint project between the community and Microsoft that provides a rich array of controls for building interactive Web experiences easily." As of now, the sample website showcases about thirty-four samples of web control extenders, such as Accordion, Animation, AutoComplete.
Extenders are server controls that allow a server control to imitate client-side behaviors. For example, the Accordion allows you to provide multiple panes that can expand or collapse depending on user's mouse click; only one pane will be visible at one time.
If you are not using Visual Studio 2008, or ASP .NET 3.5; to enable extenders to work with web controls, you must first configure your web site to be AJAX enabled, and then you must install the AjaxControlToolkit assembly. Finally, you add the controls to your Toolbox. Then you can drag and drop Toolkit's extenders in your page. It is not very difficult to do, and you do not need to write any JavaScript code to have the benefit of JavaScript, which could be a plus for programmers that do not have any JavaScript skills.
However, in what planet can you develop a rich and fast- responding website with only spoon-fed canned formulas and no JavaScript at all?
To me, the problem with Ajax extenders is that, it is not at all easy to plow through the obtuse process of creating extenders of your own. Given the meager number of extenders available on the sample website and the vast and varied requirement for rich client capabilities, you will soon be out of luck. And think of the world of JavaScript (text effects, image effects, links and menus, mouse and cursors) that are available to download!
Among the extenders in the Ajax Control Toolkit, the auto-complete extender is a good and necessary one, because to provide a list of suggested words to aid users filling their content box, it involves retrieving data (sometimes a heavy amount of data) from server upon every keystroke, which client script could not do. However, others, in my view, are not quite so necessary. It is easier still to cherry-pick from the available JavaScript and directly apply to a web control.
Take the CollapsiblePanel for example. Collapse and expand a layer has been a long-standing JavaScript function. A search will quickly turn up many JavaScript for this purpose. So I downloaded one and added the reference in the web form head section - and with a few lines, I got a nice working collapsible panel. No Toolkit’s .dll plug in, no AJAX configuration.

Listing 13: The accordion web form

01.<div style="background-color:navy; width:400px">
02.<asp:Label Font-Bold="true" Text="What is ASP .NET AJAX (hide details...)"
03.ForeColor="white" ID="label1" runat="server"></asp:Label>
04.<asp:HyperLink ImageUrl="expand_blue.jpg" runat="server"
05.ID="linkCollapse"></asp:HyperLink><br /><br />
06.</div>
07.<div id="DivContent" style="width: 300px; background-color: #99E0FB;">
08.<!--Your DIV content as follows. Note to add CSS padding or margins, do it inside
09.a DIV within the Collapsible DIV -->
10.<div style="padding: 0 5px">
11.ASP.NET AJAX is a free framework for building a new generation of richer, .....
12.</div>
13.</div>
14.<script type="text/javascript">
15.//Syntax: var uniquevar=new animatedcollapse("DIV_id", animatetime_milisec,
16.enablepersist(true/fase), [initialstate] )
17.var collapse2=new animatedcollapse("DivContent", 800, true)
18.</script>
Code in the code-behind, using attribute.add to call the JavaScript function:
1.linkCollapse.Attributes.Add("Onclick", "javascript:collapse2.slideit()");

Summary

ASP .NET is no doubt a powerful web development tool. However, transitioning from ASP to the ever more sophisticated ASP .NET does not mean we have to throw away everything we learned - especially not JavaScript. The article showed how we can easily harvest both the power of the ASP .NET and the rich and fast capacity of the vast repertoire of existing JavaScript; and how we can do it without necessarily fall back on Ajax.