Navigation


RSS: Matt Pavey RSS Feed



Saturday, September 15, 2007 @ 10:46 am,C-Sharp,Matt Pavey

We've all ran into the issue with using a TextArea control or a TextBox control with MultiLine set to True to allow the user to enter a long description or comments, etc. Specifically the problem is that the built in MaxLength attribute that we are so used to using for a standard TextBox does not work if you are using MultiLine=True.
 
There are a variety of ways to handle this. In some cases I've simply dropped in a line or two of JavaScript and added some onkeypress events to analyze the number of characters the user has typed in and when they reach the maximum to just stop them form entering anything else. That approach works fine, but it involves having to worry about making sure you copy the JavaScript to the right pages and worry about setting up your onkeypress event for each item you want to limit the length of, etc. When researching this I came across a better solution that is pretty simple, yet it's more generic and easier to reuse throughout your projects.
 
The idea is essentially just extending the functionality of the System.Web.UI.WebControls.TextBox control.

public
class TextArea : System.Web.UI.WebControls.TextBox
{
    
protected override void OnPreRender(EventArgs e)
    
{
         
if (MaxLength > 0 && TextMode == System.Web.UI.WebControls.TextBoxMode.MultiLine)
         
{
              
// add javascript handlers for paste and keypress
              
Attributes.Add("onkeypress", "doKeypress(this);");
              
Attributes.Add("onbeforepaste", "doBeforePaste(this);");
              
Attributes.Add("onpaste", "doPaste(this);");

              
// add attribute for access of maxlength property on client-side
              
Attributes.Add("maxLength", MaxLength.ToString());

              
// register client side include - only once per page
              
if(!Page.ClientScript.IsClientScriptIncludeRegistered("TextArea"))
              
{
                    
Page.ClientScript.RegisterClientScriptInclude("TextArea", ResolveClientUrl("~/Common/TextArea.js"));
               
}
          
}

         
base.OnPreRender(e);
     }
}
 
The main thing to recognize is how it's adding the onkeypress, onbeforepaste, and onpaste events to execute JavaScript functions on the client.
 
Another nice thing about this code is that you don't have to worry about putting your JavaScript code everywhere you want to use this functionality. You can simply keep it centralized in a .js file and place it in a folder with your other JavaScript files.
 
In our case we have the necessary JavaScript in TextArea.js, which is as follows:
 
// Keep user from entering more than maxLength characters
function doKeypress(control)
{
    
maxLength = control.attributes["maxLength"].value;
    
value = control.value;
    
if(maxLength && value.length > maxLength-1)
     {
         
event.returnValue = false;
          maxLength = parseInt(maxLength);
    
}
}
 
// Cancel default behavior
function doBeforePaste(control)
{
    
maxLength = control.attributes["maxLength"].value;
    
if(maxLength)
    
{
         
event.returnValue = false;
    
}
}
 
// Cancel default behavior and create a new paste routine
function doPaste(control)
{
    
maxLength = control.attributes["maxLength"].value;
    
value = control.value;
    
if(maxLength)
     {
         
event.returnValue = false;
         
maxLength = parseInt(maxLength);
         
var oTR = control.document.selection.createRange();
         
var iInsertLength = maxLength - value.length + oTR.text.length;
         
var sData = window.clipboardData.getData("Text").substr(0,iInsertLength);
         
oTR.text = sData;
    
}
}
 
Now to use it you can simply register the control on your page like so:
 
<%@ Register TagPrefix="WebControls" Namespace="Test.WebControls" Assembly="Test.WebControls" %>
 
Then you can use the control as follows:
 
<WebControls:TextArea ID="txtTest" TextMode="MultiLine" MaxLength="500" Rows="10" Columns="75" runat="server" />
 
The value you specify for the MaxLength attribute will determine the maximum number of characters the user will be able to enter.
 
