Search This Blog

Tuesday, September 10, 2013

How create Image DropDownList a Custom Asp.Net Server Control Quick Dirty:

There are lot of jquery UI elements that are making webpage look good why not try to get similar functionality in asp.net control.

Let’s try to make one, here I am choosing ImageDropdownList jquery UI.

1) First we will Create a new project as File ->New Project now from available project template choose the “Asp.Net Server Control”.

2) Now in current Solution add a new “Asp.Net Empty Web Application” We have a jquery UI for ImgeDropDownList that we will use to create our new server side control the jquery UI is at http://www.marghoobsuleman.com/mywork/jcomponents/image-dropdown/jquery-image-dropdown-2.1/index.html

download the relevant files and add below files from downloaded folder to our server control project at root

Css:
1) dd.css

Javascript:
1)jquery-1.9.0.min.js
2) jquery.dd.min.js

Right Click on each of these newly added js or css file and from property change "Build Action" to "embedded resource".

Now inside our server control csharp code add to new properties

one that let you to bind your image to ImageDropdownlist along with valuefield & Textfield through datasource/datatable & second one a dataset to hold bound datatable so that bound data doesn't get lost during page postbacks
Here is correponding code
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public string DataImageField
{
get
{
String s = (String)ViewState["DataImageField"];
return ((s == null) ? String.Empty : s);
}
set
{
ViewState["DataImageField"] = value;
}
}
public DataTable PersistentDatasource
{
get;
set;
}

looking at dropdownlist control as html control we can see that option tag is child for select i.e. dropdown hence we will code our renderchilden method as follows

/*Generates child control options tags*/
protected override void RenderChildren(HtmlTextWriter writer)
{
if (DataSource ==null)
{
DataSource = this.PersistentDatasource;
}
if (DataSource != null)
{
if (DataSource.GetType() == typeof(DataTable))
{
DataTable source = (DataTable)DataSource;
if (source.Rows.Count == 0)
{
base.RenderChildren(writer);
return;
}
foreach (DataRow dr in source.Rows)
{
string value = dr[this.DataValueField].ToString();
string imgPath = dr[this.DataImageField].ToString();
string text = dr[this.DataTextField].ToString();
bool selected = this.SelectedValue.Equals(value);
if (selected)
{
writer.Write("<option value='" + value + "' selected='selected' data-image='" + imgPath + "'>" + text + "</option>");
}
else
{
writer.Write("<option value='" + value + "' data-image='" + imgPath + "'>" + text + "</option>");

}
writer.WriteLine();
}
}
}
else
{
base.RenderChildren(writer);
}
}

to retreive data posted back as selection from Image dropdownlist we are implementing

IPostBackDataHandler interface as follows where we will get selected value of image dropdownlist using posted data collection if LoadPostData return true then RaisePostDataChangedEvent get called else not

/*retreving selected value after postback*/
protected override bool LoadPostData(string postDataKey, NameValueCollection postCollection)

{
// get the new selected value from the postback data collection
// and if it's different from the current one, select it onto our property
// and return true, so we get a RisePostDataChangedEvent call
string newSelectedValue = postCollection[postDataKey];
if (newSelectedValue != null)
{
if (newSelectedValue != SelectedValue)
{
SelectedValue = newSelectedValue;
return true;
}
}
return false;
}
/*postback event on change of selected value*/

protected override void RaisePostDataChangedEvent()
{
this.OnSelectedIndexChanged(EventArgs.Empty);
}

we are also inherting from INamingContainer from interface which doesn't have any method to implement yet maintain our controls generated id's to be unique in given scope.

here we want to keep our bound datasource avaiable even after postback hence we will store for latter use as follows

protected override object SaveControlState()
{
//grab the state for the base control.
object baseState = base.SaveControlState();
//create an array to hold the base control’s state
//and this control’s state.
object thisState = new object[] { baseState, this.PersistentDatasource };
return thisState;
}

protected override void LoadControlState(object savedState)
{
object[] stateLastRequest = (object[])savedState;
//Grab the state for the base class
//and give it to it.
object baseState = stateLastRequest[0];
base.LoadControlState(baseState);
//Now load this control’s state.
this.PersistentDatasource = (DataTable)stateLastRequest[1];
}

when Imagedropdownlist is first get bound we will store it in our PersistentDatasource property using OnLoad event as follows

protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (!Page.IsPostBack)
{
this.PersistentDatasource = (DataTable)this.DataSource;
}
}

in OnInit we are telling we need to Control State further more making sure that Page object is not null

/*checking if page object is available*/
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
Page.RegisterRequiresControlState(this);
if (Page == null)
{
throw new InvalidOperationException("We need a page to operate properly");
}
if (!Page.IsPostBack)
{
SelectedIndex = -1;
}

}

