Mover List Control for ASP.NET 1.x/2.0
page 10 of 14
by Bilal Haidar
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 60407/ 150

Overridden Methods

A set of methods that are part of the WebControl class are overridden in this control. The methods are the following.

CreateChildControls

The CreateChildControl is the core method used in composite control development. This method has mainly three functionalities:

Clear the entire child controls.

Build the control tree of the control itself.

Clear the view state of all the child controls.

The above mentioned functionalities are specified as follows:

Listing 11

protected override void CreateChildControls()
{
// Clear any present control
  Controls.Clear();
 
// Create the actual heirarchy
  CreateControlHierarchy();
 
// Clear view state of child controls
  this.ClearChildViewState();
}

The CreateControlHierarchy is implemented as follows:

Listing 12

protected virtual void CreateControlHierarchy()
{
  #region Initialize Controls
  InitializeControls();
  #endregion
 
// Build the hierarchy
  this.Controls.Add(new LiteralControl(
    "<table cellpadding='3' cellspacing='3' border='0'>"));
  this.Controls.Add(new LiteralControl("<tr>"));
 
// Cell for the Left ListBox
  this.Controls.Add(new LiteralControl("<td valign='middle' align='center'>"));
  this.Controls.Add(this.leftListBox);
  this.Controls.Add(new LiteralControl("</td>"));
 
// Cell for the Buttons
  this.Controls.Add(new LiteralControl("<td valign='middle' align='center'>"));
  this.Controls.Add(new LiteralControl(
    "<table cellpadding='0' cellspacing='0' border='0'>"));
  this.Controls.Add(new LiteralControl("<tr><td>"));
  this.Controls.Add(this.leftToRight);
  this.Controls.Add(new LiteralControl("</td></tr>"));
  this.Controls.Add(new LiteralControl("<tr><td>"));
  this.Controls.Add(this.rightToLeft);
  this.Controls.Add(new LiteralControl("</td></tr>"));
  this.Controls.Add(new LiteralControl("</table>"));
  this.Controls.Add(new LiteralControl("</td>"));
 
// Cell for the Right ListBox
  this.Controls.Add(new LiteralControl("<td valign='middle' align='center'>"));
  this.Controls.Add(this.rightListBox);
  this.Controls.Add(new LiteralControl("</td>"));
 
// Close the TR and Table
  this.Controls.Add(new LiteralControl("</tr>"));
  this.Controls.Add(new LiteralControl("</table>"));
}

First of all, the local method called InitializeControls is called. This method does nothing but initialize the local child controls used. Its implementation is show below.

Listing 13

private void InitializeControls()
{
  #region Initialize LeftListBox
  this.leftListBox.ID = LEFT_LISTBOX_NAME;
  this.leftListBox.Width = Unit.Pixel(200);
  this.leftListBox.Rows = 15;
  this.leftListBox.ApplyStyle(this.leftListBoxStyle);
// Make sure to have multiple selection mode
// this way, we will have only two buttons in between
// the list boxes so that, movement can be done
// per item or items.
  this.leftListBox.SelectionMode = ListSelectionMode.Multiple;
  #endregion
 
  #region LeftToRightButton
  this.leftToRight.ID = LEFT_TO_RIGHT_BUTTON;
  this.leftToRight.EnableViewState = false;
  this.leftToRight.Text = ">";
  this.leftToRight.ApplyStyle(this.buttonChooserStyle);
  #endregion
 
  #region RightToLeftButton
  this.rightToLeft.ID = RIGHT_TO_LEFT_BUTTON;
  this.rightToLeft.EnableViewState = false;
  this.rightToLeft.Text = "<";
  this.rightToLeft.ApplyStyle(this.buttonChooserStyle);
  #endregion
 
  #region Initialize RightListBox
  this.rightListBox.ID = RIGHT_LISTBOX_NAME;
  this.rightListBox.Width = Unit.Pixel(200);
  this.rightListBox.Rows = 15;
  this.rightListBox.ApplyStyle(this.rightListBoxStyle);
  this.rightListBox.SelectionMode = ListSelectionMode.Multiple;
  #endregion
}

As you can see, for each child control used a set of properties are configured and, hence, the control itself is being initialized.

Going back to the CreateControlHierarchy method, after initializing the child controls, there comes the time to add them to the composite controls’ property Controls. This property contains all the child controls that are going to be shown and used in this control.

We add the controls and contain them in a constructed table as shown above in the code. A better approach to construct this table is to use the Render method of each control to render the table required and each control inside it. However, for the sake of simplicity we have constructed the table using LiteralControls in the CreateChildControls to hold the table, tr, td tags, and other controls required to be rendered; however, as a rule of thumb, it is better to use the Render method to do so.

