Friday, June 13, 2008

Databinding the value property of an AJAX Autocomplete Extender in a ListView - .NET 3.5, C#

OK. New problem. Suffice it to say I like to use databinding if at all possible, but if you want to do anything tricky you need to be creative. The problem I've run across this time is this. I've got a ListView that is using an AJAX Autocomplete Extender in the Insert template field. The extender is set to update the text of a textbox, but what if I want to use the value element of the extender too? In my example I want users to be able to type in an employee name, and then when they select the employee name they are looking for, the employee ID gets stored to the DB too. Here's what the template field looks like:

<InsertItemTemplate>
<ajax:AutoCompleteExtender ID="aceEmployeeName" runat="server" TargetControlID="txtEmployeeName" ServiceMethod="GetCompletionList"
ServicePath="Default.aspx" FirstRowSelected="true" OnClientItemSelected ="EmployeeNameSelected" CompletionListCssClass="autocomplete_completionListElement"
MinimumPrefixLength="4" CompletionInterval="1000" EnableCaching="true" CompletionSetCount="12">
</ajax:AutoCompleteExtender>
Employee Name:
<asp:TextBox ID="txtEmployeeName" runat="server" Text='<%# Bind("EmpName") %>' />
<asp:HiddenField ID="hfEmpID" runat="server" OnPreRender="hfEmpID_PreRender" Value='<%# Bind("EmpID") %>'/>
</InsertItemTemplate>

Here's the ServiceMethod in the code behind that feeds the autocomplete extender (obviously this is rather hard-coded for now):

[WebMethod]
[System.Web.Script.Services.ScriptMethod]
//used by the ajax auto complete control
public static string[] GetCompletionList(string prefixText, int count)
{
List items = new List(3);
items.Add(AjaxControlToolkit.AutoCompleteExtender.
CreateAutoCompleteItem("Name1234", "1234"));
items.Add(AjaxControlToolkit.AutoCompleteExtender.
CreateAutoCompleteItem("Name2345", "2345"));
items.Add(AjaxControlToolkit.AutoCompleteExtender.
CreateAutoCompleteItem("Name3456", "3456"));

return items.ToArray();
}

But, how do I get the selected value of the autocomplete extender stored into the databound hidden field? It's tricky, but it doesn't take a lot of code. I'm going to use client side javascript. First of all I need to create a hidden field outside of the ListView to store the ID of the hidden field within the ListView. The reason I need to do this is because I have no idea what the ID is going to be at run time since I can have multiple rows in the listview, and the server names them dynamically at run time.

<asp:HiddenField ID="hfInsertHiddenFieldID" runat="server" />

Then I tell the hidden field within the list view to store its name there on PreRender:

<asp:HiddenField ID="hfEmpID" runat="server" OnPreRender="hfEmpID_PreRender" Value='<%# Bind("EmpID") %>'/>

Then, the codebehind for this event looks like this:

protected void hfEmpID_PreRender(object sender, EventArgs e)
{
//store the name of the databound hidden field on the client side
HiddenField hf = sender as HiddenField;
string strHiddenFieldClientID = hf.ClientID;
hfInsertHiddenFieldID.Value = strHiddenFieldClientID;
}

OK, so far so good. Now all that's left to do is to tell the autocomplete extender to run a piece of javascript on the client side when a new name is selected (you may have already noticed this in the first snippet above).

OnClientItemSelected ="EmployeeNameSelected"

Now, in the javascript I can find out the name of the hiddenfield in the ListView that is bound to the EmployeeID and change its value based on the selected value of the autocomplete extender:

function EmployeeNameSelected(source, eventArgs)
{
//find out the name of the databound hidden field and then store the selected value from the autocomplete extender
var hf = document.getElementById("ctl00_body_hfInsertHiddenFieldID");
var strHiddenFieldID = hf.getAttribute("value");
var targetElement = document.getElementById(strHiddenFieldID);
targetElement.value = eventArgs.get_value();
}

That's it! (I think) Simple? Not really, but now I can keep the page databound and my project still has 0 stored procedures :)