Knockout Handler
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 writeValueToModel(valueToWrite) {
if (ko.isWriteableObservable(modelValue)) {
modelValue(valueToWrite);
} else {
if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers']['jqAutoValue'])
allBindings['_ko_property_writers']['jqAutoValue'](valueToWrite);
}
}
options.select = function(event, ui) {
writeValueToModel(ui.item ? ui.item.actualValue : null);
};
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.");
}
};
var currentResponse = null;
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();
result.value = inputValueProp ? unwrap(item[inputValueProp]) : unwrap(item).toString();
result.actualValue = valueProp ? unwrap(item[valueProp]) : item;
return result;
});
return mapped;
},
write: function(newValue) {
source(newValue);
if (currentResponse) {
currentResponse(mappedSource());
}
},
disposeWhenNodeIsRemoved: element
});
if (query) {
options.source = function(request, response) {
currentResponse = response;
query.call(this, request.term, mappedSource,options.postUrl);
};
} else {
mappedSource.subscribe(function(newValue) {
$(element).autocomplete("option", "source", newValue);
});
options.source = mappedSource();
}
$(element).autocomplete(options);
},
update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
var allBindings = allBindingsAccessor(),
unwrap = ko.utils.unwrapObservable,
modelValue = unwrap(allBindings.jqAutoValue) || '',
valueProp = allBindings.jqAutoSourceValue,
inputValueProp = allBindings.jqAutoSourceInputValue || valueProp;
if (valueProp && inputValueProp !== valueProp) {
var source = unwrap(allBindings.jqAutoSource) || [];
var modelValue = ko.utils.arrayFirst(source, function(item) {
return unwrap(item[valueProp]) === modelValue;
}) || {};
}
$(element).val(modelValue && inputValueProp !== valueProp ? unwrap(modelValue[inputValueProp]) : modelValue.toString());
}
};
AutoComplete Function in ViewModel
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
<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
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));
}