Code Voyeur
RSS
Data Access Languages MVC ORM About Roadmap Contact Site Map RSS Sample Code Presentations Snippets dll Hell .net Rate My Snippet

ASP.NET MVC HtmlHelper Extensions for jQuery AutoComplete

ASP.NET MVC’s HtmlHelper class has a number of useful methods for abstracting the creation of simple HTML elements. Though most methods simply output HTML that could just as easily been coded by hand, HtmlHelper aids in common data binding tasks by matching field name to Model properties.

This latter feature is the motivation for the jQuery AutoComplete extension methods found in this article. It is possible to use Html.TextBox to render an input field that is later provided the auto complete functionality. However, this requires shifting in and out of rendering by helper extensions. A more pragmatic problem is avoiding redundant JavaScript (at least prior to rendering).

The sample project for this article includes both required jQuery scripts. References to these files are found in Site.Master (as is a CSS file with auto complete styles).

 <script language="javascript" src="<%= ResolveUrl("~/Content/Scripts/jquery.js") %>"></script>
<script language="javascript" src="<%= ResolveUrl("~/Content/Scripts/jquery.autocomplete.js") %>"></script>
The AutoComplete plugin requires nothing special be done to the HTML of the textbox.
<input id="City" name="City" style="width:200px;" type="text" value="" />

<script language='javascript'>
$('#City').autocomplete('/Home/Cities', {
 minchars : 3
 });
</script>
The script above converts the textbox with ID 'City' to an autocomplete textbox. The minChars property in the object argument is the number of characters that must be typed before the AJAX routine is called that retrieves the list of cities from the provided URL (/Home/Cities).

Two extension methods allow for a textbox to be converted to an auto complete textbox. The first method is AutoCompleteTextBox, which behaves much like the standard TextBox extension method.

<%= Html.AutoCompleteTextBox("City", "CityID", new { style = "width:200px;" })%>
However, the implementation (overloaded in the sample) actually calls both Html.TextBox and Html.Hidden.
public static string AutoCompleteTextBox(this HtmlHelper html, string textBoxName, string fieldName,
                              string selectedText, object selectedValue, object htmlAttributes) {
   
    return string.Format("{0} {1}", html.TextBox(textBoxName, selectedText, htmlAttributes),
                                                html.Hidden(fieldName, selectedValue));
}

public static string AutoCompleteTextBox(this HtmlHelper html, string textBoxName, string fieldName, object htmlAttributes) {

    return string.Format("{0} {1}", html.TextBox(textBoxName, null, htmlAttributes),
                                                html.Hidden(fieldName));
}
The purpose of the TextBox call should be clear. The Hidden call is used to allow the autocomplete textbox to behave like a drop down list, which has both selected text and a selected value. The call above to AutoCompleteTextBox will outut a textbox and a hidden field.
<input id="City" name="City" style="width:200px;" type="text" value="" />
<input id="CityID" name="CityID" type="hidden" value="" />
The other method that is needed is InitializeAutoComplete. This extension method will output necessary jQuery to unobtrusively modify the behavior of the textbox.
<%= Html.InitializeAutoComplete("City", "CityID", Url.Action("Cities"), new { delay = 100, minchars = 3 }, true)%>
There is a bit of intentional redundancy with this call in that the names of the fields are required as they were with the call to AutoCompleteTextBox. The separation into multiple methods is to have finer grained control over where the script output appears.
  public static string InitializeAutoComplete(this HtmlHelper html, string textBoxName, string fieldName, string url,
                                                                        object options, bool wrapInReady) {

    StringBuilder sb = new StringBuilder();
    if (wrapInReady) sb.AppendLineFormat("<script language='javascript'>");

    if (wrapInReady) sb.AppendLineFormat("$().ready(function() {{");
    sb.AppendLine();
    sb.AppendLineFormat("   $('#{0}').autocomplete('{1}', {{", textBoxName.Replace(".", "\\\\."), url);

    PropertyInfo[] properties = options.GetType().GetProperties();

    for (int i = 0; i < properties.Length; i++) {
        sb.AppendLineFormat("   {0} : {1}{2}",
                                properties[i].Name,
                                properties[i].GetValue(options, null),
                                i != properties.Length - 1 ? "," : "");
    }
    sb.AppendLineFormat("   }});");
    sb.AppendLine();
    sb.AppendLineFormat("   $('#{0}').result(function(e, d, f) {{", textBoxName.Replace(".", "\\\\."));
    sb.AppendLineFormat("       $('#{0}').val(d[1]);", fieldName);
    sb.AppendLineFormat("    }});");
    sb.AppendLine();
    if (wrapInReady) sb.AppendLineFormat("}});");
    if (wrapInReady) sb.AppendLineFormat("</script>");
    return sb.ToString();

}

public static string InitializeAutoComplete(this HtmlHelper html, string textBoxName, string
                                                            fieldName, string url, object options) {
    return InitializeAutoComplete(html, textBoxName, fieldName, url, options, false);
}
The details of the InitializeAutoComplete method are fairly straightforward. The jQuery code formatted in a StringBuilder (AppendLineFormat is a project included extension method). The loop within InitializeAutoComplete iterates over the properties of the options object (anonymous typically) and renders them as the JavaScript options object needed by the autocomplete call.

The InitializeAutoComplete method also outputs a handler for the result method of the autocomplete object. This function is the handler for the event raised when an item is selected. In this case, the value of the hidden field is set to the value associated with the selected text.

The boolean option to wrapInReady allows the script blocks to be wrapped in jQuery's ready function (adding the code to window.load). It is useful to turn this off when the extension method will be called from an existing ready block.

The output of the City search is:



    <script language='javascript'>
$().ready(function() {

   $('#City').autocomplete('/Home/Cities', {
   delay : 100,
   minchars : 3
   });

   $('#City').result(function(e, d, f) {
       $('#CityID').val(d[1]);
    });

});
Finally, the action that returns data for the autocomplete textbox needs to return source data, with each item on its own line. To have text/value pairs, data are separated by a |.
public ActionResult Cities(string q) {

    //i is used as a pretend primary key
    for (var i = 0; i < _cities.Length; i++ ) {
        if (_cities[i].StartsWith(q, StringComparison.OrdinalIgnoreCase))
            Response.Output.Write("{0}|{1}\r\n", _cities[i], i);
    }

    return new CancelViewResult();
   
}

Download Sample Project

References

AutoComplete plugin
Article Posted: Wednesday, November 12, 2008

Leave a Comment

Shout it

Contact Code Voyeur about this article.