Hint: When constructing the tree of controls, it is recommended to use the Render method by using AddAttribute, AddStyleAttribute, and other useful methods used inside the Render method.

Render

The render method of the composite control has been overridden, mainly to attach some JavaScript method calls on the two buttons added in the control. Those JavaScript methods will be used to move items between the two controls easily and neatly.

Listing 14

protected override void Render(HtmlTextWriter writer)
{
// Prepare Scripts
  string scriptLeftToRight = "bhMover_MoveItem('" + this.leftListBox.ClientID +
    "','" + this.rightListBox.ClientID + "');";
  string scriptRightToLeft = "bhMover_MoveItem('" + this.rightListBox.ClientID
    + "','" + this.leftListBox.ClientID + "');";
 
// Ensure controls are created
  this.EnsureChildControls();
 
// Add client side events
  this.leftToRight.Attributes.Add("onclick", scriptLeftToRight +
    " return false;");
  this.rightToLeft.Attributes.Add("onclick", scriptRightToLeft +
    " return false;");
 
  base.Render(writer);
}

First of all, we constructed the JavaScript methods calls. Notice that we have used the ChildID of the two listboxes. With this implementation you make sure that if you have placed your mover control in a MasterPage for instance, the ID of the listboxes will be calculated as it should be, hence, taking care of all the successive containers where the listbox will be displayed.

After that we make sure the control tree is constructed by simple calling EnsureChildControls method discussed above.

Then we have attached the “onclick” client event for each server side button to the above created JavaScript method calls. Notice that we have added “return false;” to the end of the “onclick” event. What does this do? This ensures that the server side button used in this composite control will not cause a postback to the server and, therefore, only the JavaScript method calls will be executed and no postback happens and all the movement of items between the two listboxes will be done on the client side.  This clearly explains why we have used the xListBox control instead of the normal ListBox!

Hint: Add “return false;” to any client side event attached to any server side control to prevent the server side control from posting back to the server.

OnPreRender

The OnPreRender method is used to add any JavaScript methods required by the control. As you have seen above, the two buttons used to move items between the listboxes calls a JavaScript method named “bhMover_MoveItem”. This method is shown below.

Listing 15

function bhMover_MoveItem(ctrlSource, ctrlTarget)
{
  var Source = document.getElementById(ctrlSource);
  var Target = document.getElementById(ctrlTarget);
 
// Make sure both Listboxs are present
  if ((Source != null) && (Target != null))
  {
// Loop through all the items selected
// since the ListBoxes are configured to have
// multiple selection mode
    while (Source.options.selectedIndex >= 0)
    {
// Create a new instance of ListItem
      var newOption = new Option();
      newOption.text = Source.options[Source.options.selectedIndex].text;
      newOption.value = Source.options[Source.options.selectedIndex].value;
 
//Append the item in Target
      Target.options[Target.length] = newOption;
 
// Add the new text|value to the Hidden Field _ADDED of the Target Listbox
      AddListItem(Target, newOption.text, newOption.value);
 
//Remove the item from Source
      Source.remove(Source.options.selectedIndex);
 
// Add the index of the item to be removed into the
// Hidden Field _REMOVED
      RemoveListItem(Source, newOption.value);
 
    }
  }
}

This method is usually called by passing to it two parameters which are the source and target listboxes. This means that in case we are moving items from the left listbox to the right listbox, we will send as a source the left listbox and as a target the right listbox. It will simply loop through all selected items in the source listbox and moves them to the target listbox control. In addition, it calls two methods that come from the xListbox, mainly the AddListItem and RemoveListItem. Those two methods have been thoroughly explained the xListBox article mentioned above. Those two items add/remove any item to some hidden fields, on the page holding the control, that are used later on the server to recreate the items of each listbox taking into consideration any changes done on the client side.

The OnPreRender method adds the above JavaScript in the form of an included JavaScript file that should be present in the same directory of the page that is using this composite control.

Listing 16

protected override void OnPreRender(EventArgs e)
{
  base.OnPreRender(e);
 
// Add the bhMover javascript file
  StringBuilder script = new StringBuilder();
  string EOL = Environment.NewLine;
  script.Append("<script language='javascript' src='bhMover_js.js'></script>" +
    EOL);
 
// Register the JavaScript file that includes utility method
  if (!Page.IsClientScriptBlockRegistered("bhMoverScript"))
    Page.RegisterClientScriptBlock("bhMoverScript", script.ToString());
}

OnDataBinding

This method has been overridden to bind the two listboxes to their data sources and set their DataTextField and DataValueField respectively as shown in the code below.

Listing 17