we need to make sure that required javascript & css file are available when needed hence first inside Assembly.info of server control add these line

[assembly: WebResource("MyDropDownList.dd.css", "text/css")]

[assembly: WebResource("MyDropDownList.jquery-1.9.0.min.js", "text/javascript", PerformSubstitution = true)]

[assembly: WebResource("MyDropDownList.jquery.dd.min.js", "text/javascript", PerformSubstitution = true)]

second thing done for including JS & CSS is inside OnPreRender

these two step create .axd file from our css & js files and include in head in every aspx page where we add our newly created ImageDropdown control.

/*Including Embedded javascript & css files*/
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
#region EmbeddedResource
string jsResource1 = "MyDropDownList.jquery-1.9.0.min.js";
this.Page.ClientScript.RegisterClientScriptResource(this.GetType(), jsResource1);
string jsResource2 = "MyDropDownList.jquery.dd.min.js";
this.Page.ClientScript.RegisterClientScriptResource(this.GetType(), jsResource2);
string cssResource = "MyDropDownList.dd.css";
string cssResourceURL = Page.ClientScript.GetWebResourceUrl(this.GetType(), cssResource);
HtmlLink cssLink = new HtmlLink();
cssLink.Href = cssResourceURL;
cssLink.Attributes.Add("rel", "stylesheet");
this.Page.Header.Controls.Add(cssLink);
#endregion
Page.RegisterRequiresPostBack(this);
}

as our ImageDropDownList control is just select control from html where we already made provision to render it's children option tag

now it is time for select tag itself using Render event.

protected override void Render(HtmlTextWriter output)
{
AddAttributesToRender(output);
output.RenderBeginTag(HtmlTextWriterTag.Select);
// Render Controls.
RenderChildren(output);
output.RenderEndTag();
}
here output.RenderBeginTag(HtmlTextWriterTag.Select); line tells to add opening select tag when render while RenderEndTag ensured it to be closed as opened earlier.

we need to ensure that proper jquey get fired when page loads that will cause our dropdowncontrol to be converted into imagedropdownlist hence we will emot corresponding javscript into body of our web page as follows

/*render startup javascript to tranform dropdownlist to image dropdownlist*/
public override void RenderControl(HtmlTextWriter writer)
{
base.RenderControl(writer);

string StrImageDropDownScript = "<script type='text/javascript'>$(document).ready(function (e) {try {$('#" + this.UniqueID.ToString() + "').msDropDown();} catch (e) {alert(e.message);}});</script>";

writer.Write(StrImageDropDownScript.ToString());
}
Here is code all of server.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threding.Tasks;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;
using System.Web.UI.HtmlControls;
using System.Collections.Specialized;
using System.Collections;

