Saturday, 22 March 2008

Building a Volta Control : A Flickr Widget

By: Tanzim Saqib
Download Sample Code

This article illustrates how to create a Volta control around Flickr, the popular image hosting service.

What is Volta?

Microsoft Live Labs Volta is Microsoft's emerging toolset that enables developers to build multi-tier web applications by applying familiar .NET techniques and patterns. The intended use of this toolset is for Developers to build web application as .NET client application, and then specify the portions of the codes that will run on the client and server side. The compiler then creates cross browser JavaScript, Web Services, communication, serialization, security etc. to tie the tiers together. This article is based on the first CTP of Volta considering its current limitations. We will see how we can create a Volta control that the compiler can convert into an AJAX Widget without requiring us writing a single line of JavaScript code. We will write code in our very familiar C# language. In this article, you will end up creating a Flickr widget like the one shown in figure 1.

Figure 1: The Flickr widget built using Volta

Currently Volta is at its first CTP release, which was made publicly available on December 5 last year. Volta requirements and the installer can be found at http://labs.live.com/volta/download/ Since it's just the first CTP, there are still a lot of limitations. I hope they will get over those in the next CTP or so.

The Specification of the Widget

The widget will have the following features:

  • The photos for a particular Flickr user will be shown in a 3x3 grid.
  • The tooltip of each of the photos will display the title of the image.
  • There will be "prev" and "next" buttons for navigation.
  • On click of each of the photos, a new window will open with the original Flickr source.
  • There will not be a single line of JavaScript code.

The Flickr API

Flickr is a very popular photo hosting and sharing service. It is considered to be one of the leading examples of Web 2.0 applications. Users of Flickr can host hundreds of photos and share them with others. Flickr allows users to tag their photos, which make them easy to find. It has some of the best photo management features, which let users control access to photos pretty efficiently. A few of the interesting features of Flickr are the possibility of marking photos as favourites, grouping photo pool and Flickr Interestingness. Flickr also offers users the ability to release images under certain common usage licenses.

Flickr has an open API besides RSS and Atom feeds. The Flickr API offers numerous methods. It supports different popular request and response formats that should be familiar to the third party developers:

  • REST
  • XML-RPC
  • SOAP

We choose REST because it is the simplest among others, and it is performed by simple HTTP GET or POST actions.

The Flickr Methods

The Flickr API exposes 115 methods so far. The methods are categorized in Authentication, Photos, User Activity, Photo Pool, Photo Upload and many more. Few of them are named as follows:

  • flickr.photos.getInfo
  • flickr.photos.search
  • flickr.interestingness.getList
  • flickr.photos.transform.rotate
  • flickr.test.echo

A full listing can be found at: http://www.flickr.com/services/api/.

The Flickr Request, Response format

The general format of a Flickr request is:

  1. http://api.flickr.com/services/rest/?api_key=__API_KEY__&method=__METHOD_NAME__&param1=value1&param2=value2  

__API_KEY__ is replaced by the API key

  1. http://api.flickr.com/services/rest/?api_key=9163070a740049b1bfe553864a4f1519&method=flickr.interestingness.getList  

The response of a REST query can be rendered by the browser. Simply copy the URL above and paste it into the browser's address bar; then hit enter. You will see a response text somewhat like the following:

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <rsp stat="ok">  
  3. <photos page="1" pages="5" perpage="100" total="500">  
  4.  <photo id="621909774" owner="30959251@N00" secret="70cdeec6f7" server="1411"  
  5.    farm="2" title="Yeah, holidays!" ispublic="1" isfriend="0" isfamily="0" />  
  6.  <photo id="620373432" owner="80176513@N00" secret="7c9d5769ab" server="1151"   
  7.    farm="2" title="In the Mood" ispublic="1" isfriend="0" isfamily="0" />  
  8.  <photo id="619761326" owner="50008412@N00" secret="5aec4bf2ec" server="1037"  
  9.    farm="2" title="Hear me roar!!!!" ispublic="1" isfriend="0" isfamily="0" />  
  10.  <photo id="619727916" owner="46355903@N00" secret="76680dafa6" server="1146" farm="2"   
  11.    title="Rain, rain, go away, I want to do some Sunrise photos" ispublic="1"   
  12.    isfriend="0" isfamily="0" />  
  13. <photo id="620355782" owner="42009080@N00" secret="148cbdb6b0" server="1145" farm="2"   
  14.    title="Beneath A Black Sky" ispublic="1" isfriend="0" isfamily="0" />  
  15. <photo id="621726130" owner="35373726@N00" secret="6b3d8fa0ca" server="1114" farm="2"  
  16.    title="" ispublic="1" isfriend="0" isfamily="0" />  
  17.   
  18. ... ...  
  19.   
  20. </photos>  
  21. </rsp>  

