Wednesday, August 4, 2010

Multiple state box in ASP.NET 3.5

We usually play with the checkboxes/ radio buttons when we provide choices for the user. But how will it be interesting to use the old style checkboxes where my choices (I will call them as states) are Accept, Reject, or Tentative? For more examples, if I would like to provide more states not just two, and want to represent each state with unique image then how can I achieve it?

Here is the example which goes out of the box to use styling images to represent each choice that users can select.

We will take below example and complete this assessment.

Example:
I would like to collect reviews from the users about my articles. For that I would like to provide them the following options:
- Not adequate
- Average
- Good
- Helpful
- Very helpful

All reviews will be presented using stylish images. User will click on the first state i.e. Not adequate. If he would like to increase his/her rating to second level, simply click on the image again. This will show an image for the second state. For the next level, simply click again and it will show you the next level state.

I should also be able to add more states dynamically with minimum efforts.

Evaluating multiple approaches:
For this, we will not use the existing checkbox/ radio button control as they do not provide enough scope for this requirement.

Initially I thought it will be simple if I use the toggle button extender control from the Ajax. When I completed the work using it, I found that before loading the actual styling images, it still shows the default checkboxes for fraction of seconds. Also it puts other limitations that we can’t read its states for more than 2 values. So I thought that this will be much easier using our own control.

So I did plan to develop a custom control for this. To respect that the initial idea came from the checkboxes and toggle button extender, I will name my custom control as “ImageCheckbox”.

Writing Custom control class:
You need to create a new class file with the name as ImageCheckbox. You can read more on how to create custom control over the net. Here is the complete source code for this class. This is simple web server control that inherits directly from WebControl class.

using System;
using System.Data;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;

namespace ImageCheckbox
{
public class ImageCheckbox : WebControl, IPostBackEventHandler
{
public ImageCheckbox()
: base(HtmlTextWriterTag.Span)
{
State = "unchecked";
ImageFormat = ".gif";
}

public event EventHandler Click;

///
/// Call JavaScript client side functions. Put the JavaScript in string format.
///

public string OnClientClick
{
get { return ViewState["onclientclick"] as string; }
set { ViewState["onclientclick"] = value; }
}

///
/// Location of the images. This is the path where all the images representing each state will reside.
///

public string ImagePath
{
get { return ViewState["imagepath"] as string; }
set { ViewState["imagepath"] = value; }
}

///
/// Format of the images. All images representing states should be of the same format.
///

public string ImageFormat
{
get { return ViewState["imageformat"] as string; }
set { ViewState["imageformat"] = value; }
}

///
/// Current state value. Assign a string value representing the current state.
///

public string State
{
get { return ViewState["state"] as string; }
set { ViewState["state"] = value; }
}

private string ImageURL { get {
string lImageURL = String.Empty;
lImageURL = ImagePath;
if (!(ImagePath.LastIndexOf('/') == (ImagePath.Length - 1)))
lImageURL += "/";
lImageURL += State + ImageFormat;
return lImageURL;
} }

protected override void RenderContents(HtmlTextWriter writer)
{
base.RenderContents(writer);
writer.WriteBeginTag("img");
writer.WriteAttribute("name", this.UniqueID);
writer.WriteAttribute("src", ImageURL);
writer.WriteAttribute("onclick", "javascript:" + OnClientClick + " " + Page.ClientScript.GetPostBackEventReference(this, string.Empty));
writer.Write("/>");
}

public void RaisePostBackEvent(string eventArgument)
{
Click(this, EventArgs.Empty);
}
}
}


Properties:
This accepts following properties from the user.

Image Path:
Location of the images. This is the path where all the images representing each state will reside.

Image Format:
Format of the images. All images representing states should be of the same format.

State:
Current state value. Assign a string value representing the current state.

On Client Click:
Call JavaScript client side functions. Put the JavaScript in string format. This is to add more flexibility for the developers allowing them to extend the solution at the next level as per their requirements.

Using custom control in our application:
This is very important part of this problem. This part will explain how to best use the dynamic nature of the custom control we just built.

