In a recent web project I had a requirement for incremental searching - for each character entered into a text box we need to call a search function on the server and update a list displayed in a div in the browser. ASP.Net and a bit of Ajax makes this task quite simple.
Calling The Server-Side Method On Every Key Press
Because I want the control to behave exactly as the standard TextBox,I
started by deriving my custom control from the TextBox class.
Our first hurdle is that by default, the TextBox control OnTextChanged method only gets called when the TextBox loses focus. In order to change this behaviour we can handle the javascript onkeyup event and use it to raise the OnTextChanged event on the server. So how do we fire the server side event from our client script? Essentially, all we do is cause a form submit in our javascript and indicate which server method to call. This is done by implementing the IPostBackEventHandler interface.
IPostBackEventHandler is used to enable our control to raise and handle form submissions. It's only method to implement is RaisePostBackEvent which is called as a result of special javascript output by our control.
The ClientScriptManager.GetPostBackEventReference(...) method provides us with this javascript. It has a number of overloads that vary how the resultant script is defined - I have used the control itself (the sender) and a constant. These will be passed as arguments to RaisePostBackEvent and we can use the constant to determine which server side function to call.
The javascript is relatively straight forward - it includes logic to only fire the event once the user has stopped typing, as well as logic to determine that the text has actually changed (non-character key presses shouldn't cause a TextChanged event).
Here's the guts of it:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
string clientScript = String.Format(@"
var {0}t; //our timeout
//if control keys (esc, tab, enter) are pressed we dont want to do a post
//use this global var to determine if the text has actually changed
var {0}currentText = '';
function {0}HandleKeyUp(text)
{{
if({0}currentText != text)
{{
{0}currentText = text;
if({0}t != undefined)
clearTimeout({0}t);
t = setTimeout('{1}', 500);
}}
}}
",
ClientID,
Page.ClientScript.GetPostBackEventReference(this, POSTBACK_EVENT_TEXT_CHANGED)
);
if (!Page.ClientScript.IsClientScriptBlockRegistered(ClientID))
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), ClientID, clientScript, true);
}
...
public void RaisePostBackEvent(string eventArgument)
{
switch (eventArgument)
{
case POSTBACK_EVENT_TEXT_CHANGED:
OnTextChanged(new EventArgs());
break;
}
}
I have added the onkeyup javascript handler to the text box in the OnPreRender event of the control and disabled autocomplete.We leave the rest of the rendering to the base TextBox class.
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
this.Attributes.Add("onkeyup", ClientID + "HandleKeyUp(this.value);");
this.Attributes.Add("autocomplete", "off");
}
So now we have a TextBox control that causes a post back on every key press. This is all well and good but without the use of the .Net Ajax controls, would not be very functional in any web site. To make the post backs un-noticed we can now hook up an update panel to the OnTextChanged event of our custom TextBox control:
protected void txtSearchValue_TextChanged(object sender, EventArgs e)
{
lblResults.Text = txtSearchValue.Text;
}
This is a very basic example that just updates the label with the text box value but you can implement you're own search and update logic however you like.
This is not the most efficient implementation due to a full post back being performed (the page goes through its full life cycle on every post back) although I haven't had any issues with it. Another option might be to implement the ICallbackEventHandler interface which eliminates the need for an UpdatePanel but IPostBackEventHandler seemed more suited for this function. I might give the alternative a go over the next couple of weeks.
If anyone has any ideas on a better way to represent the control in client script I'd like to hear them. I was thinking of implementing a client representation to fit into the ASP.Net Ajax client-side framework as outlined here but it seemed a bit overkill.
IncrementalTextBox.zip source code is attached below for download.
Jimmy
IncrementalTextBox.zip (983.00 bytes)