As you see the response is pretty straightforward. It simply has a bunch of photo tags and each of the tags has information about that particular photo. A photo tag has the unique identifier of the photo, owner identifier, server information and also a title. In our widget, we need to parse such XML and display the photos.

Note the rsp tag with a stat attribute that has ok as value. It means the API call was completely errorless and successful. Let us make some mistakes intentionally to see how Flickr reacts. Hit the browser using the previous URL with a wrong API Key. You will receive an XML as response like the following:

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2.   
  3. <rsp stat="fail">  
  4.   <err code="100" msg="Invalid API Key (Key not found)" />  
  5. </rsp>  

Here the stat attribute has fail as value, which means some kind of problem has occurred during the API call and the Flickr API notifies it so that the code can decide whether it should crawl any further or not.

The User NSID

User NSID is an alphanumeric string which uniquely identifies a user. It is often useful, especially in our case where our widget will display the photos of a particular user. In order to retrieve the NSID for a user, Flickr offers a method named flickr.people.findByUsername which takes the Flickr screen name of the user and returns an XML like the following:

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <rsp stat="ok">  
  3.   <user id="9198206@N07" nsid="9198206@N07">  
  4.     <username>your_screen_name</username>  
  5.   </user>  
  6. </rsp>  

The Solution Structure

First of all, create a new Volta Application and name it VoltaFlickrWidgetDemo. Then, add a new Volta Control application named VoltaFlickrWidget to the solution. Add a class to the Volta control project named FlickrHelper.cs. This class contains very useful methods that fetch Flickr photo data and make them easily accessible by the control. Now include the VoltaFlickrWidget project in the References of the VoltaFlickrWidgetDemo project. The resulting solution structure will be like the one shown in the following figure.

Figure 2: Solution Structure

The Flickr Volta Control

The Flickr Volta control we are going to develop should be easy to add to the Volta page considering that there is a div element with an id of divWidgetContainer present in the Volta page:

  1. var divWidgetConatiner = Document.GetById<Div>("divWidgetContainer");  
  2.   
  3. VoltaControl1 flickrWidget = new VoltaControl1("screen_name", "9149b1bfe387273833553864a4f1519", "Flickr Widget", 300, 270);  
  4.   
  5. flickrWidget.AddToDiv(divWidgetConatiner);  
  6. flickrWidget.DownloadPhotos();  

Here VoltaControl1 is the Flickr control we are going to build. Sorry for the naming, but Volta is still immature and can't rename the underlying HTML file of the Volta Control which we will discuss later. Thus, instead of making it confusing, we are keeping it called VoltaControl1. In the above code snippet, VoltaControl1 is initialized with the screen name, API key, control's title, height and width properties. The AddToDiv method of the control adds itself to the div passed as a parameter and then the DownloadPhotos method starts downloading the photos. As soon as AddToDiv is called, the control's HTML file content is injected into the DOM, and HTML elements become accessible like so:

  1. Document.GetById<Div>("divSomeDiv");  

The FlickrHelper

The FlickrHelper class basically performs two operations. One of them is retrieving the NSID of the user against the screen name; and the other is fetching the photo collection of the specified user. The following GetNSID method performs an Asynchronous REST call to the Flickr API and receives the NSID back:

  1. public void GetNSID()  
  2. {  
  3.     IHttpRequest request = HttpRequestFactory.Create();  
  4.     request.AsyncSend("POST", URL_USER_NSID + ScreenName, string.Empty,  
  5.         delegate(string response)  
  6.         {  
  7.             OnNsidLoaded(new NsidLoadedEventArgs(response));  
  8.         });  
  9. }  

Both the IHttpRequest interface and the HttpRequestFactory class can be found in the Microsoft.LiveLabs.Volta.MultiTier namespace. The AsyncSend method performs the asynchronous call and then calls back the delegate, inside of which the OnNsidLoaded event is fired to notify the user of the FlickrHelper class that the NSID data has just arrived. An instance of the NsidLoadedEventArgs is passed to the subscriber of that event to make the NSID available after parsing the necessary XML:

  1. public class NsidLoadedEventArgs : EventArgs  
  2. {  
  3.     public string Nsid { get; set; }  
  4.   
  5.     public NsidLoadedEventArgs(string response)  
  6.     {  
  7.         var xdocument = new XmlDocument();  
  8.         xdocument.LoadXml(response);  
  9.         var xnUser = xdocument.GetElementsByTagName("user")[0];  
  10.   
  11.         if (xnUser == null)  
  12.             throw new Exception("Incorrect screenName");  
  13.         else  
  14.         {  
  15.             var xelUser = xnUser as XmlElement;  
  16.             Nsid = xelUser.GetAttribute("nsid");  
  17.         }  
  18.     }  
  19. }  

