Saturday, February 15, 2014

Knockout & AJAX Autocomplete with MVC4

The other day I was trying to get a auto-complete working in my web application.(MVC4)

What I had to design was a simple lookup tool. you know, like google.

As usual, I did not and never will try to reinvent the wheel. so, these are what I went through..and tried...and failed.

The jsfiddle ones are working well, but not in my case. I didn't have much time to fix the issues so I kept on looking.

Then I found this solution.. It also uses a knockout binding handler. Just in case if you are wondering, I found it through here on this SOF question. I'm just gonna embed the fiddle here.

Even though it worked perfectly well, It really wasn't what i was expecting, So I kinda changed the original code a little bit. I will highlight what I changed. It was all has to do with styling and responding to mouse/keyboard events.

Orignla author:

var viewModel = new SearchResultViewModel();
var focusedItem = 0;
var oldItem = "";

ko.bindingHandlers.autoComplete = {

    init: function (element, valueAccessor, allBindingsAccessor) {
        //We want valueupdate on keydown
        allBindingsAccessor().valueUpdate = 'afterkeydown';
        //get the settings from the html control
        var settings = allBindingsAccessor().settings || {};

        //create ul that will be added to the DOM

        //var ul = '<ul class="knockout-autoComplete" data-bind="foreach: searchResults"><li data-bind="text: label, click : $$data), css : {\'autocomplete-selected\' : focus}"></li></ul>';

        var li = '<li class="ui-menu-item" role="menuitem" data-bind="text: label, click : $$data), css : {\'autocomplete-selected\' : focus}"></li>';

        var ul = '<ul style="z-index: 1; display: block;max-height:200px;overflow:auto" class="ui-autocomplete ui-menu ui-widget ui-widget-content ui-corner-all" aria-activedescendant="ui-active-menuitem" role="listbox" data-bind="visible:searchResults().length> 0, foreach: searchResults">' + li + '</ul>';

        //Add ul to DOM after input (searchbox)

        //Add blur event for cleaning searchresults
        $(element).blur(function () {
            //Timeout function to be able to Click on a item before the lists dissapear
            setTimeout(function () {
                //clear searchresults
                //clear elements value
            }, 150);

        //Key navigation
        ko.utils.registerEventHandler(element, 'keydown', function (evt) {
            var item;

            var noOfListItems = viewModel.searchResults().length - 1;
            var curListItem = focusedItem;

            switch (evt.keyCode) {
                //Esc clicked blur element
                case 27:



                    //9 TAB key

                case 9:

                    item = viewModel.searchResults()[focusedItem];


                    focusedItem = 0;

                    //clear searchresults



                    //38 Key upp

                case 38:


                    // Decrement the selection by one, unless that will be less than zero, then go to the last option

                    focusedItem = (curListItem - 1 < 0) ? noOfListItems : curListItem - 1;

                    //40 keydown
                case 40:


                    // Increment the selection by one, unless that will be more than the number of options, then go to the first option

                    focusedItem = (curListItem + 1 > noOfListItems) ? 0 : curListItem + 1;


                case 13:

                    //Enter select item with index of focusedItem

                    item = viewModel.searchResults()[focusedItem];


                    focusedItem = 0;

                    //clear searchresults





        function reserFocusStatus() {
            for (var i = 0; i < viewModel.searchResults().length; i++)
        //Set element in viewModel
        //set selectFunction of item
        //set updatedItemValue & updatedItemLabel of item
        viewModel.setUpdatedItem(settings.updatedItemValue, settings.updatedItemLabel);

        //Bind ul with viewModel
        ko.applyBindings(viewModel, $(element).next('ul')[0]);
        ko.bindingHandlers.value.init(element, valueAccessor, allBindingsAccessor);


    update: function (element, valueAccessor, allBindingsAccessor) {
        var value = valueAccessor();
        var settings = allBindingsAccessor().settings || {};

        //Remove old results

        //Proceed with updating only if the previous and current values are different
        if (oldItem !== value())
            if (value().length > 0) {
                //Only do search if input value is longer then 0
                //Ajax call to server
                    url: settings.url + value(),
                    contentType: 'application/json; charset=UTF-8',
                    //data: { name: value },
                    success: function (data) {
                        //Map data that is returned from server
                        var mapped = ko.utils.arrayMap(data, function (item) {
                            return { label: item.Name, value: item.Postcode, focus: ko.observable(false) };

                        //put the mapped data to the viewModels searchresults array

        ko.bindingHandlers.value.update(element, value, allBindingsAccessor);



//Autocomplete Viewmodel

function SearchResultViewModel() {

    var self = this;

    //array for searchresults

    self.searchResults = ko.observableArray([]);

    //bound element (for cleaning value during select)

    self.element = null;

    //function that will be triggerd on select

    self.selectFunction = null;

    //function that will be triggerd on select

    self.updatedObservableVal = null;

    self.updatedObservableLbl = null;

    self.setSelectFunction = function (selectFunction) {

        if (selectFunction) {
            self.selectFunction = selectFunction;

    self.setUpdatedItem = function (updatedValueObservable, updatedLabelObservable) {

        if (updatedValueObservable) {

            self.updatedObservableVal = updatedValueObservable;


        if (updatedLabelObservable) {

            self.updatedObservableLbl = updatedLabelObservable;



    self.setElement = function (element) {

        if (element) {

            self.element = element;



    //When navigating up and down toggle what item is selected and not

    self.toggleSelected = function (index) {

        var item = viewModel.searchResults()[index];

        if (item && !item.focus()) {


    //item click function = function (data) {


        try {

            oldItem = data.label;




        } catch (e) {



        //trigger custom select function




The KeyUP, KeyDown events handling, I found them through here.