protected override void OnDataBinding(EventArgs e)
{
  if (this.leftListDataSource != null)
    this.leftListBox.DataSource = this.leftListDataSource;
  if (this.rightListDataSource != null)
    this.rightListBox.DataSource = this.rightListDataSource;
 
  this.leftListBox.DataValueField = this.LeftListDataValueField;
  this.leftListBox.DataTextField = this.LeftListDataTextField;
 
  this.rightListBox.DataValueField = this.RightListDataValueField;
  this.rightListBox.DataTextField = this.RightListDataTextField;
 
  this.leftListBox.DataBind();
  this.rightListBox.DataBind();
 
  this.ChildControlsCreated = false;
}           

Special ViewState consideration

In some of the public properties we have used in this control mainly the simple data type properties:

LeftListDataValueField

RightListDataValueField

LeftListDataTextField

RightListDataTextField

We have used the nice technique of preserving their member variables in the ViewState in the “set” section of the properties.

In other public properties like the LeftListBoxStyle for example, since we had only a “get” section, there was no way to use the ViewState technique as used by other properties. Therefore we had to implement three methods that we have already mentioned above:

SaveViewState

Listing 18

protected override object SaveViewState()
{
// State of the whole composite control
  object[]state = new object[4];
 
  state[0] = base.SaveViewState();
  state[1] = ((IStateManager)this.leftListBoxStyle).SaveViewState();
  state[2] = ((IStateManager)this.rightListBoxStyle).SaveViewState();
  state[3] = ((IStateManager)this.buttonChooserStyle).SaveViewState();
 
  return state;
}

First of all, we preserver the ViewState of the entire composite control, after that we save the state for each of the Style member variables that we have used in the “get” sections of the public properties. Notice that the Style class implements the IStateManager as protected methods, and hence, we should first of all cast the style variable to IStateManager, and then access its SaveViewState method. We repeat the same for the other style member variables.

LoadViewState

We should also implement the LoadViewState method that will be used to retrieve the style local members when needed.

Listing 19

protected override void LoadViewState(object savedState)
{
  object[]state = null;
  if (savedState != null)
  {
    state = (object[])savedState;
    base.LoadViewState(state[0]);
    ((IStateManager)this.leftListBoxStyle).LoadViewState(state[1]);
    ((IStateManager)this.rightListBoxStyle).LoadViewState(state[2]);
    ((IStateManager)this.buttonChooserStyle).LoadViewState(state[3]);
  }
}

The reverse of what we have done in the SaveViewState is done in this method. We load back the state of each style member variable by calling each LoadViewState method.

TrackViewState

Finally, the TrackViewState method should be implemented to make sure each style member variable is tracking its own ViewState.

Listing 20

protected override void TrackViewState()
{
  base.TrackViewState();
  if (this.leftListBoxStyle != null)
    ((IStateManager)this.leftListBoxStyle).TrackViewState();
 
  if (this.rightListBoxStyle != null)
    ((IStateManager)this.rightListBoxStyle).TrackViewState();
 
  if (this.buttonChooserStyle != null)
    ((IStateManager)this.buttonChooserStyle).TrackViewState();
}

For each style member variable we check, if the variable is not null then call its TrackViewState.

What made the life easy here is that Style class itself implements IStateManager, so the role is nothing but calling those methods on each member variable.

To sum up this section, we had to handle the ViewState of the member variables used in the public properties that were part of their “get” section and had no other way to preserve their state in the ViewState like we have done to all other simple data type properties that included both “get” and “set” sections.

Some other properties have not been preserved in the ViewState as you might ask yourself. You are absolutely right, but I did not mention anything about those properties to make you think more of why I have done this!

If you look at the LeftListBoxDataSource property, it uses internally in the “set” section a member variable to hold the DataSource of a specific listbox. The data source will be eventually assigned to the data source of the listbox, and since the listbox preserves its items in the ViewState internally there was no need to preserve those member variables in the ViewState and do the double work!

The same applies on the LeftItems and RightItems properties which represent nothing but the items of each listbox which are also preserved internally into the ViewState.


View Entire Article

User Comments

Title: waov   
Name: almora
Date: 2011-04-26 4:09:13 AM
Comment:
Nice info here Bilal, thanks dude.
Title: How to get right side selected value   
Name: Kusum
Date: 2008-05-27 8:12:08 AM
Comment:
Hii,
Can anybody help me that how can I access the right side selected values at server side? I want to save right side selected value in database, so on click of save button, how can I access the right hand side list box item at server? Any property?
- Kusum
Title: Error on Mover control while implementing   
Name: Sandeep
Date: 2008-03-23 11:17:58 AM
Comment:
Hello,
I am getting below error while implementing mover control:

"Control 'leftlstbox' of type 'xListBox' must be placed inside a form tag with runat=server."

Please help me, even through below forum I have tried Peruri Srinivasulu steps.

Thanks,
Sandeep
Title: Re: Vessel   
Name: Bilal Haidar
Date: 2007-11-15 1:25:39 AM
Comment:
Vessel,
I guess, I have used the "," for joining elements. You can change it to something like "|" or "$" or any other not commonly used character.

Thanks
Title: Re: Vessel   
Name: Bilal Haidar
Date: 2007-11-15 1:22:27 AM
Comment:
Hello Vessel,
Are you at the ACK team?

Regards
Title: Ajax version?   
Name: Vessel
Date: 2007-11-14 4:23:30 PM
Comment:
Hi Bilal Haidar

How about to make similar Ajax-control and contribute it with Ajax Control Toolkit.

I think there will be use for it ;)

-Vessel
Title: Index was outside the bounds of the array.   
Name: Vessel
Date: 2007-11-14 3:59:26 PM
Comment:
If list item have comma ',' inside, control will crash, maybe your problem too purusingh.

Crash-item: "Choose me, Buddy"
Okay-item: "Choose me Buddy"

So, while populating list-items sanitize commas out.
Title: Re: purusingh   
Name: Bilal Haidar
Date: 2007-10-15 11:32:24 AM
Comment:
Hello,
Can you please show me some code where this exception is generated?
I have been using this control for a while and haven't got such an exception!

Just to note, such an error occurs when you are trying to access elements inside the control that are not present i.e. accessing with index greater than the max. index inside the control!

Thanks
Title: Good Article   
Name: purusingh
Date: 2007-10-15 5:33:41 AM
Comment:
Every thins is ok, we are using the control but there is some problem I am getting following error


Index was outside the bounds of the array.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.IndexOutOfRangeException: Index was outside the bounds of the array.

Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace:


[IndexOutOfRangeException: Index was outside the bounds of the array.]
xListControls.xListBox.System.Web.UI.IPostBackDataHandler.LoadPostData(String postDataKey, NameValueCollection postCollection) +1486
System.Web.UI.Page.ProcessPostData(NameValueCollection postData, Boolean fBeforeLoad) +723
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +4798
Title: good articles   
Name: good articles
Date: 2007-09-24 3:54:34 AM
Comment:
very good!!!
Title: Re: Vessel   
Name: Bilal Hadiar [MVP]
Date: 2007-04-04 1:53:56 AM
Comment:
Hello Vessel,
Can you illustrate more please?

Thanks
Title: Index was outside the bounds of the array.   
Name: Vessel
Date: 2007-03-24 2:38:07 PM
Comment:
MoverControl can't stand with ',' in ListItemValue because it uses comma as inside delimiter.

Array-handling crashes with 'Index was outside the bounds of the array.'

Took awhile to find it out ;)
Title: This is not working for me   
Name: Daniel
Date: 2007-03-21 10:10:14 AM
Comment:
IM loading the left list to a db object but when I click on the arrow to move it I notice that the page load happens several times and doesnt move teh object.
Title: error when programatically creating control   
Name: Peruri Srinivasulu
Date: 2007-02-14 10:24:59 PM
Comment:
{"Control 'ctl02_leftlstbox' of type 'xListBox' must be placed inside a form tag with runat=server."}

when placed the following code in page init.


protected override void OnInit(EventArgs e)
{
base.OnInit(e);

MoverControl.bhMover moveR1 = new bhMover();
this.Controls.Add(moveR1);
}
Title: Good Work   
Name: Tim Digital
Date: 2007-02-09 8:42:19 AM
Comment:
Excellent Work Bilal,

Fluent consulting also has a pretty neat control (ListTransfer)that I've been using in my projects :-

http://www.fluentconsulting.com/components/Fluent.ListTransfer/
Title: Good article   
Name: vishal rathod
Date: 2007-01-29 9:01:02 AM
Comment:
Nice article.
and exactly what i want.
Title: Nice Work as Usual   
Name: El Chokr
Date: 2007-01-09 3:05:45 PM
Comment:
Nice Work as Usual
Title: nice   
Name: pranot
Date: 2007-01-08 4:31:10 AM
Comment:
really nice and exactly what i was looking for
i just spent 2 days writing something similar but using client-side javascript to move items.
also, it would be helpful if you could provide some screenshots, and a working demo which can be viewed online.
Title: Good Article   
Name: Haissam Abdul Malak
Date: 2007-01-08 2:51:47 AM
Comment:
Good article, keep up the good work

Best Regards

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2021 ASPAlliance.com  |  Page Processed at 2021-12-08 11:22:18 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search