Similarly, an asynchronous call is made for fetching photo feed:

  1. public void DownloadPhotos(string nsid)  
  2. {  
  3.     IHttpRequest request = HttpRequestFactory.Create();  
  4.     request.AsyncSend("POST", URL_PHOTOS + nsid, string.Empty,  
  5.         delegate(string response)  
  6.         {  
  7.             OnPhotosLoaded(new PhotosLoadedEventArgs(response, nsid));  
  8.         });  
  9. }  

In the above code snippet, an instance of PhotoLoadedEventArgs is passed to the subscriber. In this case the photo URL collection is sent back using a Dictionary object:

  1. public class PhotosLoadedEventArgs : EventArgs  
  2. {  
  3.     public Dictionary<int, string> PhotoPool { get; set; }  
  4.   
  5.     public PhotosLoadedEventArgs(string response, string nsid)  
  6.     {  
  7.         ...  
  8.         // code edited to save space  
  9.     }  
  10. }  

The Control Logic

The contents of VoltaControl1.cs file is basically HTML manipulation, so most of it is self-explanatory and doesn't need further discussion. Yet there are few points to ponder. The most important part is the DownloadPhotos method:

  1. public void DownloadPhotos()  
  2. {  
  3.     _WidgetContent.InnerHtml = "<br /><center><i>Downloading photos...</i></center>";  
  4.     flickrHelper.NsidLoaded += new NsidLoadedEventHandler(flickrHelper_NsidLoaded);  
  5.     flickrHelper.GetNSID();  
  6. }  

It registers the NsidLoaded event of FlickrHelper, which is handled by flickrHelper_NsidLoaded. Then, the GetNSID method is called. Therefore, as soon as the NSID data is available, it will call the flickrHelper_NsidLoaded method to handle the data:

  1. void flickrHelper_NsidLoaded(object sender, NsidLoadedEventArgs e)  
  2. {  
  3.     if (e.Nsid == null)  
  4.         throw new Exception("Invalid NSID supplied.");  
  5.     else  
  6.     {  
  7.         _Nsid = e.Nsid;  
  8.         flickrHelper.PhotosLoaded += new PhotosLoadedEventHandler(flickrHelper_PhotosLoaded);  
  9.         flickrHelper.DownloadPhotos(_Nsid);  
  10.     }  
  11.   
  12.     flickrHelper.NsidLoaded -= flickrHelper_NsidLoaded;  
  13. }  

Unlike the NSID, the DownloadPhotos method of FlickrHelper returns a Dictionary of photos URLs and the LoadPhotos method iterates through the photos and displays them in a 3x3 grid using a typical logic for pagination.

Construction of the Volta Page

The construction of the Volta page is simple. The default page would do, but for our project we need an extra div with an id of divWidgetContainer. Moreover, we will add some other HTML code to beautify it. As for the Volta control, we are keeping the Volta page called VoltaPage1. We will use some CSS classes, and add a CSS file named Style.css in the root of the Volta application. In the current release of Volta, CSS files are not automatically discovered even if you add it in the page's HTML as links. To make the Style.css file usable by the Volta page, perform the following steps:

  1. Right click on the Style.css from the Solution Explorer and choose Properties
  2. Set the Build Action as Embedded Resource
  3. Open the VoltaPage1.Designer.cs file and add the following line before namespace VoltaFlickrWidgetDemo:
  1. [assembly: VoltaFile("Style.css")]  

As stated before, the control is very convenient to use:

  1. VoltaControl1 flickrWidget = new VoltaControl1("ajaxwidget",  
  2.     "9163070a740049b1bfe553864a4f1519", "Flickr Widget", 300, 270);  
  3.   
  4. flickrWidget.AddToDiv(divWidgetConatiner);  
  5. flickrWidget.DownloadPhotos();  

This is pretty much it. When you download and run this project, you will see Flickr REST calls and photo navigation all performed within an AJAX application created without writing any JavaScript code. Volta generated the necessary client code for us to run the application in multiple browsers.

