Click here to Skip to main content
15,902,777 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Hi all

I am trying to do autocomplete in a textbox using knockout js

I Googled this one and found some good link and tried to implement that one.
I tried this Link

This is showing error
Uncaught SyntaxError: Unexpected token ILLEGAL
My Code
HTML
<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="head" runat="server">
    <title></title><script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js" type="text/javascript"></script>
    <script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.1.0.js"></script>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <input data-bind="jqAuto: { autoFocus: true }, jqAutoSource: myPeople, jqAutoQuery: getPeople, jqAutoValue: mySelectedGuid, jqAutoSourceLabel: 'displayName', jqAutoSourceInputValue: 'name', jqAutoSourceValue: 'guid'" />

            <hr />


            <div data-bind="text: mySelectedGuid() ? mySelectedGuid() : 'None selected'"></div>

            <hr />

            For testing setting the model value elsewhere:
            <select data-bind="options: myPeople, optionsCaption: 'select a person...', optionsText: 'displayName', optionsValue: 'guid', value: mySelectedGuid"></select>

            <hr /></div>
        <script>
            //jqAuto -- main binding (should contain additional options to pass to autocomplete)
            //jqAutoSource -- the array to populate with choices (needs to be an observableArray)
            //jqAutoQuery -- function to return choices
            //jqAutoValue -- where to write the selected value
            //jqAutoSourceLabel -- the property that should be displayed in the possible choices
            //jqAutoSourceInputValue -- the property that should be displayed in the input box
            //jqAutoSourceValue -- the property to use for the value
            ko.bindingHandlers.jqAuto = {
                init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
                    var options = valueAccessor() || {},
                        allBindings = allBindingsAccessor(),
                        unwrap = ko.utils.unwrapObservable,
                        modelValue = allBindings.jqAutoValue,
                        source = allBindings.jqAutoSource,
                        query = allBindings.jqAutoQuery,
                        valueProp = allBindings.jqAutoSourceValue,
                        inputValueProp = allBindings.jqAutoSourceInputValue || valueProp,
                        labelProp = allBindings.jqAutoSourceLabel || inputValueProp;

                    //function that is shared by both select and change event handlers
                    function writeValueToModel(valueToWrite) {
                        if (ko.isWriteableObservable(modelValue)) {
                            modelValue(valueToWrite );  
                        } else {  //write to non-observable
                            if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['jqAutoValue'])
                                allBindings['_ko_property_writers']['jqAutoValue'](valueToWrite );    
                        }
                    }
        
                    //on a selection write the proper value to the model
                    options.select = function(event, ui) {
                        writeValueToModel(ui.item ? ui.item.actualValue : null);
                    };
            
                    //on a change, make sure that it is a valid value or clear out the model value
                    options.change = function(event, ui) {
                        var currentValue = $(element).val();
                        var matchingItem =  ko.utils.arrayFirst(unwrap(source), function(item) {
                            return unwrap(inputValueProp ? item[inputValueProp] : item) === currentValue;   
                        });
            
                        if (!matchingItem) {
                            writeValueToModel(null);
                        }    
                    }
        
                    //hold the autocomplete current response
                    var currentResponse = null;
            
                    //handle the choices being updated in a DO, to decouple value updates from source (options) updates
                    var mappedSource = ko.dependentObservable({
                        read: function() {
                            mapped = ko.utils.arrayMap(unwrap(source), function(item) {
                                var result = {};
                                result.label = labelProp ? unwrap(item[labelProp]) : unwrap(item).toString();  //show in pop-up choices
                                result.value = inputValueProp ? unwrap(item[inputValueProp]) : unwrap(item).toString();  //show in input box
                                result.actualValue = valueProp ? unwrap(item[valueProp]) : item;  //store in model
                                return result;
                            });
                            return mapped;                
                        },
                        write: function(newValue) {
                            source(newValue);  //update the source observableArray, so our mapped value (above) is correct
                            if (currentResponse) {
                                currentResponse(mappedSource());
                            }
                        },
                        disposeWhenNodeIsRemoved: element
                    });
        
                    if (query) {
                        options.source = function(request, response) {  
                            currentResponse = response;
                            query.call(this, request.term, mappedSource);
                        }
                    } else {
                        //whenever the items that make up the source are updated, make sure that autocomplete knows it
                        mappedSource.subscribe(function(newValue) {
                            $(element).autocomplete("option", "source", newValue); 
                        });
            
                        options.source = mappedSource();
                    }
        
        
                    //initialize autocomplete
                    $(element).autocomplete(options);
                },
                update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
                    //update value based on a model change
                    var allBindings = allBindingsAccessor(),
                        unwrap = ko.utils.unwrapObservable,
                        modelValue = unwrap(allBindings.jqAutoValue) || '', 
                        valueProp = allBindings.jqAutoSourceValue,
                        inputValueProp = allBindings.jqAutoSourceInputValue || valueProp;
        
                    //if we are writing a different property to the input than we are writing to the model, then locate the object
                    if (valueProp && inputValueProp !== valueProp) {
                        var source = unwrap(allBindings.jqAutoSource) || [];
                        var modelValue = ko.utils.arrayFirst(source, function(item) {
                            return unwrap(item[valueProp]) === modelValue;
                        }) || {};             
                    } 

                    //update the element with the value that should be shown in the input
                    $(element).val(modelValue && inputValueProp !== valueProp ? unwrap(modelValue[inputValueProp]) : modelValue.toString());    
                }
            };

            function Person(guid, name, email) {
                this.guid = ko.observable(guid);
                this.name = ko.observable(name);
                this.email = ko.observable(email);
    
                this.displayName = ko.dependentObservable(function() {
                    return this.name() + " [" + this.email() + "]";
                }, this);
            }

            var viewModel = {
                myPeople: ko.observableArray(),
                mySelectedGuid: ko.observable("ec361d63-38ae-4ecc-ab46-6c0ef19ed3ac")
            };


            function getPeople(searchTerm, sourceArray) {
                $.ajax({
                    type: 'POST',
                    url: '/echo/json/',
                    data: {
                        json: '{}',
                        delay: .5
                    },
                    success: function(data) {
                        //fake response
                        var result = [];
                        for (var i = 0; i < 5; i++) {
                            result.push(new Person("5658ff20-f230-4176-97d1-0ac21abfdbd" + i, searchTerm + "blah" + i, searchTerm + "blah" + i + "@company.com"));
                        }
                        sourceArray(result);
                    },
                    dataType: 'json'
                });
            }


            ko.applyBindings(viewModel);​
        </script>
    </form>
