yavsc/web/Scripts/knockout-jqAutocomplete.js

190 lines
7.4 KiB
JavaScript

// knockout-jqAutocomplete 0.4.3 | (c) 2015 Ryan Niemeyer | http://www.opensource.org/licenses/mit-license
;(function(factory) {
if (typeof define === "function" && define.amd) {
// AMD anonymous module
define(["knockout", "jquery", "jquery-ui/autocomplete"], factory);
} else {
// No module loader - put directly in global namespace
factory(window.ko, jQuery);
}
})(function(ko, $) {
var JqAuto = function() {
var self = this,
unwrap = ko.utils.unwrapObservable; //support older KO versions that did not have ko.unwrap
//binding's init function
this.init = function(element, valueAccessor, allBindings, data, context) {
var existingSelect, existingChange,
options = unwrap(valueAccessor()),
config = {},
filter = typeof options.filter === "function" ? options.filter : self.defaultFilter;
//extend with global options
ko.utils.extend(config, self.options);
//override with options passed in binding
ko.utils.extend(config, options.options);
//get source from a function (can be remote call)
if (typeof options.source === "function" && !ko.isObservable(options.source)) {
config.source = function(request, response) {
//provide a wrapper to the normal response callback
var callback = function(data) {
self.processOptions(valueAccessor, null, data, request, response);
};
//call the provided function for retrieving data
options.source.call(context.$data, request.term, callback);
};
}
else {
//process local data
config.source = self.processOptions.bind(self, valueAccessor, filter, options.source);
}
//save any passed in select/change calls
existingSelect = typeof config.select === "function" && config.select;
existingChange = typeof config.change === "function" && config.change;
//handle updating the actual value
config.select = function(event, ui) {
if (ui.item && ui.item.actual) {
options.value(ui.item.actual);
if (ko.isWriteableObservable(options.dataValue)) {
options.dataValue(ui.item.data);
}
}
if (existingSelect) {
existingSelect.apply(this, arguments);
}
};
//user made a change without selecting a value from the list
config.change = function(event, ui) {
if (!ui.item || !ui.item.actual) {
options.value(event.target && event.target.value);
if (ko.isWriteableObservable(options.dataValue)) {
options.dataValue(null);
}
}
if (existingChange) {
existingChange.apply(this, arguments);
}
};
//initialize the widget
var widget = $(element).autocomplete(config).data("ui-autocomplete");
//render a template for the items
if (options.template) {
widget._renderItem = self.renderItem.bind(self, options.template, context);
}
//destroy the widget if KO removes the element
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
if (widget && typeof widget.destroy === "function") {
widget.destroy();
widget = null;
}
});
};
//the binding's update function. keep value in sync with model
this.update = function(element, valueAccessor) {
var propNames, sources,
options = unwrap(valueAccessor()),
value = unwrap(options && options.value);
if (!value && value !== 0) {
value = "";
}
// find the appropriate value for the input
sources = unwrap(options.source);
propNames = self.getPropertyNames(valueAccessor);
// if there is local data, then try to determine the appropriate value for the input
if ($.isArray(sources) && propNames.value) {
value = ko.utils.arrayFirst(sources, function (opt) {
return opt[propNames.value] == value;
}
) || value;
}
if (propNames.input && value && typeof value === "object") {
element.value = value[propNames.input];
}
else {
element.value = value;
}
};
//if dealing with local data, the default filtering function
this.defaultFilter = function(item, term) {
term = term && term.toLowerCase();
return (item || item === 0) && ko.toJSON(item).toLowerCase().indexOf(term) > -1;
};
//filter/map options to be in a format that autocomplete requires
this.processOptions = function(valueAccessor, filter, data, request, response) {
var item, index, length,
items = unwrap(data) || [],
results = [],
props = this.getPropertyNames(valueAccessor);
//filter/map items
for (index = 0, length = items.length; index < length; index++) {
item = items[index];
if (!filter || filter(item, request.term)) {
results.push({
label: props.label ? item[props.label] : item.toString(),
value: props.input ? item[props.input] : item.toString(),
actual: props.value ? item[props.value] : item,
data: item
});
}
}
//call autocomplete callback to display list
response(results);
};
//if specified, use a template to render an item
this.renderItem = function(templateName, context, ul, item) {
var $li = $("<li></li>").appendTo(ul),
itemContext = context.createChildContext(item.data);
//apply the template binding
ko.applyBindingsToNode($li[0], { template: templateName }, itemContext);
//clean up
$li.one("remove", ko.cleanNode.bind(ko, $li[0]));
return $li;
};
//retrieve the property names to use for the label, input, and value
this.getPropertyNames = function(valueAccessor) {
var options = ko.toJS(valueAccessor());
return {
label: options.labelProp || options.valueProp,
input: options.inputProp || options.labelProp || options.valueProp,
value: options.valueProp
};
};
//default global options passed into autocomplete widget
this.options = {
autoFocus: true,
delay: 50
};
};
ko.bindingHandlers.jqAuto = new JqAuto();
});