Summary

You have learned how to create a Volta control around the Flickr API. The code was purely written in .NET, yet we achieved complete AJAX functionality without writing a single line of JavaScript code introducing typical ContentProxy kind of things.

Source: http://dotnetslackers.com/articles/aspnet/BuildingAVoltaControlAFlickrWidget.aspx

Friday, 21 March 2008

Extending the GridView to Include Sort Arrows

Introduction
Before ASP.NET version 2.0 was released, I wrote a book and dozens of articles on the DataGrid control, which was the most functional data Web control in the ASP.NET 1.x toolbox. While the DataGrid still exists in ASP.NET 2.0, the GridView control serves as a much more functional and feature-rich choice. The GridView can be bound to data source controls like the SqlDataSource and ObjectDataSource, and paging, sorting, editing, and deleting can be implemented without having to write a single line of code.

While the DataGrid (and GridView) offer built-in sorting support, there is no visual feedback as to what column the data is sorted by. In Part 18 of the An Extensive Examination of the DataGrid Web Control article series, I showed how to dynamically update a sortable DataGrid's header columns so that an up or down arrow image would appear next to the sorted column name depending on whether the column was sorted ascendingly or descendingly (view a live demo). This was accomplished by programmatically looping through the DataGrid's Columns collection and adding the up or down arrow image to column whose SortExpression value matched the DataGrid's SortExpression.

A down arrow image is shown in the Price column because the data is sorted in descending order, by price.
I recently needed to implement this functionality in a GridView control. Rather than adding this code to an ASP.NET page, like I did with the DataGrid demo, I decided to instead create a custom Web server control that extended the GridView control, adding the necessary functionality. In this article we will look at the steps for building such a custom control as well as how to use the control in an ASP.NET page. The custom control's complete source code and a demo application are available for download at the end of this article. Read on to learn more!

A Quick Primer on GridView Sorting
The GridView control makes sorting a breeze. Simply set the grid's AllowSorting property to True. Doing so renders each column's header as a LinkButton. When this LinkButton is clicked, a postback ensues and the GridView's Sorting event is fired. If the GridView is bound to a data source control that supports sorting, the GridView will internally re-bind to the data source using the new sort order. If you have programmatically bound data to the GridView, then you will need to create an event handler for the Sorting event, and manually sort and re-bind the data. After the Sorting event has fired and the data has been re-sorted, the GridView fires its Sorted event, thereby completing the sorting workflow.

Each column in the GridView has a SortExpression property that indicates the data field by which the data should be sorted if that column's sorting LinkButton is clicked. When the LinkButton in a column header is clicked, the GridView sets its SortExpression property value to the value of the clicked column's SortExpression. If the GridView's SortExpression matches the SortExpression of the sorted column, then the GridView's SortDirection property is toggled. This functionality enables the GridView control to provide built-in, bi-directional sorting.

The following diagram depicts the sorting workflow, including the GridView's assignment of its SortExpression and SortDirection properties. Keep in mind that the entire process is kicked off by the user clicking a LinkButton in one of the sortable column's headers, and completes by displaying the data in the requested sorted order. So, from the end user's perspective, she clicks the text in a particular column header and the data is sorted by that column.

The GridView's sorting workflow.

For more information on sorting with the GridView, along with a downloadable demo, check out the Paging and Sorting Report Data tutorial.

Creating a Custom Server Control that Extends the GridView
When I implemented the up and down arrow images for the DataGrid control in Part 18 of the An Extensive Examination of the DataGrid Web Control article series, I put the code that added (or removed) the arrow images in the ASP.NET page's code-behind class. Specifically, the code enumerated the DataGrid's Columns collection and, for every column, started by removing any <img> elements that were present in the HeaderText. This "cleaned out" the up or down arrow image from all columns. After removing any <img> elements, the code checked to see if the current column's SortExpression matched the DataGrid's SortExpression. If so, then an <img> element was tacked on to the end of the column's HeaderText, displaying the up or down arrow image depending on whether the data was being sorted in ascending or descending order.

Like the DataGrid, the GridView has a Columns collection; each column has a SortExpression; the GridView has SortExpression and SortDirection properties. In short, I could have implemented the up and down arrow images in the GridView using code in the ASP.NET page very similar to that used for the DataGrid. However, I decided that instead of having to write code for each page where I wanted to show up and down arrow images, I decided to instead build a custom server control that extended the GridView control. I could then bake in the necessary code by overriding the necessary GridView methods.