</body>
</html>


Please help me to solve this problem

Thanks in advance
Posted

1 solution

Knockout Handler
JavaScript
//jqAuto -- main binding (should contain additional options to pass to autocomplete)
//jqAutoSource -- the array to populate with choices (needs to be an observableArray)
//jqAutoQuery -- function to return choices
//jqAutoValue -- where to write the selected value
//jqAutoSourceLabel -- the property that should be displayed in the possible choices
//jqAutoSourceInputValue -- the property that should be displayed in the input box
//jqAutoSourceValue -- the property to use for the value
ko.bindingHandlers.jqAuto = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        var options = valueAccessor() || {},
            allBindings = allBindingsAccessor(),
            unwrap = ko.utils.unwrapObservable,
            modelValue = allBindings.jqAutoValue,
            source = allBindings.jqAutoSource,
            query = allBindings.jqAutoQuery,
            valueProp = allBindings.jqAutoSourceValue,
            inputValueProp = allBindings.jqAutoSourceInputValue || valueProp,
            labelProp = allBindings.jqAutoSourceLabel || inputValueProp;

        //function that is shared by both select and change event handlers
        
        function writeValueToModel(valueToWrite) {
            if (ko.isWriteableObservable(modelValue)) {
                modelValue(valueToWrite);
            } else {
                //write to non-observable
                if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['jqAutoValue'])
                    allBindings['_ko_property_writers']['jqAutoValue'](valueToWrite);
            }
        }

        //on a selection write the proper value to the model
        options.select = function(event, ui) {
            writeValueToModel(ui.item ? ui.item.actualValue : null);
        };

        //on a change, make sure that it is a valid value or clear out the model value
        options.change = function(event, ui) {
            var currentValue = $(element).val();
            var matchingItem = ko.utils.arrayFirst(unwrap(source), function(item) {
                return unwrap(inputValueProp ? item[inputValueProp] : item) === currentValue;
            });
            if (!ui.item) {
                alert("Invalid Value Selected.");
            }
            if (!matchingItem) {
                writeValueToModel(null);
                alert("Invalid Value Selected.");
            }
        };

        //hold the autocomplete current response
        var currentResponse = null;

        //handle the choices being updated in a DO, to decouple value updates from source (options) updates
        var mappedSource = ko.dependentObservable({
            read: function() {
                mapped = ko.utils.arrayMap(unwrap(source), function(item) {
                    var result = {};
                    result.label = labelProp ? unwrap(item[labelProp]) : unwrap(item).toString(); //show in pop-up choices
                    result.value = inputValueProp ? unwrap(item[inputValueProp]) : unwrap(item).toString(); //show in input box
                    result.actualValue = valueProp ? unwrap(item[valueProp]) : item; //store in model
                    return result;
                });
                return mapped;
            },
            write: function(newValue) {
                source(newValue); //update the source observableArray, so our mapped value (above) is correct
                if (currentResponse) {
                    currentResponse(mappedSource());
                }
            },
            disposeWhenNodeIsRemoved: element
        });

        if (query) {
            options.source = function(request, response) {
                currentResponse = response;
                query.call(this, request.term, mappedSource,options.postUrl);
            };
        } else {
            //whenever the items that make up the source are updated, make sure that autocomplete knows it
            mappedSource.subscribe(function(newValue) {
                $(element).autocomplete("option", "source", newValue);
            });

            options.source = mappedSource();
        }


        //initialize autocomplete
        $(element).autocomplete(options);
    },
    update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        //update value based on a model change
        var allBindings = allBindingsAccessor(),
            unwrap = ko.utils.unwrapObservable,
            modelValue = unwrap(allBindings.jqAutoValue) || '',
            valueProp = allBindings.jqAutoSourceValue,
            inputValueProp = allBindings.jqAutoSourceInputValue || valueProp;

        //if we are writing a different property to the input than we are writing to the model, then locate the object
        if (valueProp && inputValueProp !== valueProp) {
            var source = unwrap(allBindings.jqAutoSource) || [];
            var modelValue = ko.utils.arrayFirst(source, function(item) {
                return unwrap(item[valueProp]) === modelValue;
            }) || {};
        }

        //update the element with the value that should be shown in the input
        $(element).val(modelValue && inputValueProp !== valueProp ? unwrap(modelValue[inputValueProp]) : modelValue.toString());
    }
};

