diff options
Diffstat (limited to 'app/genealogy/dllib/jquery.flexdatalist.js')
-rw-r--r-- | app/genealogy/dllib/jquery.flexdatalist.js | 2083 |
1 files changed, 0 insertions, 2083 deletions
diff --git a/app/genealogy/dllib/jquery.flexdatalist.js b/app/genealogy/dllib/jquery.flexdatalist.js deleted file mode 100644 index 6fef98b..0000000 --- a/app/genealogy/dllib/jquery.flexdatalist.js +++ /dev/null @@ -1,2083 +0,0 @@ -/** - * 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 = $('<input type="text">') - .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 = $('<ul tabindex="1">') - .addClass('flexdatalist-multiple ' + fid) - .css({ - 'border-color': $this.css('border-left-color'), - 'border-width': $this.css('border-left-width'), - 'border-style': $this.css('border-left-style'), - 'border-radius': $this.css('border-top-left-radius'), - 'background-color': $this.css('background-color') - }) - .insertAfter($this).on('click', function () { - $(this).find('input').trigger('focus'); - }); - - $('<li class="input-container">') - .addClass('flexdatalist-multiple-value') - .append($alias) - .appendTo($multiple); - - return $multiple; - }, - /** - * Chained inputs handling. - */ - chained: function () { - var options = _this.options.get(); - if (options.relatives && options.chainedRelatives) { - var toggle = function (init) { - options.relatives.each(function () { - var emptyRelative = _this.isEmpty($(this).val()), - empty = _this.isEmpty(_this.value); - // If disabling, clear all values - if (emptyRelative || !empty) { - _this.fvalue.clear(); - } - _this.fdisabled(emptyRelative); - }); - }; - options.relatives.on('change', function () { - toggle(); - }); - toggle(true); - } - }, - /** - * Accessibility. - */ - accessibility: function ($input) { - var aliasid = ($this.attr('id') ? $this.attr('id') + '-flexdatalist' : fid); - var scrReaderAttr = { - 'aria-autocomplete': 'list', - 'aria-expanded': 'false', - 'aria-owns': aliasid + '-results', - }; - - $input.attr(scrReaderAttr); - } - } - - /** - * Process input value(s) (where the magic happens). - */ - this.fvalue = { - /** - * Get value(s). - */ - get: function (asString) { - var val = _this.value, - options = _this.options.get(); - if ((options.multiple || this.isJSON()) && !asString) { - return this.toObj(val); - } - return val; - }, - /** - * Set value. - * Parse value if necessary. - */ - set: function (val, append) { - if (!_this.fdisabled()) { - if (!append) { - this.clear(true); - } - this._load(val); - } - return $this; - }, - /** - * Add value. - */ - add: function (val) { - if (_this.options.get('multiple')) { - this.set(val, true); - } - return this; - }, - /** - * Toggle value. - */ - toggle: function (val) { - if (!_this.fdisabled()) { - this.multiple.toggle(val); - } - return this; - }, - /** - * Remove value. - */ - remove: function (val) { - if (!_this.fdisabled()) { - val = this.toObj(val); - $this.trigger('before:flexdatalist.remove', [val]); - var result = []; - if (_this.isArray(val)) { - $.each(val, function (i, value) { - var removed = _this.fvalue.multiple.remove(value); - if (removed) { - result.push(removed); - } - }); - } else { - var _result = this.multiple.remove(val); - if (_result) { - result.push(_result); - } - } - $this - .trigger('after:flexdatalist.remove', [val, result]) - .trigger('change:flexdatalist', [result, _this.options.get()]) - .trigger('change'); - } - return this; - }, - /** - * Load (remote?) value(s). - */ - _load: function (values, callback, init) { - var options = _this.options.get(), - valueProp = options.valueProperty, - _values = this.toStr(values), - _val = this.get(true); - - callback = (callback ? callback : $.noop); - // If nothing changes, return - if (_values.length == 0 && _val.length == 0) { - callback(values); - return; - } - values = this.toObj(values); - if (!_this.isEmpty(values) && !_this.isEmpty(valueProp) && valueProp !== '*') { - if (!_this.isObject(valueProp)) { - valueProp = valueProp.split(','); - } - // Load data - _this.data.load(function (data) { - if (!_this.isObject(values)) { - values = values.split(','); - } else if (!_this.isArray(values)) { - values = [values]; - } - var found = []; - for (var idxv = 0; idxv < values.length; idxv++) { - var value = values[idxv]; - for (var i = 0; i < data.length; i++) { - var item = data[i]; - for (var idx = 0; idx < valueProp.length; idx++) { - var prop = valueProp[idx], - value = _this.isDefined(value, prop) ? value[prop] : value; - if (_this.isDefined(item, prop) && value === item[prop]) { - found.push(item); - } - } - } - } - if (found.length > 0) { - _this.fvalue.extract(found, true); - } - callback(values); - }, values); - return; - } - callback(values); - _this.fvalue.extract(values, init); - }, - /** - * Extract value and text. - */ - extract: function (values, init) { - var options = _this.options.get(), - result = []; - - if (!init) { - $this.trigger('before:flexdatalist.value', [values, options]); - } - - if (_this.isArray(values)) { - $.each(values, function (i, value) { - result.push(_this.fvalue._extract(value)); - }); - } else { - result = _this.fvalue._extract(values); - } - - if (!init) { - $this - .data('result_selected', values) - .trigger('after:flexdatalist.value', [result, options]) - .trigger('change:flexdatalist', [result, options]) - .trigger('change'); - } - }, - /** - * @inherited. - */ - _extract: function (val) { - var txt = this.text(val), - value = this.value(val), - options = _this.options.get(); - - if (options.multiple) { - // For allowDuplicateValues - if (!_this.isEmpty(txt)) { - if (_this.isDup(txt)) { - return; - } - _values.push(txt); - this.multiple.add(value, txt); - } - } else { - this.single(value, txt); - } - - return {value: value, text: txt}; - }, - /** - * Default input value. - */ - single: function (val, txt) { - if (txt && txt !== $alias.val()) { - $alias[0].value = txt; - } - _this.value = val; - }, - /** - * Input with multiple values. - */ - multiple: { - /** - * Add value and item on list. - */ - add: function (val, txt) { - var _multiple = this, - $li = this.li(val, txt); - - // Toggle - $li.on('click', function () { - _multiple.toggle($(this)); - // Remove - }).find('.fdl-remove').on('click', function () { - _this.fvalue.remove($(this).parent()); - }); - - this.push(val); - $alias[0].value = ''; - this.handleLimit(); - }, - /** - * Push value to input. - */ - push: function (val, index) { - var current = _this.fvalue.get(); - if (current.includes(val)) { - return; - } - val = _this.fvalue.toObj(val); - current.push(val); - val = _this.fvalue.toStr(current); - _this.value = val; - }, - /** - * Toggle value. - */ - toggle: function (val) { - var options = _this.options.get(); - if (!options.toggleSelected) { - return; - } - - var $li = this.findLi(val); - if (!$li) { - return; - } - - var data = $li.data(), - action = $li.hasClass('disabled') ? 'enable' : 'disable', - eventArgs = [{value: data.value, text: data.text, action: action}, options]; - - $this.trigger('before:flexdatalist.toggle', eventArgs); - - if (action === 'enable') { - $li.removeClass('disabled'); - } else { - $li.addClass('disabled'); - } - - var current = []; - $multiple.find('li.toggle:not(.disabled)').each(function () { - var $item = $(this); - current.push($item.data('value')); - }); - - current = _this.fvalue.toStr(current); - _this.value = current; - - $this - .trigger('after:flexdatalist.toggle', eventArgs) - .trigger('change:flexdatalist', eventArgs) - .trigger('change'); - - }, - /** - * Remove value from input. - */ - remove: function (val) { - var $li = this.findLi(val); - if (!$li) { - return; - } - - var values = _this.fvalue.get(), - index = $li.index(), - data = $li.data(), - arg = {value: data.value, text: data.text}; - - values.splice(index, 1); - values = _this.fvalue.toStr(values); - _this.value = values; - $li.remove(); - _this.fvalue.multiple.handleLimit(); - - // For allowDuplicateValues - _values.splice(index, 1); - - this.handleLimit(); - - return arg; - }, - /** - * Remove all. - */ - removeAll: function () { - var values = _this.fvalue.get(), - options = _this.options.get(); - - $this.trigger('before:flexdatalist.remove.all', [values, options]); - - $multiple.find('li:not(.input-container)').remove(); - - _this.value = ''; - _values = []; - - this.handleLimit(); - - $this.trigger('after:flexdatalist.remove.all', [values, options]); - }, - /** - * Create new item and return it. - */ - li: function (val, txt) { - var $inputContainer = $multiple.find('li.input-container'); - var options = _this.options.get(); - return $('<li>') - .addClass('value' + (options.toggleSelected ? ' toggle' : '')) - .append('<span class="text">' + txt + '</span>') - .append('<span class="fdl-remove">×</span>') - .data({ - 'text': txt, - 'value': _this.fvalue.toStr(val) - }) - .insertBefore($inputContainer); - }, - /** - * Handle the limit. - * - * @return void - */ - handleLimit: function () { - var isAtLimit = this.isAtLimit(); - var $input = $multiple.find('li.input-container'); - isAtLimit ? $input.hide() : $input.show(); - }, - /** - * Check the limit of items. - * - * @return bool True if reached the limit, false otherwise - */ - isAtLimit: function () { - var limit = _this.options.get('limitOfValues'); - if (!(limit > 0)) { - return false; - } - return limit == _values.length; - }, - /** - * Get li item from value. - */ - findLi: function ($li) { - if (!($li instanceof jQuery)) { - var val = $li; - $li = null; - $multiple.find('li:not(.input-container)').each(function () { - var $_li = $(this); - if ($_li.data('value') === val) { - $li = $_li; - return false; - } - }); - } else if ($li.length === 0) { - $li = null; - } - return $li; - }, - /** - * Get li item from value. - */ - isEmpty: function () { - return this.get().length > 0; - } - }, - /** - * Get value that will be set on input field. - */ - value: function (item) { - var value = item, - options = _this.options.get(), - valueProperty = options.valueProperty; - - if (_this.isObject(item)) { - if (this.isJSON() || this.isMixed()) { - delete item.name_highlight; - if (_this.isArray(valueProperty)) { - var _value = {}; - for (var i = 0; i < valueProperty.length; i++) { - var propValue = _this.getPropertyValue(item, valueProperty[i]); - if (propValue) { - _value[valueProperty[i]] = propValue; - } - } - value = this.toStr(_value); - } else { - value = this.toStr(item); - } - } else if (_this.isDefined(item, valueProperty)) { - value = _this.getPropertyValue(item, valueProperty); - } else if (_this.isDefined(item, options.searchIn[0])) { - value = _this.getPropertyValue(item, options.searchIn[0]); - } else { - value = null; - } - } - return value; - }, - /** - * Get text that will be shown to user on the alias input field. - */ - text: function (item) { - var text = item, - options = _this.options.get(); - - if (_this.isObject(item)) { - text = _this.getPropertyValue(item, options.searchIn[0]); - if (_this.isDefined(item, options.textProperty)) { - text = _this.getPropertyValue(item, options.textProperty); - } else { - text = this.placeholders.replace(item, options.textProperty, text); - } - } - - text = _this.escapeHtml(text); - - return text; - }, - /** - * Process text placeholders. - */ - placeholders: { - replace: function (item, pattern, fallback) { - if (_this.isObject(item) && typeof pattern === 'string') { - var properties = this.parse(pattern); - if (!_this.isEmpty(item) && properties) { - $.each(properties, function (string, property) { - if (_this.isDefined(item, property)) { - pattern = pattern.replace(string, _this.getPropertyValue(item, property)); - } - }); - return pattern; - } - } - return fallback; - }, - parse: function (pattern) { - var matches = pattern.match(/\{.+?\}/g); - if (!matches) { - return false; - } - var properties = {}; - matches.map(function (string) { - properties[string] = string.slice(1, -1); - }); - return properties; - } - }, - /** - * Clear input value(s). - */ - clear: function (alias, init) { - var current = _this.value, - options = _this.options.get(); - - if (options.multiple) { - this.multiple.removeAll(); - } - - _this.value = ''; - if (alias) { - $alias[0].value = ''; - } - if (current !== '' && !init) { - $this - .trigger('change:flexdatalist', [{value: '', text: ''}, options]) - .trigger('clear:flexdatalist', [{value: '', text: ''}, options]) - .trigger('change'); - } - _values = []; - return this; - }, - /** - * Value to object. - */ - toObj: function (val) { - if (typeof val !== 'object') { - var options = _this.options.get(); - if (_this.isEmpty(val) || !_this.isDefined(val)) { - val = options.multiple ? [] : (this.isJSON() ? {} : ''); - } else if (this.isCSV()) { - val = val.toString().split(options.valuesSeparator); - val = val.map(function (v) { - return v.trim(); - }); - } else if ((this.isMixed() || this.isJSON()) && this.isJSON(val)) { - val = JSON.parse(val); - } else if (typeof val === 'number') { - val = val.toString(); - } - } - return val; - }, - /** - * Is value expected to be JSON (either object or string). - */ - toStr: function (val) { - if (typeof val !== 'string') { - if (_this.isEmpty(val) || !_this.isDefined(val)) { - val = ''; - } else if (typeof val === 'number') { - val = val.toString(); - } else if (this.isCSV()) { - val = val.join(_this.options.get('valuesSeparator')); - } else if (this.isJSON() || this.isMixed()) { - val = JSON.stringify(val); - } - } - return $.trim(val); - }, - /** - * If argument is passed, will check if is a valid JSON object/string. - * otherwise will check if JSON is the value expected for input - */ - isJSON: function (str) { - if (typeof str !== 'undefined') { - if (_this.isObject(str)) { - str = JSON.stringify(str); - } else if (typeof str !== 'string') { - return false; - } - return (str.indexOf('{') === 0 || str.indexOf('[{') === 0); - } - var options = _this.options.get(), - prop = options.valueProperty; - return (_this.isObject(prop) || prop === '*'); - }, - /** - * Is value expected to be JSON (either object or string). - */ - isMixed: function () { - var options = _this.options.get(); - return !options.selectionRequired && options.valueProperty === '*'; - }, - /** - * Is value expected to be CSV? - */ - isCSV: function () { - return (!this.isJSON() && _this.options.get('multiple')); - } - } - - /** - * Data. - */ - this.data = { - /** - * Load data from all sources. - */ - load: function (callback, load) { - var __this = this, - data = []; - $this.trigger('before:flexdatalist.data'); - // Remote data - this.url(function (remote) { - data = data.concat(remote); - // Static data - __this.static(function (_static) { - data = data.concat(_static); - // Datalist - __this.datalist(function (list) { - data = data.concat(list); - - $this.trigger('after:flexdatalist.data', [data]); - callback(data); - }); - }); - }, load); - }, - /** - * Get static data. - */ - static: function (callback) { - var __this = this, - options = _this.options.get(); - // Remote source - if (typeof options.data === 'string') { - var url = options.data, - cache = _this.cache.read(url, true); - if (cache) { - callback(cache); - return; - } - this.remote({ - url: url, - success: function (data) { - options.data = data; - callback(data); - _this.cache.write(url, data, options.cacheLifetime, true); - } - }); - } else { - if (typeof options.data !== 'object') { - options.data = []; - } - callback(options.data); - } - }, - /** - * Get datalist values. - */ - datalist: function (callback) { - var list = $this.attr('list'), - datalist = []; - if (!_this.isEmpty(list)) { - $('#' + list).find('option').each(function () { - var $option = $(this), - val = $option.val(), - label = $option.text(); - datalist.push({ - label: (label.length > 0 ? label : val), - value: val - }); - }); - } - callback(datalist); - }, - /** - * Get remote data. - */ - url: function (callback, load) { - var keyword = $alias.val(), - options = _this.options.get(), - keywordParamName = options.keywordParamName, - searchContainParamName = options.searchContainParamName, - value = _this.fvalue.get(), - relatives = this.relativesData(); - - if (_this.isEmpty(options.url)) { - return callback([]); - } - - var _opts = {}; - if (options.requestType === 'post') { - $.each(options, function (option, value) { - if (option.indexOf('_') == 0 || option == 'data') { - return; - } - _opts[option] = value; - }); - delete _opts.relatives; - } - - // Cache - var cacheKey = _this.cache.keyGen({ - relative: relatives, - load: load, - keyword: keyword, - contain: options.searchContain - }, options.url), - cache = _this.cache.read(cacheKey, true); - - if (cache) { - callback(cache); - return; - } - - var params = typeof(options.params) == 'function' ? - options.params.call($this[0], keyword) : - options.params; - - var data = $.extend( - relatives, - params, - { - load: load, - selected: value, - original: options.originalValue, - options: _opts - } - ); - - data[keywordParamName] = keyword; - data[searchContainParamName] = options.searchContain; - - this.remote({ - url: options.url, - data: data, - success: function (_data) { - var _keyword = $alias.val(); - // Is this really needed? - if (_keyword.length >= keyword.length) { - callback(_data); - } - _this.cache.write(cacheKey, _data, options.cacheLifetime, true); - } - }); - }, - /** - * AJAX request. - */ - remote: function (settings) { - var __this = this, - options = _this.options.get(); - - // Prevent get data when pressing backspace button - if ($this.hasClass('flexdatalist-loading')) { - return; - } - $this.addClass('flexdatalist-loading'); - - if (options.requestContentType === 'json') { - settings.data = JSON.stringify(settings.data); - } - - $.ajax($.extend( - { - type: options.requestType, - dataType: 'json', - headers: options.requestHeaders, - contentType: 'application/' + options.requestContentType + '; charset=UTF-8', - complete: function () { - $this.removeClass('flexdatalist-loading'); - } - }, settings, { - success: function (data) { - data = __this.extractRemoteData(data); - settings.success(data); - } - } - )); - }, - /** - * Extract remote data from server response. - */ - extractRemoteData: function (data) { - var options = _this.options.get(), - _data = _this.isDefined(data, options.resultsProperty) ? data[options.resultsProperty] : data; - - if (typeof _data === 'string' && _data.indexOf('[{') === 0) { - _data = JSON.parse(_data); - } - if (_data && _data.options) { - _this.options.set($.extend({}, options, _data.options)); - } - if (_this.isObject(_data)) { - return _data; - } - return []; - }, - /** - * Extract remote data from server response. - */ - relativesData: function () { - var relatives = _this.options.get('relatives'), - data = {}; - if (relatives) { - data['relatives'] = {}; - relatives.each(function () { - var $_input = $(this), - name = $_input.attr('name') - .split('][').join('-') - .split(']').join('-') - .split('[').join('-') - .replace(/^\|+|\-+$/g, ''); - data['relatives'][name] = $_input.val(); - }); - } - return data; - } - } - - /** - * Search. - */ - this.search = { - /** - * Search for keywords in data and return matches to given callback. - */ - get: function (keywords, data, callback) { - var __this = this, - options = _this.options.get(), - matches = data; - - if (!options.searchDisabled) { - var matches = _this.cache.read(keywords); - if (!matches) { - $this.trigger('before:flexdatalist.search', [keywords, data]); - if (!_this.isEmpty(keywords)) { - matches = []; - var words = __this.split(keywords); - for (var index = 0; index < data.length; index++) { - var item = data[index]; - if (_this.isDup(item)) { - continue; - } - item = __this.matches(item, words); - if (item) { - matches.push(item); - } - } - } - _this.cache.write(keywords, matches, 2); - $this.trigger('after:flexdatalist.search', [keywords, data, matches]); - } - } - - callback(matches); - }, - /** - * Match against searchable properties. - */ - matches: function (item, keywords) { - var _item = $.extend({}, item), - found = [], - options = _this.options.get(), - searchIn = options.searchIn; - - if (keywords.length > 0) { - for (var index = 0; index < searchIn.length; index++) { - var searchProperty = searchIn[index]; - if (!_this.isDefined(item, searchProperty) || !item[searchProperty]) { - continue; - } - - var text = item[searchProperty].toString(), - highlight = text, - strings = this.split(text); - - for (var kwindex = 0; kwindex < keywords.length; kwindex++) { - var keyword = keywords[kwindex]; - if (this.find(keyword, strings)) { - found.push(keyword); - highlight = this.highlight(keyword, highlight); - } - } - - if (highlight !== text) { - _item[searchProperty + '_highlight'] = this.highlight(highlight); - } - } - - } - - if (found.length === 0 || (options.searchByWord && found.length < (keywords.length - 1))) { - return false; - } - - return _item; - }, - /** - * Wrap found keyword with span.highlight. - */ - highlight: function (keyword, text) { - if (text) { - // Fix by https://github.com/antunesl - keyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - return text.replace( - new RegExp(keyword, (_this.options.get('searchContain') ? "ig" : "i")), - '|:|$&|::|' - ); - } - keyword = keyword.split('|:|').join('<span class="highlight">'); - return keyword.split('|::|').join('</span>'); - }, - /** - * Search for keyword(s) in string. - */ - find: function (keyword, splitted) { - var options = _this.options.get(); - for (var index = 0; index < splitted.length; index++) { - var text = splitted[index]; - text = this.normalizeString(text), - keyword = this.normalizeString(keyword); - if (options.searchEqual) { - return text == keyword; - } - if ((options.searchContain ? (text.indexOf(keyword) >= 0) : (text.indexOf(keyword) === 0))) { - return true; - } - } - return false; - }, - /** - * Split string by words if needed. - */ - split: function (keywords) { - if (typeof keywords === 'string') { - keywords = [$.trim(keywords)]; - } - if (_this.options.get('searchByWord')) { - for (var index = 0; index < keywords.length; index++) { - var keyword = $.trim(keywords[index]); - if (keyword.indexOf(' ') > 0) { - var words = keyword.split(' '); - $.merge(keywords, words); - } - } - } - return keywords; - }, - /** - * Normalize string to a consistent one to perform the search/match. - */ - normalizeString: function (string) { - if (typeof string === 'string') { - var normalizeString = _this.options.get('normalizeString'); - if (typeof normalizeString === 'function') { - string = normalizeString(string); - } - string = string.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); - return string.toUpperCase(); - } - return string; - } - } - - /** - * Handle results. - */ - this.results = { - /** - * Save key = value data in local storage (if supported) - * - * @param string key Data key string - */ - show: function (results) { - var __this = this, - options = _this.options.get(); - - this.remove(true); - - if (!results) { - return; - } else if(results.length === 0) { - this.empty(options.noResultsText); - return; - } - - var $ul = this.container(); - if (!options.groupBy) { - this.items(results, $ul); - } else { - results = this.group(results); - Object.keys(results).forEach(function (groupName, index) { - var items = results[groupName], - property = options.groupBy, - groupText = _this.results.highlight(items[0], property, groupName); - - var $li = $('<li>') - .addClass('group') - .append($('<span>') - .addClass('group-name') - .html(groupText) - ) - .append($('<span>') - .addClass('group-item-count') - .text(' ' + items.length) - ) - .appendTo($ul); - - __this.items(items, $ul); - }); - } - - var $li = $ul.find('li:not(.group)'); - - // Listen to result's item events - $li.on('click', function (event) { - var item = $(this).data('item'); - if (item) { - _this.fvalue.extract(item); - __this.remove(); - $this.trigger('select:flexdatalist', [item, options]); - } - }).on('hover', function () { - $li.removeClass('active'); - $(this).addClass('active').trigger('active:flexdatalist.results', [$(this).data('item')]); - }, function () { - $(this).removeClass('active'); - }); - - if (options.focusFirstResult) { - $li.filter(':first').addClass('active'); - } - }, - /** - * Remove results container. - */ - empty: function (text) { - if (_this.isEmpty(text)) { - return; - } - var $container = this.container(), - keyword = $alias.val(); - - text = text.split('{keyword}').join(keyword); - $('<li>') - .addClass('item no-results') - .append(text) - .appendTo($container) - - $this.trigger('empty:flexdatalist.results', [text]); - }, - /** - * Items iteration. - */ - items: function (items, $resultsContainer) { - var max = _this.options.get('maxShownResults'); - - $this.trigger('show:flexdatalist.results', [items]); - - for (var index = 0; index < items.length; index++) { - if (max > 0 && max === index) { - break; - } - this.item(items[index], index, items.length).appendTo($resultsContainer); - } - - $this.trigger('shown:flexdatalist.results', [items]); - }, - /** - * Result item creation. - */ - item: function (item, index, total) { - var $li = $('<li>') - .attr({ - 'role': 'option', - 'tabindex': '-1', - 'aria-posinset': index + 1, - 'aria-setsize': total - }) - .data('item', item) - .addClass('item'), - options = _this.options.get(), - visibleProperties = options.visibleProperties; - - for (var index = 0; index < visibleProperties.length; index++) { - var visibleProperty = visibleProperties[index]; - - if (visibleProperty.indexOf('{') > -1) { - var str = _this.fvalue.placeholders.replace(item, visibleProperty), - parsed = _this.fvalue.placeholders.parse(visibleProperty); - $item = $('<span>') - .addClass('item item-' + Object.values(parsed).join('-')) - .html(str + ' '); - } else { - if (options.groupBy && options.groupBy === visibleProperty || !_this.isDefined(item, visibleProperty)) { - continue; - } - var $item = {}; - if (visibleProperty === options.iconProperty) { - // Icon - $item = $('<img>') - .addClass('item item-' + visibleProperty) - .attr('src', item[visibleProperty]); - } else { - var propertyText = _this.results.highlight(item, visibleProperty); - // Other text properties - $item = $('<span>') - .addClass('item item-' + visibleProperty) - .html(propertyText + ' '); - } - } - - $item.appendTo($li); - } - - $this.trigger('item:flexdatalist.results', [$li, item]); - - return $li; - }, - /** - * Results container - */ - container: function () { - var $target = $this; - - if ($multiple) { - $target = $multiple; - } - - var $container = $('ul.flexdatalist-results'); - - if ($container.length === 0) { - $container = $('<ul>') - .addClass('flexdatalist-results ') - .appendTo('body') - .attr({ - 'id': $alias.attr('id') + '-results', - 'role': 'listbox' - }) - .css({ - 'border-color': $target.css("border-left-color"), - 'border-width': '1px', - 'border-bottom-left-radius': $target.css("border-bottom-left-radius"), - 'border-bottom-right-radius': $target.css("border-bottom-right-radius") - }).data({ - target: ($multiple ? $multiple : $alias), - input: $this - }); - _this.position($alias); - } - - return $container; - }, - /** - * Results container - */ - group: function (results) { - var data = [], - groupProperty = _this.options.get('groupBy'); - - for (var index = 0; index < results.length; index++) { - var _data = results[index]; - if (_this.isDefined(_data, groupProperty)) { - var propertyValue = _data[groupProperty]; - if (!_this.isDefined(data, propertyValue)) { - data[propertyValue] = []; - } - data[propertyValue].push(_data); - } - } - - return data; - }, - /** - * Check if highlighted property value exists, - * if true, return it, if not, fallback to given string - */ - highlight: function (item, property, fallback) { - if (_this.isDefined(item, property + '_highlight')) { - return item[property + '_highlight']; - } - return (_this.isDefined(item, property) ? item[property] : fallback); - }, - /** - * Set given item as active - */ - active: function ($item) { - - }, - /** - * Remove results - */ - remove: function (itemsOnly) { - var selector = 'ul.flexdatalist-results'; - if (itemsOnly) { - selector = 'ul.flexdatalist-results li'; - } - $this.trigger('remove:flexdatalist.results'); - $(selector).remove(); - $this.trigger('removed:flexdatalist.results'); - } - } - - /** - * Interface for localStorage. - */ - this.cache = { - /** - * Save key = value data in local storage (if supported) - * - * @param string key Data key string - * @param mixed value Value to be saved - * @param int lifetime In Seconds - * @return mixed - */ - write: function (key, value, lifetime, global) { - if (_this.cache.isSupported()) { - key = this.keyGen(key, undefined, global); - var object = { - value: value, - // Get current UNIX timestamp - timestamp: _this.unixtime(), - lifetime: (lifetime ? lifetime : false) - }; - localStorage.setItem(key, JSON.stringify(object)); - } - }, - /** - * Read data associated with given key - * - * @param string key Data key string - * @return mixed - */ - read: function (key, global) { - if (_this.cache.isSupported()) { - key = this.keyGen(key, undefined, global); - var data = localStorage.getItem(key); - if (data) { - var object = JSON.parse(data); - if (!this.expired(object)) { - return object.value; - } - localStorage.removeItem(key); - } - } - return null; - }, - /** - * Remove data associated with given key. - * - * @param string key Data key string - */ - delete: function (key, global) { - if (_this.cache.isSupported()) { - key = this.keyGen(key, undefined, global); - localStorage.removeItem(key); - } - }, - /** - * Clear all data. - */ - clear: function () { - if (_this.cache.isSupported()) { - for (var key in localStorage){ - if (key.indexOf(fid) > -1 || key.indexOf('global') > -1) { - localStorage.removeItem(key); - } - } - localStorage.clear(); - } - }, - /** - * Run cache garbage collector to prevent using all localStorage's - * available space. - */ - gc: function () { - if (_this.cache.isSupported()) { - for (var key in localStorage){ - if (key.indexOf(fid) > -1 || key.indexOf('global') > -1) { - var data = localStorage.getItem(key); - data = JSON.parse(data); - if (this.expired(data)) { - localStorage.removeItem(key); - } - } - } - } - }, - /** - * Check if browser supports localtorage. - * - * @return boolean True if supports, false otherwise - */ - isSupported: function () { - if (_this.options.get('cache')) { - try { - return 'localStorage' in window && window['localStorage'] !== null; - } catch (e) { - return false; - } - } - return false; - }, - /** - * Check if cache data as expired. - * - * @param object object Data to check - * @return boolean True if expired, false otherwise - */ - expired: function (object) { - if (object.lifetime) { - var diff = (_this.unixtime() - object.timestamp); - return object.lifetime <= diff; - } - return false; - }, - /** - * Generate cache key from object or string. - * - * @return string Cache key - */ - keyGen: function (str, seed, global) { - if (typeof str === 'object') { - str = JSON.stringify(str); - } - var i, l, - hval = (seed === undefined) ? 0x811c9dc5 : seed; - - for (i = 0, l = str.length; i < l; i++) { - hval ^= str.charCodeAt(i); - hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24); - } - return (global ? 'global' : fid) + ("0000000" + (hval >>> 0).toString(16)).substr(-8); - } - } - - /** - * Options handler. - */ - this.options = { - init: function () { - var options = $.extend({}, - _options, - $this.data(), - { - multiple: (_options.multiple === null ? $this.is('[multiple]') : _options.multiple), - disabled: (_options.disabled === null ? $this.is('[disabled]') : _options.disabled), - originalValue: _this.value - } - ); - this.set(options); - return options; - }, - get: function (option) { - var options = $this.data('flexdatalist'); - if (!option) { - return options ? options : {}; - } - return _this.isDefined(options, option) ? options[option] : null; - }, - set: function (option, value) { - var options = this.get(); - if (_this.isDefined(options, option) && _this.isDefined(value)) { - options[option] = value; - } else if (_this.isObject(option)) { - options = this._normalize(option); - } - $this.data('flexdatalist', options); - return $this; - }, - _normalize: function (options) { - options.searchIn = _this.csvToArray(options.searchIn); - options.relatives = options.relatives && $(options.relatives).length > 0 ? $(options.relatives) : null; - options.textProperty = options.textProperty === null ? options.searchIn[0] : options.textProperty; - options.visibleProperties = _this.csvToArray(options.visibleProperties, options.searchIn); - if (options.valueProperty === '*' && options.multiple && !options.selectionRequired) { - throw new Error('Selection must be required for multiple, JSON fields!'); - } - return options; - } - } - - /** - * Position results below parent element. - */ - this.position = function () { - var $results = $('ul.flexdatalist-results'), - $target = $results.data('target'); - if ($results.length > 0) { - // Set some required CSS properties - $results.css({ - 'width': $target.outerWidth() + 'px', - 'top': (($target.offset().top + $target.outerHeight())) + 'px', - 'left': $target.offset().left + 'px' - }); - } - } - - /** - * Handle disabled state. - */ - this.fdisabled = function (disabled) { - if (this.isDefined(disabled)) { - $this.prop('disabled', disabled); - $alias.prop('disabled', disabled); - if ($multiple) { - $multiple.css('background-color', $this.css('background-color')); - var $btns = $multiple.find('li .fdl-remove'), - $input = $multiple.find('li.input-container'); - if (disabled) { - $multiple.addClass('disabled'); - if ($btns.length > 0) { - $input.hide(); - } - $btns.hide(); - } else { - $multiple.removeClass('disabled'); - $input.show(); - $btns.show(); - } - } - this.options.set('disabled', disabled); - } - return this.options.get('disabled'); - } - - /** - * Check for dup values. - */ - this.isDup = function (val) { - if (!this.options.get('allowDuplicateValues')) { - return _values.length > 0 && _values.indexOf(this.fvalue.text(val)) > -1; - } - return false; - } - - /** - * Get key code from event. - */ - this.keyNum = function (event) { - return event.which || event.keyCode; - } - - /** - * Is variable empty. - */ - this.isEmpty = function (value) { - if (!_this.isDefined(value)) { - return true; - } else if (value === null) { - return true; - } else if (value === true) { - return false; - } else if (this.length(value) === 0) { - return true; - } else if ($.trim(value) === '') { - return true; - } - return false; - } - - /** - * Is variable an object. - */ - this.isObject = function (value) { - return (value && typeof value === 'object'); - } - - /** - * Get length of variable. - */ - this.length = function (value) { - if (this.isObject(value)) { - return Object.keys(value).length; - } else if (typeof value === 'number' || typeof value.length === 'number') { - return value.toString().length; - } - return 0; - } - - /** - * Check if variable (and optionally property) is defined. - */ - this.isDefined = function (variable, property) { - var _variable = (typeof variable !== 'undefined'); - if (_variable && typeof property !== 'undefined') { - return (typeof this.getPropertyValue(variable, property) !== 'undefined'); - } - return _variable; - } - - /** - * Check if variable is an array. - */ - this.isArray = function (variable) { - return Object.prototype.toString.call(variable) === '[object Array]'; - } - - /** - * Get unixtime stamp. - * - * @return boolean True if supports, false otherwise - */ - this.unixtime = function (time) { - var date = new Date(); - if (time) { - date = new Date(time); - } - return Math.round(date.getTime() / 1000); - } - - /** - * To array. - */ - this.csvToArray = function (value, _default) { - if (value.length === 0) { - return _default; - } - return typeof value === 'string' ? value.split(_this.options.get('valuesSeparator')) : value; - }, - /** - * A function to take a string written in dot notation style, and use it to - * find a nested object property inside of an object. - * - * Useful in a plugin or module that accepts a JSON array of objects, but - * you want to let the user specify where to find various bits of data - * inside of each custom object instead of forcing a standardized - * property list. - * - * Thanks to https://github.com/sylvainblot for the PR. - * - * @param object object (optional) The object to search - * @param string path A dot notation style path to the value (ie "urls.small") - * @return the value of the property in question - * @see https://github.com/sergiodlopes/jquery-flexdatalist/pull/195 - */ - this.getPropertyValue = function (obj, path) { - if (!obj || typeof path !== 'string') { - return undefined; - } - - var parts = path.split('.'); - while (parts.length && (obj = obj[parts.shift()])); - return obj; - } - /** - * Escape HTML special characters. - * - * @param string str String to escape HTML - * @return string String with HTML special characters escaped - */ - this.escapeHtml = function (str) { - return str - .replace(/&/g, "&") - .replace(/</g, "<") - .replace(/>/g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); - } - - /** - * Plugin warnings for debug. - */ - this.debug = function (msg, data) { - var options = _this.options.get(); - if (!options.debug) { - return; - } - if (!data) { - data = {}; - } - msg = 'Flexdatalist: ' + msg; - console.warn(msg); - console.log($.extend({ - inputName: $this.attr('name'), - options: options - }, data)); - console.log('--- /flexdatalist ---'); - } - - // Go! - this.init(); - }); -} - -jQuery(function ($) { - var $document = $(document); - // Handle results selection list keyboard shortcuts and events. - if (!$document.data('flexdatalist')) { - // Remove results on outside click - $(document).on('mouseup', function (event) { - var $container = $('.flexdatalist-results'), - $target = $container.data('target'); - if ((!$target || !$target.is(':focus')) && !$container.is(event.target) && $container.has(event.target).length === 0) { - $container.remove(); - } - // Keyboard navigation - }).on('keydown', function (event) { - var $ul = $('.flexdatalist-results'), - $li = $ul.find('li'), - $active = $li.filter('.active'), - index = $active.index(), - length = $li.length, - keynum = event.which || event.keyCode; - - if (length === 0) { - return; - } - - // on escape key, remove results - if (keynum === 27) { - $ul.remove(); - return; - } - - // Enter/tab key - if (keynum === 13) { - event.preventDefault(); - $active.trigger('click'); - // Up/Down key - } else if (keynum === 40 || keynum === 38) { - event.preventDefault(); - // Down key - if (keynum === 40) { - if (index < length && $active.nextAll('.item').first().length > 0) { - $active = $active.removeClass('active').nextAll('.item').first().addClass('active'); - } else { - $active = $li.removeClass('active').filter('.item:first').addClass('active'); - } - // Up key - } else if (keynum === 38) { - if (index > 0 && $active.prevAll('.item').first().length > 0) { - $active = $active.removeClass('active').prevAll('.item').first().addClass('active'); - } else { - $active = $li.removeClass('active').filter('.item:last').addClass('active'); - } - } - - $active.trigger('active:flexdatalist.results', [$active.data('item')]); - - // Scroll to - var position = ($active.prev().length === 0 ? $active : $active.prev()).position().top; - $ul.animate({ - scrollTop: position + $ul.scrollTop() - }, 100); - } - }).data('flexdatalist', true); - } - - jQuery('input.flexdatalist:not(.flexdatalist-set):not(.autodiscover-disabled)').flexdatalist(); -}); - -(function ($) { - var jVal = $.fn.val; - $.fn.val = function (value) { - var isFlex = this.length > 0 && typeof this[0].fvalue !== 'undefined'; - if (typeof value === 'undefined') { - return isFlex ? this[0].fvalue.get(true) : jVal.call(this); - } - return isFlex ? this[0].fvalue.set(value) : jVal.call(this, value); - }; -})(jQuery); |