To create a custom server control that extends an existing control, create a public class that derives from the Web control that you want to extend. I created a class named GridView in my skmControls2 Class Library project. (The skmControls2 Class Library project was first created and discussed in Creating a TextBox Word / Character Counter Control.)

public class GridView : System.Web.UI.WebControls.GridView
{
    ... Override necessary GridView methods here ...
}

The GridView has a virtual InitializeRow method that is called each time a row is added to the GridView, including the header row. My first thought was to override this method and, for the header row, enumerate the fields and update each column's HeaderText appropriately (removing any image markup first, and then adding the up or down arrow image for the sorted row). However, if you attempt to update a GridView's field's properties during the databinding stage, the field reports to the GridView that its state has been changed and that the data needs to be re-bound. Consequently, I ended up stuck in an infinite loop:

  1. Databinding would commence
  2. The InitializeRow method would be executed for the header row
  3. I'd update the HeaderText for the cells in the header row
  4. Updating the HeaderText would signal the GridView to re-bind its data, taking us back to Step 1! Eep.
Instead, I needed to modify the HeaderText properties either before or after the databinding process. After some thought, I realized that I really only needed to update the HeaderText properties after the data has been sorted. The GridView's OnSorted method is what raises the Sorted event, which occurs after the data has been sorted. Therefore, I decided to override this method and update the HeaderText properties there.

public class GridView : System.Web.UI.WebControls.GridView
{
   protected override void OnSorted(EventArgs e)
   {
      string imgArrowUp = ...;
      string imgArrowDown = ...;
      
      foreach (DataControlField field in this.Columns)
      {
         // strip off the old ascending/descending icon
         int iconPosition = field.HeaderText.IndexOf(@" <img border=""0"" src=""");
         if (iconPosition > 0)
            field.HeaderText = field.HeaderText.Substring(0, iconPosition);

         // See where to add the sort ascending/descending icon
         if (field.SortExpression == this.SortExpression)
         {
            if (this.SortDirection == SortDirection.Ascending)
               field.HeaderText += imgArrowUp;
            else
               field.HeaderText += imgArrowDown;
         }
      }

      base.OnSorted(e);
   }
}

The overridden OnSorted method starts by defining the URLs for the up and down arrow images. We'll discuss how these image URLs are determined later on in this article. Next, the GridView's Columns collection is enumerated. For each column, if there is an <img> element it is stripped off. Next, a check is performed to see if the current column's SortExpression matches the GridView's. If so, the HTML to display the up or down arrow image is appended to the column's HeaderText depending on the value of the GridView's SortDirection property.

An HTML Encoding Gotcha

This Control Has Been Enhanced!
The "HTML Encoding Gotcha" outlined here is no longer an issue with the enhancements made to this control on February 6th, 2008. For more information on these enhancements, see: Improving the Sort Arrows GridView Control.

One subtlety that I quickly stumbled upon when testing, is that the BoundField HTML encodes its HeaderText's content, by default. That is, when the BoundField was being rendered it was taking the <img> tag added in the OnSorted method and HTML encoding it, replacing < and > with &lt; and &gt;, respectively. In other words, it was transforming a HeaderText value of:

Price <img border="0" src="up.gif" />

To:

Price &lt;img border="0" src="up.gif" /&gt;

Consequently, the browser would display the sorted column's header text as Price <img border="0" src="up.gif" /> rather than showing an image.

This BoundField has an HtmlEncode property that, if True (the default), HTML encodes the entire content of the BoundField. One way to fix the header text being HTML encoded, then, would be to set the BoundField's HtmlEncode property to False. But what if you wanted the data rows to be HTML encoded, but not the header text?

To allow for this level of flexibility, I created another custom class in the skmControls2 project. This one extended the BoundField class and overrode the InitializeCell method, which is where the HTML encoding takes place. The BoundField HTML encodes its content only if both its HtmlEncode and SupportsHtmlEncode properties are True. SupportsHtmlEncode is a read-only property that, for the BoundField, always returns True. I overrode this property and instead had it return a value based on a private member variable. Then, in the InitializeCell method I set this member variable to false when initializing a header cell.

public class BoundField : System.Web.UI.WebControls.BoundField
{
   bool allowHtmlEncode = true;

   protected override bool SupportsHtmlEncode
   {
      get
      {
         return allowHtmlEncode;
      }
   }

   public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
   {
      if (this.HtmlEncode && cellType == DataControlCellType.Header)
      {
         allowHtmlEncode = false;
         base.InitializeCell(cell, cellType, rowState, rowIndex);
         allowHtmlEncode = true;
      }
      else
         base.InitializeCell(cell, cellType, rowState, rowIndex);
   }
}

