/** * jQuery Flexdatalist. * Autocomplete input fields, with support for datalists. * * Version: * 2.3.0 * * Depends: * jquery.js > 1.8.3 * * Demo and Documentation: * http://projects.sergiodinislopes.pt/flexdatalist/ * * Github: * https://github.com/sergiodlopes/jquery-flexdatalist/ * */ jQuery.fn.flexdatalist = function (_option, _value) { 'use strict'; var destroy = function ($flex, clear) { $flex.each(function () { var $this = $(this), data = $this.data(), options = data.flexdatalist, $aliascontainer = data.aliascontainer; if ($aliascontainer) { $this.removeClass('flexdatalist-set') .attr({'style': null, 'tabindex': null}) .val((options && options.originalValue && !clear ? options.originalValue : '')) .removeData('flexdatalist') .removeData('aliascontainer') .off(); $aliascontainer.remove(); } }); } // Callable stuff if (typeof _option === 'string' && _option !== 'reset') { if (typeof this[0] === 'object' && typeof this[0].fvalue !== 'undefined') { var target = this[0]; if (_option === 'destroy') { destroy(this, _value); // Get/Set value } else if (_option === 'value') { if (typeof _value === 'undefined') { return target.fvalue.get(); } target.fvalue.set(_value); // Add value } else if (_option === 'add') { if (typeof _value === 'undefined') { return target.debug('Missing value to add!'); } target.fvalue.add(_value); // Toggle value } else if (_option === 'toggle') { if (typeof _value === 'undefined') { return target.debug('Missing value to toggle!'); } target.fvalue.toggle(_value); // Remove value } else if (_option === 'remove') { if (typeof _value === 'undefined') { return target.debug('Missing value to remove!'); } target.fvalue.remove(_value); // Disabled/enabled } else if (_option === 'disabled') { if (typeof _value === 'undefined') { return target.fdisabled(); } target.fdisabled(_value); // Option(s) } else if (typeof _option === 'string') { if (typeof _value === 'undefined') { return target.options.get(_option); } target.options.set(_option, _value); } return this; } _option = {_option: _value}; } // Destroy if already set if (this.length > 0 && typeof this[0].fvalue !== 'undefined') { destroy(this); } var _options = $.extend({ url: null, data: [], params: {}, relatives: null, chainedRelatives: false, cache: true, cacheLifetime: 60, minLength: 3, groupBy: false, selectionRequired: false, focusFirstResult: false, textProperty: null, valueProperty: null, visibleProperties: [], iconProperty: 'thumb', searchIn: ['label'], searchContain: false, searchEqual: false, searchByWord: false, searchDisabled: false, searchDelay: 400, normalizeString: null, multiple: null, disabled: null, maxShownResults: 100, removeOnBackspace: true, noResultsText: 'No results found for "{keyword}"', toggleSelected: false, allowDuplicateValues: false, redoSearchOnFocus: true, requestType: 'get', requestContentType: 'x-www-form-urlencoded', requestHeaders: null, resultsProperty: 'results', keywordParamName: 'keyword', searchContainParamName: 'contain', limitOfValues: 0, valuesSeparator: ',', debug: true }, _option); return this.each(function (id) { var $this = $(this), _this = this, _searchTimeout = null, _values = [], fid = 'flex' + id, $alias = null, $multiple = null; /** * Initialization */ this.init = function () { var options = this.options.init(); this.set.up(); $alias // Focusin .on('focusin', function (event) { _this.action.redoSearchFocus(event); _this.action.showAllResults(event); if ($multiple) { $multiple.addClass('focus'); } }) // Keydown .on('input keydown', function (event) { if (_this.keyNum(event) === 9) { _this.results.remove(); } _this.action.keypressValue(event, 188); _this.action.backSpaceKeyRemove(event); }) // Keyup .on('input keyup', function (event) { _this.action.keypressValue(event, 13); _this.action.keypressSearch(event); _this.action.copyValue(event); _this.action.backSpaceKeyRemove(event); _this.action.showAllResults(event); _this.action.clearValue(event); _this.action.removeResults(event); _this.action.inputWidth(event); }) // Focusout .on('focusout', function (event) { if ($multiple) { $multiple.removeClass('focus'); } _this.action.clearText(event); _this.action.clearValue(event); }); window.onresize = function (event) { _this.position(); }; // Run garbage collector this.cache.gc(); if (options.selectionRequired) { _this.fvalue.clear(true, true); } this.fvalue._load(options.originalValue, function (values, matches) { _this.fdisabled(options.disabled); $this.trigger('init:flexdatalist', [options]); }, true); } /** * Handle user actions. */ this.action = { /** * Add value on comma or enter keypress. */ keypressValue: function (event, keyCode) { var key = _this.keyNum(event), val = $alias[0].value, options = _this.options.get(); if (val.length > 0 && key === keyCode && !options.selectionRequired && options.multiple) { var val = $alias[0].value; event.preventDefault(); event.stopPropagation(); _this.fvalue.extract(val); _this.results.remove(); } }, /** * Check if keypress is valid. */ keypressSearch: function (event) { var key = _this.keyNum(event), keyword = $alias.val(), length = keyword.length, options = _this.options.get(); clearTimeout(_searchTimeout); if (!key || (key !== 13 && (key < 37 || key > 40))) { _searchTimeout = setTimeout(function () { if ((options.minLength === 0 && length > 0) || (options.minLength > 0 && length >= options.minLength)) { _this.data.load(function (data) { _this.search.get(keyword, data, function (matches) { _this.results.show(matches); }); }); } }, options.searchDelay); } }, /** * Redo search if input get's back on focus and no value selected. */ redoSearchFocus: function (event) { var val = _this.fvalue.get(), options = _this.options.get(), alias = $alias.val(); if (options.redoSearchOnFocus && ((alias.length > 0 && options.multiple) || (alias.length > 0 && val.length === 0))) { this.keypressSearch(event); } }, /** * Copy value from alias to target input. */ copyValue: function (event) { if (_this.keyNum(event) !== 13) { var keyword = $alias.val(), val = _this.fvalue.get(true), options = _this.options.get(); if (!options.multiple && !options.selectionRequired && keyword.length !== val.length) { _this.fvalue.extract(keyword); } } }, /** * Remove value on backspace key (multiple input only). */ backSpaceKeyRemove: function (event) { var options = _this.options.get(); if (options.removeOnBackspace && options.multiple) { var val = $alias.val(), $remove = $alias.data('_remove'); if (_this.keyNum(event) === 8) { if (val.length === 0) { if ($remove) { _this.fvalue.remove($remove); $alias.data('_remove', null); } else { $alias.data('_remove', $alias.parents('li:eq(0)').prev()); } } else { $alias.data('_remove', null); } } } }, /** * Show all results if minLength option is 0. */ showAllResults: function (event) { var val = $alias.val(); val = $.trim(val); if (val === '' && _this.options.get('minLength') === 0) { _this.data.load(function (data) { _this.results.show(data); }); } }, /** * Calculate input width by number of characters. */ inputWidth: function (event) { var options = _this.options.get(); if (options.multiple) { var keyword = $alias.val(), fontSize = parseInt($alias.css('fontSize').replace('px', '')), minWidth = 40, maxWidth = $this.innerWidth(), width = ((keyword.length + 1) * fontSize); if (width >= minWidth && width <= maxWidth) { $alias[0].style.width = width + 'px'; } } }, /** * Clear text/alias input when criteria is met. */ clearText: function (event) { var val = _this.fvalue.get(), options = _this.options.get(); if (!options.multiple && options.selectionRequired && val.length === 0) { $alias[0].value = ''; } }, /** * Clear value when criteria is met. */ clearValue: function (event) { var val = _this.fvalue.get(), keyword = $alias.val(), options = _this.options.get(); if (!options.multiple && options.selectionRequired && keyword.length <= options.minLength) { _this.fvalue.clear(); } }, /** * Remove results when criteria is met. */ removeResults: function (event) { var keyword = $alias.val(), options = _this.options.get(); if (options.minLength > 0 && keyword.length < options.minLength) { _this.results.remove(); } } } /** * Setup flex. */ this.set = { /** * Prepare input replacement. */ up: function () { $alias = this.getAlias(); if (_this.options.get('multiple')) { $multiple = this.multipleInput($alias); } else { $alias.insertAfter($this); } this.accessibility($alias); // Respect autofocus attribute if ($this.attr('autofocus')) { $alias.trigger('focus'); } $this.data('aliascontainer', ($multiple ? $multiple : $alias)).addClass('flexdatalist flexdatalist-set').css({ 'position': 'absolute', 'top': -14000, 'left': -14000 }).attr('tabindex', -1); // update input label with alias id var inputId = $this.attr('id'), aliasId = $alias.attr('id'); $('label[for="' + inputId + '"]').attr('for', aliasId); this.chained(); }, /** * Create and return the alias input field for single value input. */ getAlias: function () { var aliasid = ($this.attr('id') ? $this.attr('id') + '-flexdatalist' : fid); var $alias = $('') .attr({ 'class': $this.attr('class'), 'name': ($this.attr('name') ? 'flexdatalist-' + $this.attr('name') : null), 'id': aliasid, 'placeholder': $this.attr('placeholder') }) .addClass('flexdatalist-alias ' + aliasid) .removeClass('flexdatalist') .attr('autocomplete', 'off'); return $alias; }, /** * Multiple values input/list */ multipleInput: function ($alias) { $multiple = $('