AutoComplete Function in ViewModel
JavaScript
function HmCreate() {
    var self = this;
self.BookingOfficeId = ko.observable();
    self.BookingOffices = ko.observableArray();
function autoList(value, label) {
        this.lbl = ko.observable(value);
        this.vl = ko.observable(label.split(':')[0]);
    };
self.autoCompleteCall = function (searchTerm, sourceArray, postUrl) {
        $.ajax({
            type: 'POST',
            url: postUrl,
            data: { term: searchTerm },
            success: function(data) {
                var result = [];
                    $.each(data, function(n, item) {
                        result.push(new autoList(item.value, item.label));
                    });
                sourceArray(result);
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {
                    alert('error occured while autocomplete');
                },
            dataType: 'json'
        });
    };
}

ko.applyBindings(new HmCreate);

HTML Searchbox
HTML
 <input autocomplete="off" data-bind="jqAuto: { autoFocus: true,postUrl:'/Offices/AutoList/'}, jqAutoSource: BookingOffices, jqAutoQuery: autoCompleteCall, jqAutoValue: BookingOfficeId, jqAutoSourceLabel: 'lbl', jqAutoSourceInputValue: 'lbl', jqAutoSourceValue: 'vl'" id="LrBranchId" name="LrBranchId" type="search" />
@Html.KoAuto(x=>x.LrBranchId,"BookingOffices","BookingOfficeId","/Offices/AutoList/")

MVC HTML Helper
C#
/// <summary>
        /// Knockout JS Autocomplete HTML Input
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="expression">Model Propert Name</param>
        /// <param name="jqAutoSource">The observableArray Name</param>
        /// <param name="jqAutoValue">The observable peroperty to store Value for Selected Option</param>
        /// <param name="postUrl">The post URL.</param>
        /// <param name="ajaxMethod">The ajax method Name. Default :autoCompleteCall</param>
        /// <param name="jqAutoSourceLabel">The Select List Label. Default:lbl</param>
        /// <param name="jqAutoSourceInputValue">The input display value. Default:lbl</param>
        /// <param name="jqAutoSourceValue">The value to store in observable. Default:vl</param>
        /// <param name="htmlAttributes">The HTML attributes.</param>
        /// <param name="validate">if set to <c>true</c> [Apply the Model Validation Properties.].</param>
        /// <param name="extraAjaxparam">The extra parameter to pass in Ajax Method.</param>
        /// <returns></returns>
        public static MvcHtmlString KoAuto<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string jqAutoSource, string jqAutoValue, string postUrl, IDictionary<string, object> extraAjaxparam=null, string ajaxMethod = "autoCompleteCall", string jqAutoSourceLabel = "'lbl'", string jqAutoSourceInputValue = "'lbl'", string jqAutoSourceValue = "'vl'", IDictionary<string, object> htmlAttributes = null, bool validate = false)
        {
            var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            var fullHtmlFieldName = ExpressionHelper.GetExpressionText(expression);
            var fullHtmlFieldId = ExpressionHelper.GetExpressionText(expression);
            var jqAuto = string.Empty;
            if (!string.IsNullOrWhiteSpace(postUrl))
            {
                jqAuto = "{ autoFocus: true,postUrl:'"+postUrl;
                if (extraAjaxparam!=null)
                {
                    jqAuto += "',extraParam:'";
                    jqAuto = extraAjaxparam.Aggregate(jqAuto, (current, pair) => current + string.Format("{0}:{1},", pair.Key, pair.Value));
                }
                jqAuto += "'}";

            }
            var tag = new TagBuilder("input");
            tag.MergeAttribute("type","search");
            var databind = string.Format("jqAuto: {0}, jqAutoSource: {1}, jqAutoQuery: {2}, jqAutoValue: {3}, jqAutoSourceLabel: {4}, jqAutoSourceInputValue: {5}, jqAutoSourceValue: {6}",
                jqAuto,jqAutoSource,ajaxMethod,jqAutoValue,jqAutoSourceLabel,jqAutoSourceInputValue,jqAutoSourceValue);
            var attributes =htmlAttributes ?? new Dictionary<string,object>();
            attributes.Add("data-bind", databind);
            attributes.Add("id", fullHtmlFieldId);
            attributes.Add("name", fullHtmlFieldName);
            attributes.Add("autocomplete","off");
            tag.MergeAttributes(attributes, true);
            
            if (!validate) return MvcHtmlString.Create(tag.ToString(TagRenderMode.SelfClosing));
            var validationAttributed = htmlHelper.GetUnobtrusiveValidationAttributes(metadata.ContainerType.FullName+'.'+fullHtmlFieldName, metadata);
            tag.MergeAttributes(validationAttributed);
            return MvcHtmlString.Create(tag.ToString(TagRenderMode.SelfClosing));
        }
 
Share this answer
 
Comments
Keval Gangani 27-Oct-17 2:39am    
@Mukesh Rebari Can you please provide me full source code of above example?

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900