Since the BoundField is the only GridView field that HTML encodes its data, this hack is only needed for the BoundField. All other fields - the TemplateField, CheckBoxField, ButtonField, and so on - will correctly display the up and down arrow images without any extra hacks. We'll see how to use this custom BoundField class in the "Using the Custom GridView Control in an ASP.NET Web Page" section.

Specifying the Image URLs for the Up and Down Arrows
At this point you may be wondering how the custom GridView control determines the URLs for the up and down image arrows. The simplest option would be to require the page developer to specify the URLs for these two images. I created two such properties: ArrowUpImageUrl and ArrowDownImageUrl. But I also wanted to allow the page developer to have a working GridView even if she didn't want to spend the time to find up and down arrow images. Therefore, I embedded an up and down arrow image into the skmControls2 assembly and use these in the case where the developer does not specify a value for the ArrowUpImageUrl and ArrowDownImageUrl properties.

For more information on embedding resources into an assembly, and retrieving these resources through an ASP.NET page, be sure to read Accessing Embedded Resources through a URL Using WebResource.axd.

Using the Custom GridView Control in an ASP.NET Web Page
The download available at the end of this article includes the complete source code for the custom GridView and BoundField controls, as well as a demo ASP.NET website. To use the skmControls2 controls in an ASP.NET website, copy the DLL to the website's /Bin directory and then add the following @Register directive to the tops of the .aspx pages where you want to use the controls:

<%@ Register Assembly="skmControls2" Namespace="skmControls2" TagPrefix="skm" %>

(Alternatively, you can add this @Register directive in the Web.config file so that you do not need to add it to every ASP.NET page that uses the controls. See Tip/Trick: How to Register User Controls and Custom Controls in Web.config.)

Then just use the same GridView declarative markup you normally would, but replace the "asp" in the <asp:GridView> tags with "skm", as in <skm:GridView>. That's it! Likewise, if you're using any BoundFields that have their HtmlEncode properties set to True (the default), you will want to replace the "asp" in <asp:BoundField with "skm".

The demo included in the download has an ASP.NET page that shows the ProductID, ProductName, CategoryName, UnitPrice, and Discontinued fields from the Northwind's Products database table in a sortable GridView using BoundFields (namely, skmControls2 BoundFields), a TemplateField, and a CheckBoxField.

<skm:GridView ID="ProductsGrid" runat="server" AutoGenerateColumns="False" DataSourceID="NorthwindDataSource" AllowSorting="True">
   <Columns>
      <skm:BoundField DataField="ProductID" HeaderText="ID" InsertVisible="False"
         SortExpression="ProductID" />
      <asp:TemplateField HeaderText="Name" SortExpression="ProductName">
         <ItemTemplate>
            <asp:Label runat="server" Text='<%# Bind("ProductName") %>' id="Label1" Font-Bold="True"></asp:Label>
         </ItemTemplate>
      </asp:TemplateField>
      <skm:BoundField DataField="CategoryName" HeaderText="Category Name" SortExpression="CategoryName" />
      <skm:BoundField HtmlEncode="False" DataFormatString="{0:c}" DataField="UnitPrice" HeaderText="Price" SortExpression="UnitPrice" />
      <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" />
   </Columns>
</skm:GridView>

Note that the grid's declarative markup does not set the ArrowUpImageUrl or ArrowDownImageUrl properties. Consequently, the grid displays the standard up and down arrow images (see the first screen shot below). If you want to use custom arrow images, specify the URL either declaratively or programmatically. The properties can be set to absolute URLs (like http://www.example.com/images/UpArrow.gif) or relative paths using the ~ character, like ~/Images/UpArrow.png. The demo page includes a checkbox that allows you to toggle between using the default arrow images and custom ones.

The following two screen shots show the demo page in action. The first one shows the grid sorted by price in descending order. The built-in up and down arrow images are used here.

The grid sorted by price in descending order

The second image shows the grid sorted by product name in ascending order. Here, a custom up arrow image is used.

The grid sorted by product name in ascending order

Custom BoundField Markup is Lost When Using the Fields Dialog Box
If you use the custom BoundField controls from the skmControls2 library - e.g., <skm:BoundField ... /> - it is important to know that if you use the Fields dialog box to modify the GridView's columns, Visual Studio will replace the custom BoundField markup with the default markup (namely, <asp:BoundField ... />). The Fields dialog box is the dialog box displayed when you click the "Edit Columns" link from the GridView's Smart Tag.