namespace MyDropDownList
{
[DefaultProperty("Text")]
[ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")]
public class ServerControl1 : DropDownList,INamingContainer,IPostBackDataHandler
{
StringBuilder sb = new StringBuilder();
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public string DataImageField
{
get
{
String s = (String)ViewState["DataImageField"];
return ((s == null) ? String.Empty : s);
}
set
{
ViewState["DataImageField"] = value;
}
}
public DataTable PersistentDatasource
{
get;
set;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (!Page.IsPostBack)
{
this.PersistentDatasource = (DataTable)this.DataSource;
}
}

/*checking if page object is available*/
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
Page.RegisterRequiresControlState(this);
if (Page == null)
{
throw new InvalidOperationException("We need a page to operate properly");
}
if (!Page.IsPostBack)
{
SelectedIndex = -1;
}
}

/*Including Embedded javascript & css files*/
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
#region EmbeddedResource
string jsResource1 = "MyDropDownList.jquery-1.9.0.min.js";
this.Page.ClientScript.RegisterClientScriptResource(this.GetType(), jsResource1);
string jsResource2 = "MyDropDownList.jquery.dd.min.js";
this.Page.ClientScript.RegisterClientScriptResource(this.GetType(), jsResource2);
string cssResource = "MyDropDownList.dd.css";
string cssResourceURL = Page.ClientScript.GetWebResourceUrl(this.GetType(), cssResource);
HtmlLink cssLink = new HtmlLink();
cssLink.Href = cssResourceURL;
cssLink.Attributes.Add("rel", "stylesheet");
this.Page.Header.Controls.Add(cssLink);
#endregion
Page.RegisterRequiresPostBack(this);
}

protected override void Render(HtmlTextWriter output)
{
AddAttributesToRender(output);
output.RenderBeginTag(HtmlTextWriterTag.Select);
// Render Controls.
RenderChildren(output);
output.RenderEndTag();
}
/*render startup javascript to tranform dropdownlist to image dropdownlist*/
public override void RenderControl(HtmlTextWriter writer)
{
base.RenderControl(writer);
string StrImageDropDownScript = "<script type='text/javascript'>$(document).ready(function (e) {try {$('#" + this.UniqueID.ToString() + "').msDropDown();} catch (e) {alert(e.message);}});</script>";

writer.Write(StrImageDropDownScript.ToString());
}
/*Generates child control options tags*/
protected override void RenderChildren(HtmlTextWriter writer)
{
if (DataSource ==null)
{
DataSource = this.PersistentDatasource;
}
if (DataSource != null)
{
if (DataSource.GetType() == typeof(DataTable))
{
DataTable source = (DataTable)DataSource;
if (source.Rows.Count == 0)
{
base.RenderChildren(writer);
return;
}
foreach (DataRow dr in source.Rows)
{
string value = dr[this.DataValueField].ToString();
string imgPath = dr[this.DataImageField].ToString();
string text = dr[this.DataTextField].ToString();
bool selected = this.SelectedValue.Equals(value);
if (selected)
{
writer.Write("<option value='" + value + "' selected='selected' data-image='" + imgPath + "'>" + text + "</option>");

}
else
{
writer.Write("<option value='" + value + "' data-image='" + imgPath + "'>" + text + "</option>");

}
writer.WriteLine();
}
}
}
else
{
base.RenderChildren(writer);
}
}

/*retreving selected value after postback*/
protected override bool LoadPostData(string postDataKey, NameValueCollection postCollection)
{
// get the new selected value from the postback data collection
// and if it's different from the current one, select it onto our property
// and return true, so we get a RisePostDataChangedEvent call
string newSelectedValue = postCollection[postDataKey];
if (newSelectedValue != null)
{
if (newSelectedValue != SelectedValue)
{
SelectedValue = newSelectedValue;
return true;
}
}
return false;
}
/*postback event on change of selected value*/
protected override void RaisePostDataChangedEvent()

{
this.OnSelectedIndexChanged(EventArgs.Empty);
}

protected override object SaveControlState()
{
//grab the state for the base control.
object baseState = base.SaveControlState();

//create an array to hold the base control’s state
//and this control’s state.
object thisState = new object[] { baseState, this.PersistentDatasource };
return thisState;
}
protected override void LoadControlState(object savedState)
{
object[] stateLastRequest = (object[])savedState;
//Grab the state for the base class
//and give it to it.
object baseState = stateLastRequest[0];
base.LoadControlState(baseState);

//Now load this control’s state.
this.PersistentDatasource = (DataTable)stateLastRequest[1];
}
}
}
Now we need to compile our server control code and add our new dropdownlist in a aspx page from our second empty website project
after dragging the image dropdownlist to your aspx page html tags look like below

<cc1:ServerControl1 ID="ServerControl11" runat="server" OnSelectedIndexChanged="ServerControl11_SelectedIndexChanged"></cc1:ServerControl1>

here 'ServerControl1' is name of my class in my servercontrol.cs file.inside your aspx.cs add below code which binding newly created ImageDropdownlist.in this code we are creating a datatable and binding it to image dropdownlist.you will notice that unlike normal dropdownlist bind here DataImageField is the addtional property that is also being bind

inside your web application add Images folder and add three images namely 1.gif,2.gif,3.gif ensure that these are small in size

protected void Page_Load(object sender, EventArgs e)
{
if(!this.IsPostBack)
{
BindDropdown();
}
}
public void BindDropdown()
{
DataTable dt = new DataTable();
dt.Columns.Add("ValueField", typeof(String));
dt.Columns.Add("TextField", typeof(String));

dt.Columns.Add("ImageField", typeof(String));
DataRow dr1 = dt.NewRow();
dr1["ValueField"] = "1";
dr1["TextField"] = "one";

dr1["ImageField"] = "Images/1.gif";
dt.Rows.Add(dr1);

DataRow dr2 = dt.NewRow();
dr2["ValueField"] = "2";
dr2["TextField"] = "two";
dr2["ImageField"] = "Images/2.gif";
dt.Rows.Add(dr2);

DataRow dr3 = dt.NewRow();
dr3["ValueField"] = "3";
dr3["TextField"] = "three";
dr3["ImageField"] = "Images/3.gif";
dt.Rows.Add(dr3);

ServerControl11.DataSource = dt;
ServerControl11.DataTextField = "TextField";
ServerControl11.DataValueField = "ValueField";
ServerControl11.DataImageField = "ImageField";
ServerControl11.DataBind();
}
protected void Button1_Click(object sender, EventArgs e)
{
string Str = ServerControl11.SelectedValue;
}
Code above is working code not neccesarily optimized code