Add folder and images for state values:
As we have 5 states (options) for the ratings that I will be collecting from the users, I will need 5 images representing each state value. All the images must use the same image format (file extension should be the same).
Create a new folder in your application (most likely under images folder which you are using to store all the images that your application needs). I will name my folder as “ratings”. Add these images into this folder.

Here are my file names:
- not_adequate.gif
- average.gif
- good.gif
- helpful.gif
- very_helpful.gif

Suppose I am writing default.aspx page in which I will include the above control.

We will get back to this point on how to include the Image checkbox custom control in our page. Before that we will look at other important aspects of this solution.

Using Enum to represent collection of the state values:
I will use enum to make this solution strongly bounded with the requirement.

Here is my Enum which defines behavior of the states. (This will go into the default.aspx.cs file.)

public enum CheckboxStates
{
not_adequate,
average,
good,
helpful,
very_helpful
}

Now, we will go back to default.aspx page and get the custom control added to the page (you can read more on how to add custom control to the page over the net).

Register custom control:
Add below line in your aspx page to register your custom control.

(Ignore the space before or after <> )

< %@ Register Assembly="ImageCheckbox" Namespace="ImageCheckbox" TagPrefix="cm" % >


Add it in the form:
Add the instance of the custom control in the form and set appropriate values of the properties.

< id="imgCheckbox" onclick="imgCheckbox_Click" runat="server" state="average" onclientclick="alert('hi....');" imageformat=".gif" imagepath="images/ratings/"> < /cm:imagecheckbox >


Assign the state to default value. Here the default value given is ‘average’.

OnClientClick property is used to show the alert box saying ‘hi…’ just to present how it works or how it can be used. You can call any function of your need.

Here the image format is ‘.gif’.

Path for the images is ‘images/ratings’.

Very important is to add OnClick event. The current version of this custom control uses post back technique to change the state of the control. Modifying it to handle client side state change will be very good improvement here.

I wanted it do at server side, just to explore that we can even call a server side script to execute server side code and perform some business logic with every change of state. I can store the frequency of user’s choice in the database with the onclick event.


Server side hander to change control states:
Here is my server side code for the OnClick event. This handles change in state one by one based on the values specified in the Enum.

protected void imgCheckbox_Click(object sender, EventArgs e)
{
string firstState = String.Empty;
string nextState = String.Empty;
string currentState = imgCheckbox.State;
bool isBreak = false;

foreach(string stritem in Enum.GetNames(typeof(CheckboxStates)))
{
if(String.IsNullOrEmpty(firstState))
firstState = stritem;

if (isBreak)
{
nextState = stritem;
break;
}
if(!isBreak)
{
if(stritem == currentState)
isBreak = true;
}
}
if (String.IsNullOrEmpty(nextState))
nextState = firstState;

imgCheckbox.State = nextState;

//Do more stuff

}


Adding more state values:
Just add the new images for your new states in the ratings folder and add the name of the files in the Enum. That’s it… you are done!


Limitations:
> I will try to get the next version of the solution here which will handle changing states at client side soon.

> I will also try to add more properties in order to show the text value for the state providing more utilization of the solution to replace the normal checkboxes or toggle button extender in our application.

> The sequence of the states should be monitored and should appear as they are listed in the Enum.

Thank you.

Let me know your suggestions or improvements that you think are important for this solution.

2 comments:

  1. It is really nice post and very useful...
    Just wanted to know whether it will affect on performance as compared to standard controls???

    ReplyDelete
  2. CM: not reallly ..... standard controls are itself classes which needs to be built
    the same way ..the custom controls are built.
    it is same... it is just that standard controls provide readymade features
    here we have to built by our own

    Nitesh: means every controls need to be build at runtime...

    CM: here built means rendered

    Nitesh: and same functioning is with our custom controls

    CM: Standard controls are the DLL files basically.
    we can also create DLL and add it in toolbox for our custom controls

    Nitesh: yes

    CM: complete Ajax library is built using custom controls

    Nitesh: Means performance doesnt matter for custom control

    CM: I will say yes

    Nitesh: Ok...thanks.

    ReplyDelete