Conclusion
In this article we looked at building a custom GridView server control (along with a custom BoundField control) in order to automatically display up and down arrow images for the sorted column. This arrow provides the user with visual feedback as to what column the data is sorted by, as well as whether the data is sorted in ascending or descending order. Best of all, this functionality is wrapped up in a custom server control, so you can utilize this behavior without having to write any code in your ASP.NET page's code-behind class. Be sure to download the source code and the demo application available at the end of this article.

Happy Programming!

  • By Scott Mitchell
  • Source: http://aspnet.4guysfromrolla.com/articles/012308-1.aspx

    Thursday, 20 March 2008

    How to open popup windows in IE/Firefox and return values using ASP.NET and Javascript

    With the forums flooded with questions of opening a popup window, passing values to the popup window and then return values back to the parent page using both Internet Explorer and Firefox, I decided to take a plunge into the subject and experiment with an easy implementation. This article explains how to transfer values between the Parent page and a Pop-up window. The code has been tested against IE7 and Firefox.
    Internet Explorer(IE) contains the showModalDialog() method which proves very effective while creating a modal dialog box and displaying a html document in it. One caveat being, showModalDialog() is not a W3C implementation. So it is not supported in Firefox (as of version 2.0.0.11). Firefox supports the window.open() method. However there is no built in functionality in Firefox that keeps the popup modal. At the most, you can use 'modal=yes' to make the popup appear in front. However unlike the showModalDialog() in IE, you cannot restrict the user to access the parent page when the popup is opened in Firefox. In short, it is not truly modal. There are different ways to get around this problem, however in this article, we will only focus on exchanging values between the parent and popup page.
    In this article, we will see how to take a simple approach and create a popup window using both IE and Firefox. In the first part, we will pass in the first name from the parent page to the popup window. In the second part, the popup window will reverse the name and return the reversed string to the parent window. All set!! Let us get started.
    Part 1 - Passing value to Popup window
    Step 1: Open Visual Studio. Create a new website (File > New > Website). Set the location, filename and language of the project.
    Step 2: In your Default.aspx, add a label (lblFName), textbox (txtFName) and a button (btnPopup). Set the 'Text' property of the label to 'FirstName'. Similarly set the 'Text' property of the button control to 'Show Popup'.
    Step 3: Now add the popup form. Right click your project > Add New Item > Web Form > Rename form as 'PopupWin.aspx' and click on Add.
    Step 4: In the PopupWin.aspx, add a textbox (txtReverse) and a button (btnReverse).
    Well now we have two pages, Default.aspx which is the parent page and PopupWin.aspx which will be the popup page. Let us now pass a value from Default.aspx to the popup window.
    Step 5: We will invoke the popup window on the button (btnPopup) click of Default.aspx. To do so, we will use Button.Attribute.Add and call a javascript function that will open the popup window. The javascript function will be contained in a seperate pop.js file which we will create shortly. Add this code in the code behind file of your Default.aspx.
    C#
    protected void Page_Load(object sender, EventArgs e)
        {
            btnPopup.Attributes.Add("onClick", "javascript:InvokePop('" + txtFName.ClientID + "');");
        } 
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
            btnPopup.Attributes.Add("onClick", "javascript:InvokePop('" & txtFName.ClientID & "');")
        End Sub
    Over here we are passing the ClientID of the textbox. ClientID is the identifier of the server control, generated by ASP.NET. You must be wondering why I am not passing the value of the textbox directly. Well passing the control has an advantage where there is more than one control that is passed to the popup page. While returning back the values from the popup to the parent page; it helps you to decide and determine which control receives which value. Even though we will be using only one textbox for simplicity, I thought of creating a sample which can be extended later by you to suit your needs. If the use of ClientID is not clear to you, wait till we get to part 2 of this article, and I will again touch upon the subject.
    Step 6: Let us now create the javascript functionality which will open the Popup. Right click your project > Add New Item > Jscript file > Rename the file to pop.js. Add the following function to the pop.js file
    function InvokePop(fname)
    {
            val = document.getElementById(fname).value;
            // to handle in IE 7.0          
            if (window.showModalDialog)
            {      
                retVal = window.showModalDialog("PopupWin.aspx?Control1=" + fname + "&ControlVal=" + val ,'Show Popup Window',"dialogHeight:90px,dialogWidth:250px,resizable:yes,center:yes,");
                document.getElementById(fname).value = retVal;
            }
            // to handle in Firefox
            else
            {     
                retVal = window.open("PopupWin.aspx?Control1="+fname + "&ControlVal=" + val,'Show Popup Window','height=90,width=250,resizable=yes,modal=yes');
                retVal.focus();           
            }         
    }
    This function accepts the textbox control, retrieve's the value of the textbox that needs to be reversed and passes the textbox control and its value to PopupWin.aspx through query string. This is the function which will be called on the btnPopup click.
    Step 7: To wire up the .js with your asp.net pages, add a link to the javascript file in both the pages as shown below:
    Default.aspx
    <head runat="server">
        <title>Parent Page</title>
        <script type="text/javascript" src="pop.js"></script>
    </head>
    PopupWin.aspx
    <head runat="server">
        <title>Popup Page</title>
        <script type="text/javascript" src="pop.js"></script>
    </head>
    Step 8: In the code behind file of PopupWin.aspx, add the following code at the Page_Load() to retrieve the value from the querystring and display the value in the TextBox 'txtReverse', placed in the popup window.
    C#
    protected void Page_Load(object sender, EventArgs e)
        {
            txtReverse.Text = Request.QueryString["ControlVal"].ToString();
        }
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
            txtReverse.Text = Request.QueryString("ControlVal").ToString()
        End Sub
    If you are eager to test the value going from Parent page to the popup window, you can do so now. Make Default.aspx as 'Set as Start page' and run the sample. Enter your name in txtFName TextBox and click on Show Popup button. A popup window opens with the value entered in the Parent page.
    Part 2 - Passing value from Popup window to the Parent page
    In this second part, we will reverse the string and pass the reversed string back to the parent page. To do so, follow these steps:
    Step 1: Add additional functions to the pop.js file which will reverse the string and return the string back to the parent page.
    // pop.js
    function ReverseString()
    {
             var originalString = document.getElementById('txtReverse').value;
             var reversedString = Reverse(originalString);
             RetrieveControl();
             // to handle in IE 7.0
             if (window.showModalDialog)
             {             
                  window.returnValue = reversedString;
                  window.close();
             }
             // to handle in Firefox
             else
             {
                  if ((window.opener != null) && (!window.opener.closed))
                  {              
                    // Access the control.       
                    window.opener.document.getElementById(ctr[1]).value = reversedString;
                  }
                  window.close();
             }
    }
     
    function Reverse(str)
    {
       var revStr = "";
       for (i=str.length - 1 ; i > - 1 ; i--)
       {
          revStr += str.substr(i,1);
       }
       return revStr;
    }
     
    function RetrieveControl()
    {
            //Retrieve the query string
            queryStr = window.location.search.substring(1);
            // Seperate the control and its value
            ctrlArray = queryStr.split("&");
            ctrl1 = ctrlArray[0];
            //Retrieve the control passed via querystring
            ctr = ctrl1.split("=");
    }
    As you saw in part 1, the value was passed from the parent window to the popup window and was kept in the txtReverse TextBox. The function ReverseString() retrieves the value from this textbox and passes the string to the Reverse() function which reverses the string. The reversed string is then kept in the 'reversedString' variable. The 'RetrieveControl' splits the query string and identifies the control in the parent page to which the reversed string value is to be sent.
    Note: If you observe, in the IE implementation, I am not really making use of the RetrieveControl(), however in Firefox I am. If you remember, in the beginning of part1 , I had mentioned the use of ClientID, using which both controls and their values can be passed to determine which control recieves which value. This is especially needed when there are multiple controls. Well the RetrieveControl seperates the different controls and you can use the variables in this method to return values to the respective contro.l
    The value is then returned to the parent window and the popup window is closed.
    Step 2: Now in order to use these newly added javacript functions, just call the ReverseString() function on the btnReverse click. To do so, add the onclick attribute to the btnReverse.
    <input class="button" type="button" id="btnReverse" value="Reverse value back" onclick="ReverseString();"/>
    That's it. Now test the code. Pass your name from the Parent window to the Popup window and then reverse the string and pass it back to the Parent window.
    I would like to mention that there are multiple ways of doing the task demoed in this article. I have seen some cool examples by experts. One of them I would like to mention is that of NC01 where he makes use of properties and ViewState, to pass values to the Popup window. I would encourage you to use this article as a base and try out your own implementations that would work on multiple browsers.
    The source code of this article, both in C# and VB.NET, can be downloaded from here. I hope this article was useful and I thank you for viewing it. If you liked the article, please subscribe to the RSS feed over here.