Of course the user can disable JavaScript, so it's always a good idea to test for that accordingly or make sure to validate on the server side as well.


Saturday, September 15, 2007 @ 10:40 am,C-Sharp,Matt Pavey

Most of use the <asp:DropDownList> control on a regular basis, which for almost all cases works as expected; however, if you have any specific styles set for items in the drop down list you probably have noticed that they get lost on a postback. The data itself and the selected value obviously stay intact during the postback, but if you have any special background colors or font colors or any styles applied they don't get maintained on the postback.
 
I ran into this particular situation several months back because I was representing data in a drop down list in a master/detail type format. To make it visually apparent how the data was structured I show the parent category with a gray background color and I show the child categories indented with a white background color. This all looked fine initially; however, after the post back the gray background color was lost.
 
To solve the problem I utilized a custom drop down list class that simply extended the base functionality of System.Web.UI.WebControls.DropDownList
 
public class DropDownListCustom : System.Web.UI.WebControls.DropDownList
{
    
protected override object SaveViewState()
    
{
          // Create an object array with one element for the DropDownListList's
         
// ViewState contents, and one element for each ListItem in skmCheckBoxList
         
object[] state = new object[this.Items.Count + 1];
         
object baseState = base.SaveViewState();

         
state[0] = baseState;

         
// Now, see if we even need to save the view state
         
bool itemHasAttributes = false;
    
          for (int i = 0; i < this.Items.Count; i++)
        
{
              
if (this.Items[i].Attributes.Count > 0)
              
{
                   
itemHasAttributes = true;
              
                   
// Create an array of the item's Attribute's keys and values
                   
object[] attribKV = new object[this.Items[i].Attributes.Count * 2];

                    
int k = 0;

                   
foreach (string key in this.Items[i].Attributes.Keys)
                  
{
                       
attribKV[k++] = key;
                       
attribKV[k++] = this.Items[i].Attributes[key];
                  
}

                  
state[i + 1] = attribKV;
               }
         
}
 
          // return either baseState or state, depending on whether or not
         
// any ListItems had attributes
         
if (itemHasAttributes)
              
return state;
         
else
              
return baseState;
     }
 
     protected override void LoadViewState(object savedState)
    
{
         
if (savedState == null) return;
 
          // see if savedState is an object or object array
         
if (savedState is object[])
         
{
               // we have an array of items with attributes
               object[] state = (object[])savedState;
              
base.LoadViewState(state[0]); // load the base state
              
              
for (int i = 1; i < state.Length; i++)
              
{
                   
if (state[i] != null)
                   
{
                        
// Load back in the attributes
                         
object[] attribKV = (object[])state[i];
                        
for (int k = 0; k < attribKV.Length; k += 2)
                             
this.Items[i - 1].Attributes.Add(attribKV[k].ToString(), attribKV[k + 1].ToString());
                    
}
                
}
           
}
          
else
               
// we have just the base state
               
base.LoadViewState(savedState);
      
}
}
 
Then to use it on your page you just need to register the object like so:
 
<%@ Register TagPrefix="WebControls" Namespace="Test.WebControls" Assembly="Test.WebControls" %>
 
Then you can use the DropDownListCustom object as follows:

<
WebControls:DropDownListCustom ID="ddlTest" runat="server" />
 
Now you'll be able to maintain any formatting in your drop down during postbacks!


Friday, September 14, 2007 @ 11:18 am,C-Sharp,Matt Pavey

This makes life much easier when you're trying to get to controls that are themselves contained within other containers, eg, a TextBox inside a DataView or DataList.
 
private Control FindControlRecursive(Control root, string id)
{
    if (root.ID == id)
    {
        return root;
    }

    foreach (Control c in root.Controls)
    {
        Control t = FindControlRecursive(c, id);
        if (t != null)
        {
            return t;
        }
    }

    return null;
}


The opinions expressed on this website are my personal opinions
and do not represent my employer's or my clients' views in any way.