var Qs_AutocompleteElement = { _instances: {}, getInstance: function (elementId) { if (typeof this._instances[elementId] !== 'undefined') { return this._instances[elementId]; } return false; }, getInstances: function () { return this._instances; }, addInstance: function (elementId, instance) { this._instances[elementId] = instance; } }; /** * @version $Id$ * @uses jQuery * @uses jQuery.ui.autocomplete */ var Qs_Form_Element_Autocomplete = qs.createObject(); Qs_Form_Element_Autocomplete.defaultOptions = { valueElementId: undefined, displayElementId: undefined, titleElementId: undefined, dataUrl: undefined, dataCallback: undefined, dataArray: undefined, minLength: 2, /** * Callback for item rendering * @type {Function|String} */ renderItem: undefined }; Qs_Form_Element_Autocomplete.prototype = { initialize: function (options) { this.className = 'Qs_Form_Element_Autocomplete'; this.options = $.extend({}, Qs_Form_Element_Autocomplete.defaultOptions, options); this._autocompleteSource = undefined; this.valueElement = $('#' + this.options.valueElementId); this.displayElement = $('#' + this.options.displayElementId); this.titleElement = $('#' + this.options.titleElementId); if (1 !== this.valueElement.size() || 1 !== this.displayElement.size() || 1 !== this.titleElement.size()) { qs.debug.warn(this.className + ': can not me initialized'); return this; } if (!this.getAutocompleteSource()) { qs.debug.warn(this.className + ': source is not specified'); return this; } this.initAutocomplete(); Qs_AutocompleteElement.addInstance(this.options.valueElementId, this); return this; }, initAutocomplete: function () { var options = $.extend({ source: this.getAutocompleteSource(), autoFocus: true, focus: _.bind(this.onFocus, this), change: _.bind(this.onChange, this), select: _.bind(this.onSelect, this) }, this.getAutocompleteOptions()); this.displayElement.autocomplete(options); this.displayElement.data('uiAutocomplete')._renderItem = this.getRenderItemCallback(); return this; }, getRenderItemCallback: function () { var callback; if (this.options.renderItem) { if ('function' === typeof this.options.renderItem) { callback = this.options.renderItem; } else if ('function' !== typeof (callback = qs.variable(this.options.renderItem))) { callback = undefined; } } callback = (callback) ? callback : this.renderItem; return _.bind(callback, this); }, renderItem: function (ul, item) { return $(document.createElement('li')) .data('item.autocomplete', item) .append($(document.createElement('a')).text(item.title)) .appendTo(ul); }, onFocus: function (e, ui) { if (e.originalEvent.originalEvent && /^key/.test(e.originalEvent.originalEvent.type)) { var change = (this.valueElement.val() != ui.item.value); this.valueElement.val(ui.item.value); this.displayElement.val(ui.item.title); this.titleElement.val(ui.item.title); if (change) { this.valueElement.trigger('change'); } } return false; }, onChange: function(e, ui) { if (this.displayElement.val() != this.titleElement.val()) { var change = (!!this.valueElement.val()); this.valueElement.val(''); this.titleElement.val(''); this.displayElement.val(''); if (change) { this.valueElement.trigger('change'); } } }, onSelect: function (e, ui) { var change = (this.valueElement.val() != ui.item.value); this.valueElement.val(ui.item.value); this.displayElement.val(ui.item.title); this.titleElement.val(ui.item.title); if (change) { this.valueElement.trigger('change'); } return false; }, getAutocompleteSource: function () { if (undefined === this._autocompleteSource) { var source, tmp; if (this.options.dataUrl) { source = this.options.dataUrl; } else if (this.options.dataCallback) { tmp = qs.variable(this.options.dataCallback); if ('function' === typeof tmp) { source = tmp; } else { qs.debug.warn(this.className + ': dataCallback is not a function'); } } else if (this.options.dataArray) { source = _.bind(this.filterDataArray, this); } this._autocompleteSource = source; } return this._autocompleteSource; }, filterDataArray: function (request, response) { var matcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), 'i'), filtered = $.grep(this.options.dataArray, function (item) { return matcher.test(item.title); }); response(filtered); return true; }, getAutocompleteOptions: function () { var options = {}, i, length, name, callback, allowedOptions = ['autoFocus', 'delay', 'minLength', 'position'], allowedCallbacks = ['search', 'open', 'focus', 'select', 'close', 'change']; for (i = 0, length = allowedOptions.length; i < length; ++i) { name = allowedOptions[i]; if (undefined !== this.options[name]) { options[name] = this.options[name]; } } for (i = 0, length = allowedCallbacks.length; i < length; ++i) { name = allowedCallbacks[i]; if (undefined !== this.options[name] && 'function' === typeof (callback = qs.variable(name))) { options[name] = callback; } } return options; } };