Monday, July 23, 2012

MVC AutoComplete template


Get the Id/Key of the selected item and highlight the inputed text in the list of selections.
The below works with MVC3 or MVC4 using Razor view engine

I will use JQuery to enhance the UI of the app, so here is my autocomplete that can be used anywhere in your MVC project and more than once in a page.


First configure your scripts to include JQuery and JQuery UI, etc in the layout page, so the autocomplete can work all over the application in any view.
        @Scripts.Render("~/bundles/jquery")
        @Scripts.Render("~/bundles/jqueryui")
        @Scripts.Render("~/bundles/jqueryval")
I added the autocomplete java code in the ("~/bundles/jqueryval") bundle, at the App_Start as below:
  public static void RegisterBundles(BundleCollection bundles)
        {
            //TODO: On production this will be true
            BundleTable.EnableOptimizations = false;

            bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-1.*"));

            bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
                        "~/Scripts/jquery-ui*"));

            bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                        "~/Scripts/jquery.unobtrusive*",
                        "~/Scripts/jquery.validate*",
                        "~/Scripts/ControlsJScript.js"));
           .....
           .....

This ControlJscript.js file will be my stander control code for all the templates I need, currently its populated with the autocomplete code that will wire the autocomplete and the highlighted behavior

$(document).ready(function () {

    //set autocomplete with hieghlit text
    $(":input[data-autocomplete]").each(function () {
        $(this).autocomplete({
            source: $(this).attr('data-autocomplete'),
            minLength: 2,
            change: function (event, ui) {
                var TargetId = "#" + $(this).attr('data-target');    //find Target Control Id
                if (ui.item == null) {$(TargetId).attr('value', "  ");} //Set the value to the selected value
                else { $(TargetId).attr('value', ui.item.id);}
            }
        }).data("autocomplete")._renderItem = function (ul, item) {
            var term = $(this)[0].term;
            var index = item.label.toLowerCase().indexOf(term.toLowerCase());

            return $("<li>li>")
                .data("item.autocomplete", item)
                .append("<a>" + item.label.substring(0, index)
                      + "<font class='AutoFont'>" + item.label.substring(index, index + term.length)
                      + "font>" + item.label.substring(index + term.length) + "a>")
                .appendTo(ul);
            //&#8205 script joiner
            //u is the solution
        };
    });
});

 Note: AutoFont is a css class that specify the style for the input text
Second set Autocomplete View Model class in your model folder or anywhere you like.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace ADM.Ntrasal.UI.Web.Browser.Models
{
    public class AutoCompleteViewModule
    {
        public AutoCompleteViewModule()
        {
            //default values
            AutocompleteCount = 10;
            Type = AutoCompleteTypes.EmployeeNames;
        }
        public string Id { get; set; }
        public string Name { get; set; }
        public string Field { get; set; }
        public int AutocompleteCount { get; set; }
        public AutoCompleteTypes Type { get; set; }

        public enum AutoCompleteTypes
        {
            EmployeeNames,
            OrgChart,
            Sources
        }
    }
}

I prefer to add the controller logic to an api controller, but for this example (for backwards compatibility) im adding the managing code to a normal controller as below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using ADM.Ntrasal.Business;
using ADM.Ntrasal.Data;
namespace ADM.Ntrasal.Web.UI.Browser.Controllers
{
    public class AutoCompleteController : Controller
    {
         private readonly IRepositories Repositories;
         public AutoCompleteController()
        {  //Prefare to use Ioc but for simplicity
            this.Repositories = new Repositories();
        }
        //
        // GET: /AutoComplete/

        public ActionResult EmployeeNames(string term)
        {
            var count = 10;
            var UserProfileRepostery = this.Repositories.Create<UserProfile>();


            var result = UserProfileRepostery.GetAll().Where(x => x.Employee_Name.ToLower().Contains(term.ToLower()))
              .Take(count).Select(x =>
                  new
                  {
                      label = x.Employee_Name,
                      id = x.UserID
                  });
            return Json(result, JsonRequestBehavior.AllowGet);
        }

    }
}
Note: the idea is to return the text with its key, so when the user selects something you get the id/key directly.

Add the below as a template for the Autocomplete
@model ADM.Ntrasal.UI.Web.Browser.Models.AutoCompleteViewModule
 <input type="text"  name="AutocompleteTextBox"
     data-target='@Model.Field'
     data-autocomplete="@Url.Action(@Model.Type.ToString(), "AutoComplete", new { count = @Model.AutocompleteCount })"
     value='@Model.Name' />

@Html.Hidden(@Model.Field)

Add it to your view whenever an autocomplete is neededset, for a test view, make the view a strong type with AutoCompleteViewModule as below.
@model ADM.Ntrasal.UI.Web.Browser.Models.AutoCompleteViewModule
  @Html.EditorFor(x=>@Model)

I did’t add a code sample because this is a part of a large project, but if anybody is interested, I can make a quick project and upload it.

No comments:

Post a Comment