magicsearch
Version:
An input plugin based on jquery.
1,103 lines (1,049 loc) • 45.6 kB
JavaScript
/*!
* MagicSearch - An input plugin based on jquery
*
* Copyright (c) 2016 dingyi1993
*
* Version: 1.0.2
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*
* project link:
* https://github.com/dingyi1993/jquery-magicsearch
*
* home page link:
* https://www.dingyi1993.com/blog/magicsearch
*/
;(function(factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// using AMD; register as anon module
define(['jquery'], factory);
} else {
// no AMD; invoke directly
factory( (typeof(jQuery) != 'undefined') ? jQuery : window.Zepto );
}
}
(function($) {
;
const
//separator of format
SEPARATOR = '%',
//default multi style,unit px
DEFAULT_ITEM_WIDTH = 57,
DEFAULT_ITEM_SPACE = 3,
//default max width when multiple,unit px
DEFAULT_INPUT_MAX_WIDTH = 500,
//max loaded item at a time when show all(min:dropdownMaxItem + 1)
ALL_DROPDOWN_NUM = 100,
//default search box animate duration,unit ms
BOX_ANIMATE_TIME = 200,
//default search delay,unit ms
SEARCH_DELAY = 200,
//default dropdown button width,unit px
DROPDOWN_WIDTH = 24,
//key code
KEY = {
BACKSPACE: 8,
ENTER: 13,
ESC: 27,
SPACE: 32,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
},
//cache doms string
doms = {
wrapper: 'magicsearch-wrapper',
box: 'magicsearch-box',
arrow: 'magicsearch-arrow',
loading: 'magicsearch-loading',
hidden: 'magicsearch-hidden',
items: 'multi-items',
item: 'multi-item',
close: 'multi-item-close',
},
isString = value => $.type(value) === 'string',
//transform string which like this : %username%
formatParse = (format, data) => {
let fields = format.match(new RegExp(`\\${SEPARATOR}[^\\${SEPARATOR}]+\\${SEPARATOR}`, 'g'));
if (! fields) {
return format;
}
for (let i = 0; i < fields.length; i++) {
fields[i] = fields[i].replace(new RegExp('\\' + SEPARATOR, 'g'), '');
}
for (let i = 0; i < fields.length; i++) {
format = format.replace(SEPARATOR + fields[i] + SEPARATOR, data[fields[i]] ? data[fields[i]] : 'error');
}
return format;
},
//delete px at ending
deletePx = value => {
let index = value.lastIndexOf('px');
return index < 0 ? Number(value) : Number(value.substring(0, index));
},
//transform to positive num
transform2PositiveNum = (value, key) => {
if (! $.isNumeric(value)) {
value = MagicSearch.defaults[key];
} else {
value = Math.ceil(Number(value));
if (value <= 0) {
value = MagicSearch.defaults[key];
}
}
return value;
},
//constructor
MagicSearch = function(element, options) {
this.element = element;
this.$element = $(element);
this.options = options;
};
MagicSearch.defaults = {
dataSource: [], //array or string or function
type: '', //string
ajaxOptions: {}, //object
id: '', //string
hidden: false, //boolean
fields: '', //string or array
format: '', //string(function?)
inputFormat: '', //string
maxShow: 5, //int
isClear: true, //boolean
showSelected: true, //boolean
dropdownBtn: false, //boolean
dropdownMaxItem: 8, //int
multiple: false, //boolean
maxItem: true, //boolean or int
showMultiSkin: true, //boolean
multiStyle: {}, //object
multiField: '', //string
focusShow: false, //boolean
noResult: '', //string
skin: '', //string
disableRule: data => false,
success: ($input, data) => true,
afterDelete: ($input, data) => true
};
MagicSearch.prototype = {
init: function() {
//to ensure unique,plus one at init everytime
window.MagicSearch.index += 1;
//you must do those things before ajax
let $input = this.$element;
this.options = $.extend({}, MagicSearch.defaults, this.options);
this.props = {
isFirstGetDataSource: true,
style: $.extend({}, this.element.style),
objDataSource: {},
multiStyle: {
space: DEFAULT_ITEM_SPACE,
width: DEFAULT_ITEM_WIDTH
}
};
//can bind on input text only
if (! this.$element.is('input:text')) {
console.error('magicsearch: Can not bind magicsearch to other elements except input which type is text.');
return false;
}
if (! isString(this.options.id) || this.options.id === '') {
console.error('magicsearch: The option id must be a string which is not empty.');
return false;
}
if (this.options.multiple) {
this.options.isClear = true;
} else if (! this.options.isClear) {
this.options.hidden = false;
}
this.props.styles = {
borderTopWidth: deletePx($input.css('border-top-width')),
borderRightWidth: deletePx($input.css('border-right-width')),
borderBottomWidth: deletePx($input.css('border-bottom-width')),
borderLeftWidth: deletePx($input.css('border-left-width')),
paddingTop: deletePx($input.css('padding-top')),
paddingRight: deletePx($input.css('padding-right')),
paddingBottom: deletePx($input.css('padding-bottom')),
paddingLeft: deletePx($input.css('padding-left')),
maxWidth: deletePx($input.css('max-width')) || DEFAULT_INPUT_MAX_WIDTH
};
this.props.styles.height = $input.height() + this.props.styles.borderTopWidth + this.props.styles.borderBottomWidth + this.props.styles.paddingTop + this.props.styles.paddingBottom;
this.props.styles.sightHeight = this.props.styles.height - this.props.styles.borderTopWidth - this.props.styles.borderBottomWidth;
this.props.styles.width = $input.width() + this.props.styles.borderLeftWidth + this.props.styles.borderRightWidth + this.props.styles.paddingLeft + this.props.styles.paddingRight;
let styles = this.props.styles;
let ids = $input.attr('data-id');
if ($input.parent().attr('data-belong') !== 'magicsearch') {
let $wrapper = $(`<div class="${doms.wrapper + (this.options.skin ? (' ' + this.options.skin) : '')}" data-belong="magicsearch"></div>`);
$wrapper.css('width', styles.width);
$input.wrap($wrapper);
}
let $magicsearch_wrapper = $input.parent();
let inputDisplay = $input.css('display');
//init magicsearch wrapper
$magicsearch_wrapper.css({
// if input's display is inline,regard as inline-block
'display': inputDisplay === 'inline' ? 'inline-block' : inputDisplay,
'float': $input.css('float'),
'margin': $input.css('margin')
});
$magicsearch_wrapper.attr('data-index', window.MagicSearch.index);
//init input
$input.css({
'margin': 0,
'box-sizing': 'border-box',
'width': styles.width,
'height': styles.height
});
if ($input.attr('placeholder')) {
$input.attr('data-placeholder', $input.attr('placeholder'));
}
$input.removeAttr('disabled');
if (this.options.isClear) {
$input.val('');
}
if (ids === undefined) {
$input.attr('data-id', '');
ids = '';
}
//init magicsearch box
if ($magicsearch_wrapper.find('.' + doms.box).length === 0) {
$magicsearch_wrapper.append(`<div class="${doms.box}"></div>`);
}
$magicsearch_wrapper.find('.' + doms.box).css({
'top': styles.height
});
//init hidden
if (this.options.hidden) {
let name = $input.attr('name');
let $hidden = $(`<input class="${doms.hidden}" type="hidden" value="${ids}">`);
if (name) {
$hidden.attr('name', name);
$input.removeAttr('name');
}
$magicsearch_wrapper.append($hidden);
}
//init magicsearch arrow
if (this.options.dropdownBtn) {
let $arrow = $(`<div class="${doms.arrow}"><i></i></div>`);
$input.addClass('dropdown');
$input.css('padding-right', styles.paddingRight + DROPDOWN_WIDTH);
$arrow.css({
'top': styles.borderTopWidth,
'bottom': styles.borderBottomWidth,
'right': styles.borderRightWidth
});
$magicsearch_wrapper.append($arrow);
}
if (this.options.type == 'ajax') {
let $loading = $(`<div class="${doms.loading}"><div></div></div>`);
$magicsearch_wrapper.append($loading);
let loadingTimeout = setTimeout(() => {
$magicsearch_wrapper.find('.' + doms.loading).find('div').show();
}, 500);
let ajaxOptions = {
type: 'GET',
url: this.options.dataSource,
dataType: 'json',
error: () => {
console.error('magicsearch: Error with xhr.Index: ' + window.MagicSearch.index);
},
success: data => { },
};
ajaxOptions = $.extend({}, ajaxOptions, this.options.ajaxOptions);
let success = ajaxOptions.success;
ajaxOptions.success = data => {
clearTimeout(loadingTimeout);
this.options.dataSource = data;
$magicsearch_wrapper.find('.' + doms.loading).remove();
this.initAfterAjax();
success(data);
};
$.ajax(ajaxOptions);
} else {
this.initAfterAjax();
}
return this;
},
initAfterAjax: function() {
let $input = this.$element;
let $magicsearch_wrapper = $input.parent();
let styles = this.props.styles;
let ids = $input.attr('data-id');
if ($.isFunction(this.options.dataSource)) {
this.options.dataSource = this.options.dataSource(this.$element);
}
if (! this.options.dataSource) {
this.options.dataSource = [];
} else if (isString(this.options.dataSource)) {
if (this.options.dataSource.toLowerCase() == 'null') {
this.options.dataSource = [];
} else {
try {
this.options.dataSource = $.parseJSON(this.options.dataSource);
if (! $.isArray(this.options.dataSource)) {
let dataSource = [];
for (let id in this.options.dataSource) {
dataSource.push(this.options.dataSource[id]);
}
this.options.dataSource = dataSource;
}
} catch (err) {
this.options.dataSource = [];
console.error('magicsearch: A problem is occured during parsing dataSource,please check.Index: ' + window.MagicSearch.index);
return false;
}
}
} else if (! $.isArray(this.options.dataSource)) {
let dataSource = [];
for (let id in this.options.dataSource) {
dataSource.push(this.options.dataSource[id]);
}
this.options.dataSource = dataSource;
}
if (isString(this.options.fields)) {
this.options.fields = [this.options.fields === '' ? this.options.id : this.options.fields];
} else if (! $.isArray(this.options.fields)) {
this.options.fields = [this.options.id];
}
if (! isString(this.options.format) || this.options.format === '') {
this.options.format = SEPARATOR + this.options.id + SEPARATOR;
}
this.options.maxShow = transform2PositiveNum(this.options.maxShow, 'maxShow');
if (this.options.dropdownBtn) {
this.options.dropdownMaxItem = transform2PositiveNum(this.options.dropdownMaxItem, 'dropdownMaxItem');
}
if (this.options.multiple) {
if (this.options.maxItem !== true) {
this.options.maxItem = transform2PositiveNum(this.options.maxItem, 'maxItem');
}
if (this.options.showMultiSkin) {
if (! isString(this.options.multiField) || this.options.multiField === '') {
this.options.multiField = this.options.id;
}
}
if (this.options.multiStyle) {
this.props.multiStyle.space = this.options.multiStyle.space ? this.options.multiStyle.space : DEFAULT_ITEM_SPACE;
this.props.multiStyle.width = this.options.multiStyle.width ? this.options.multiStyle.width : DEFAULT_ITEM_WIDTH;
}
}
if (! $.isFunction(this.options.success)) {
this.options.success = ($input, data) => true;
}
if (! $.isFunction(this.options.disableRule)) {
this.options.disableRule = data => false;
}
if (ids !== '') {
this.getDataSource();
}
//init multi items
if (this.options.multiple) {
$input.addClass('multi');
if (this.options.showMultiSkin && $magicsearch_wrapper.find('.' + doms.items).length === 0) {
let $items = $(`<div class="${doms.items}"></div>`);
$items.css({
'top': this.props.multiStyle.space + styles.borderTopWidth,
'left': this.props.multiStyle.space + styles.borderLeftWidth,
'bottom': this.props.multiStyle.space + styles.borderBottomWidth,
'right': this.props.multiStyle.space + styles.borderRightWidth + (this.options.dropdownBtn ? DROPDOWN_WIDTH : 0)
});
$magicsearch_wrapper.append($items);
}
}
if (this.options.multiple) {
let idArr = ids ? ids.split(',') : [];
if (this.options.maxItem !== true && idArr.length >= this.options.maxItem) {
$input.attr('disabled', 'disabled');
$magicsearch_wrapper.addClass('disabled');
}
//init default style
if (this.options.showMultiSkin) {
let hasSetId = [];
for (let i = 0; i < idArr.length; i++) {
hasSetId.push(idArr[i]);
$input.attr('data-id',hasSetId.join(','));
if (this.options.hidden) {
$magicsearch_wrapper.find('.' + doms.hidden).val(hasSetId.join(','));
}
this.appendHtml(this.props.objDataSource[idArr[i]]);
}
}
} else {
if (ids !== '') {
let data = this.props.objDataSource[ids];
let format = this.options.inputFormat ? this.options.inputFormat : this.options.format;
$input.val(formatParse(format, data));
}
}
},
destroy: function() {
let $input = this.$element,
$magicsearch_wrapper = $input.parent();
if ($magicsearch_wrapper.attr('data-belong') === 'magicsearch') {
this.element.style = this.props.style;
$input.css({
'margin': $magicsearch_wrapper.css('margin'),
'padding-top': this.props.styles.paddingTop,
'padding-right': this.props.styles.paddingRight,
'padding-bottom': this.props.styles.paddingBottom,
'padding-left': this.props.styles.paddingLeft,
'height': this.props.styles.height,
'width': this.props.styles.width,
});
let placeholder = $input.attr('data-placeholder');
if (placeholder !== undefined) {
$input.attr('placeholder', placeholder);
$input.removeAttr('data-placeholder');
}
$input.removeClass('dropdown multi').removeAttr('disabled data-id');
$input.siblings().remove();
$input.unwrap();
$input.off();
$input.val('');
}
},
getDataSource: function() {
let dataSource = this.options.dataSource;
if (this.props.isFirstGetDataSource) {
let objDataSource = {};
for (let i = 0; i < dataSource.length; i++) {
for (let key in dataSource[i]) {
this.options.dataSource[i][key] = String(dataSource[i][key]);
}
}
this.props.isFirstGetDataSource = false;
for (let i = 0; i < dataSource.length; i++) {
objDataSource[dataSource[i][this.options.id]] = dataSource[i];
}
this.props.objDataSource = objDataSource;
}
return this.options.dataSource;
},
setData: function() {
let $input = this.$element,
$magicsearch_wrapper = $input.parent(),
$magicsearch_box = $magicsearch_wrapper.find('.' + doms.box),
$magicsearch_arrow = $magicsearch_wrapper.find('.' + doms.arrow),
$ishover = $magicsearch_box.find('li.ishover');
let options = this.options,
ids = $input.attr('data-id'),
data = this.props.objDataSource[$ishover.attr('data-id')];
if (options.multiple) {
if ($magicsearch_box.is(':hidden')) {
return;
}
$input.val('');
let idArr = ids ? ids.split(',') : [];
if (options.maxItem !== true && idArr.length >= options.maxItem) {
return this;
}
idArr.push($ishover.attr('data-id'));
if (options.maxItem !== true && idArr.length == options.maxItem) {
$input.attr('disabled', 'disabled');
$magicsearch_wrapper.addClass('disabled');
}
$input.attr('data-id', idArr.join(','));
if (options.hidden) {
$magicsearch_wrapper.find('.' + doms.hidden).val(idArr.join(','));
}
if (options.showMultiSkin) {
this.appendHtml(data);
}
} else {
$input.val(options.inputFormat ? formatParse(options.inputFormat, data) : $ishover.text());
$input.attr('data-id', $ishover.attr('data-id'));
if (options.hidden) {
$magicsearch_wrapper.find('.' + doms.hidden).val($ishover.attr('data-id'));
}
}
options.success($input, data);
return this;
},
deleteData: function(id) {
let $input = this.$element,
$magicsearch_wrapper = $input.parent(),
$magicsearch_box = $magicsearch_wrapper.find('.' + doms.box),
$magicsearch_arrow = $magicsearch_wrapper.find('.' + doms.arrow);
let that = this,
delIdArr = id ? id.split(',') : [],
ids = $input.attr('data-id'),
idArr = ids ? ids.split(',') : [],
options = this.options,
styles = this.props.styles;
if (! options.multiple) {
id = $input.attr('data-id');
$input.val('').attr('data-id', '');
if (options.hidden) {
$magicsearch_wrapper.find('.' + doms.hidden).val('');
}
options.afterDelete($input, that.props.objDataSource[id]);
return this;
}
let delCallBack = function() {
$(this).remove();
};
for (let i = 0; i < delIdArr.length; i++) {
$magicsearch_wrapper.find(`.${doms.item}[data-id="${delIdArr[i]}"]`).fadeOut(400, delCallBack);
idArr.splice(idArr.indexOf(delIdArr[i]), 1);
}
setTimeout(() => {
let maxLineItem = parseInt((styles.maxWidth - (options.dropdownBtn ? DROPDOWN_WIDTH : 0) - styles.borderLeftWidth - styles.borderRightWidth - that.props.multiStyle.space) / (that.props.multiStyle.width + that.props.multiStyle.space));
let lineNum = parseInt(idArr.length / maxLineItem);
$input.attr('data-id', idArr.join(','));
if (options.hidden) {
$magicsearch_wrapper.find('.' + doms.hidden).val(idArr.join(','));
}
$magicsearch_wrapper.removeClass('disabled');
$input.removeAttr('disabled');
if (options.showMultiSkin) {
if (idArr.length === 0 && $input.attr('data-placeholder')) {
$input.attr('placeholder', $input.attr('data-placeholder'));
}
$input.css('padding-left', (idArr.length % maxLineItem) * (that.props.multiStyle.width + that.props.multiStyle.space) + styles.paddingLeft);
$input.css('height', styles.height + lineNum * styles.sightHeight);
$input.css('padding-top', lineNum * styles.sightHeight + styles.paddingTop);
$magicsearch_box.css('top', styles.height + lineNum * styles.sightHeight);
if (lineNum === 0) {
let minWidth = (idArr.length % maxLineItem + 1) * (that.props.multiStyle.width + that.props.multiStyle.space) + that.props.multiStyle.space * 2 + styles.borderLeftWidth + styles.borderRightWidth + (options.dropdownBtn ? DROPDOWN_WIDTH : 0);
$input.css('min-width', minWidth);
$magicsearch_wrapper.css('min-width', minWidth);
}
}
if (delIdArr.length == 1) {
options.afterDelete($input, that.props.objDataSource[delIdArr[0]]);
}
}, 400);
return this;
},
searchData: function(isAll, isScroll) {
let $input = this.$element,
$magicsearch_box = $input.parent().find('.' + doms.box);
let options = this.options,
dataJson = this.getDataSource(),
ids = $input.attr('data-id'),
idArr = ids ? ids.split(',') : [],
inputVal = $.trim($input.val()),
htmlStr = '',
data = [],
isAppendHtml = true;
//if is not scroll,clean all items first
if (isScroll !== true) {
$magicsearch_box.html('');
}
if (inputVal === '' && ! isAll) {
return this;
}
//get the match data
if (isAll) {
let page = $input.data('page') || 1;
data = dataJson.slice(0);
//skip selected data
if (! options.showSelected) {
for (let i = 0, num = 0; i < data.length; i++) {
let index = idArr.indexOf(data[i][options.id]);
if (index > -1) {
data.splice(i, 1);
num++;
i--;
if (num == idArr.length) {
break;
}
}
}
}
//if page less than total page,page plus one
if (page <= Math.ceil(data.length / ALL_DROPDOWN_NUM)) {
$input.data('page', page + 1);
}
data = data.slice((page - 1) * ALL_DROPDOWN_NUM, page * ALL_DROPDOWN_NUM);
//will not appending html if data's length is zero when scrolling
if (page != 1 && data.length === 0) {
isAppendHtml = false;
}
} else {
let inputVals = [...new Set(inputVal.toLowerCase().split(' '))];
let inputData = [];
for (let i = 0; i < inputVals.length; i++) {
if (inputVals[i] === '') {
continue;
}
inputData.push({value: inputVals[i], flag: false});
}
//search match data
for (let i = 0; i < dataJson.length; i++) {
//skip selected
if (! options.showSelected) {
if (idArr.includes(dataJson[i][options.id])) {
continue;
}
}
inputData = inputData.map(item => {
item.flag = false;
return item;
});
for (let j = 0; j < options.fields.length; j++) {
for (let k = 0; k < inputData.length; k++) {
if (dataJson[i][options.fields[j]] !== null && dataJson[i][options.fields[j]].toLowerCase().includes(inputData[k].value)) {
inputData[k].flag = true;
}
}
let isMatch = inputData.every(item => item.flag);
if (isMatch) {
data.push(dataJson[i]);
break;
}
}
if (data.length >= options.maxShow) {
break;
}
}
}
//generate html string
if (data.length === 0) {
let noResult = options.noResult ? options.noResult : '未搜索到结果';
htmlStr = `<span class="no-result">${noResult}</span>`;
} else {
//delete empty input
let inputVals = [...new Set(inputVal.split(' '))].filter(item => item !== '');
let tempArr = [];
for (let i = 0; i < inputVals.length - 1; i++) {
for (let j = i + 1; j < inputVals.length; j++) {
tempArr.push(inputVals[i] + ' ' + inputVals[j]);
}
}
inputVals = inputVals.concat(tempArr);
//locate highlight chars
let dataHighlight;
if (! isAll) {
dataHighlight = $.extend(true, [], data);
data.forEach((item, index) => {
options.fields.forEach(field => {
let posArr = [];
if (item[field] !== null) {
for (let i = 0; i < item[field].length; i++) {
posArr[i] = 0;
}
inputVals.forEach(value => {
let position = item[field].toLowerCase().indexOf(value.toLowerCase());
if (position > -1) {
for (let i = position; i < value.length + position; i++) {
posArr[i] = 1;
}
}
});
}
let tmpPosArr = [];
let hasStarted = false, start = -1, length = 0;
for (let i = posArr.length - 1; i >= 0; i--) {
if (posArr[i] == 1) {
if (! hasStarted) {
hasStarted = true;
start = i;
} else {
start--;
}
length++;
if (i === 0) {
tmpPosArr.push({start: start, length: length});
}
} else {
if (hasStarted) {
hasStarted = false;
tmpPosArr.push({start: start, length: length});
length = 0;
}
}
}
if (dataHighlight[index][field] !== undefined) {
dataHighlight[index][field] = tmpPosArr;
}
});
});
}
htmlStr += '<ul>';
data.forEach((item, index) => {
let tmpItem = $.extend({}, item);
htmlStr += '<li class="';
htmlStr += options.disableRule(item) ? 'disabled' : 'enabled';
if (options.showSelected) {
htmlStr += idArr.includes(item[options.id]) ? ' selected' : '';
}
htmlStr += `" data-id="${item[options.id] === undefined ? '' : item[options.id]}"`;
if (! isAll) {
options.fields.forEach(field => {
if (item[field] !== null) {
dataHighlight[index][field].forEach(value => {
let matchStr = tmpItem[field].substr(value.start, value.length);
tmpItem[field] = tmpItem[field].replace(new RegExp(matchStr, 'i'), `<span class="keyword">${matchStr}</span>`);
});
}
});
}
htmlStr += ` title="${formatParse(options.format, item)}">${formatParse(options.format, tmpItem)}</li>`;
});
htmlStr += '</ul>';
}
//create dom
if (isAll) {
if (isAppendHtml) {
$magicsearch_box.html($magicsearch_box.html() + htmlStr);
}
$magicsearch_box.addClass('all');
} else {
$magicsearch_box.html(htmlStr);
$magicsearch_box.removeClass('all').css('max-height', 'none');
}
return this;
},
showSearchBox: function(callback) {
let $input = this.$element,
$magicsearch_wrapper = $input.parent(),
$magicsearch_box = $magicsearch_wrapper.find('.' + doms.box);
if ($magicsearch_box.is(':visible')) {
return false;
}
//rotate the dropdown button 180deg
if (this.options.dropdownBtn) {
let $magicsearch_arrow = $magicsearch_wrapper.find('.' + doms.arrow);
$magicsearch_arrow.removeClass('arrow-rotate-360');
$magicsearch_arrow.addClass('arrow-rotate-180');
}
$magicsearch_box.slideDown(BOX_ANIMATE_TIME, callback);
return this;
},
hideSearchBox: function(isClear) {
let $input = this.$element,
$magicsearch_wrapper = $input.parent(),
$magicsearch_box = $magicsearch_wrapper.find('.' + doms.box);
if (! $magicsearch_box.is(':visible')) {
return false;
}
if (isClear === undefined) {
if (this.options.isClear && (this.options.multiple || $input.attr('data-id') === '')) {
$input.val('');
}
} else {
if (isClear) {
$input.val('');
}
}
//rotate the dropdown button 360deg
if (this.options.dropdownBtn) {
let $magicsearch_arrow = $magicsearch_wrapper.find('.' + doms.arrow);
$magicsearch_arrow.removeClass('arrow-rotate-180');
$magicsearch_arrow.addClass('arrow-rotate-360');
}
setTimeout(() => {
$magicsearch_box.scrollTop(0);
}, BOX_ANIMATE_TIME - 1);
$magicsearch_box.slideUp(BOX_ANIMATE_TIME, () => {
$magicsearch_box.html('');
});
return this;
},
appendHtml: function(data) {
let $input = this.$element,
$magicsearch_wrapper = $input.parent(),
$magicsearch_box = $magicsearch_wrapper.find('.' + doms.box),
$magicsearch_arrow = $magicsearch_wrapper.find('.' + doms.arrow),
$ishover = $magicsearch_box.find('li.ishover');
let options = this.options,
styles = this.props.styles,
that = this;
let idArr = $input.attr('data-id').split(',');
if ($input.attr('placeholder')) {
$input.removeAttr('placeholder');
}
let maxLineItem = parseInt((styles.maxWidth - (options.dropdownBtn ? DROPDOWN_WIDTH : 0) - styles.borderLeftWidth - styles.borderRightWidth - that.props.multiStyle.space) / (that.props.multiStyle.width + that.props.multiStyle.space));
//create item and insert into items
let $item = $(`<div class="${doms.item}" data-id="${data[options.id]}" title="${formatParse(options.format, data)}"><span>${formatParse(SEPARATOR + options.multiField + SEPARATOR, data)}</span><a class="${doms.close}" data-id="${data[options.id]}" href="javascript:;"></a></div>`);
$item.css({
'height': styles.sightHeight - that.props.multiStyle.space * 2,
'width': that.props.multiStyle.width,
'margin-bottom': that.props.multiStyle.space * 2,
'margin-right': that.props.multiStyle.space,
'line-height': (styles.sightHeight - that.props.multiStyle.space * 2 - 2) + 'px'
});
$item.find('.' + doms.close).css({
'top': parseInt((styles.sightHeight - that.props.multiStyle.space * 2 - 2 - 12) / 2)
});
let lineNum = parseInt(idArr.length / maxLineItem);
$input.css('padding-left', styles.paddingLeft + (idArr.length % maxLineItem) * (that.props.multiStyle.width + that.props.multiStyle.space));
if (idArr.length % maxLineItem === 0) {
$input.css('height', styles.height + lineNum * styles.sightHeight);
$input.css('padding-top', lineNum * styles.sightHeight);
$magicsearch_box.css('top', styles.height + lineNum * styles.sightHeight);
} else {
if (lineNum === 0) {
let minWidth = (idArr.length % maxLineItem + 1) * (that.props.multiStyle.width + that.props.multiStyle.space) + that.props.multiStyle.space * 2 + styles.borderLeftWidth + styles.borderRightWidth + (options.dropdownBtn ? DROPDOWN_WIDTH : 0);
$input.css('min-width', minWidth);
$magicsearch_wrapper.css('min-width', minWidth);
}
}
//bind click event on delete button
$item.find('.' + doms.close).click(function() {
that.deleteData($(this).attr('data-id')).hideSearchBox();
});
$magicsearch_wrapper.find('.' + doms.items).append($item);
}
};
$.fn.magicsearch = function(options) {
let hasDropdownBtn = false;
let searchTimeout = null;
let preInput = '';
let jqo = this.each(function() {
let $this = $(this);
let magicSearch = $.data(this, 'magicsearch');
if (magicSearch) {
magicSearch.destroy();
}
magicSearch = new MagicSearch(this, options);
if (magicSearch.init() === false) {
return;
}
$.data(this, 'magicsearch', magicSearch);
let selfOptions = magicSearch.options;
let $magicsearch_wrapper = $this.parent(),
$magicsearch_box = $magicsearch_wrapper.find('.' + doms.box);
let dropdown = () => {
if ($magicsearch_wrapper.hasClass('disabled')) {
return false;
}
if ($magicsearch_box.is(':visible')) {
magicSearch.hideSearchBox();
} else {
$magicsearch_box.css({
'max-height': selfOptions.dropdownMaxItem * 30 + 8
});
$magicsearch_box.unbind('scroll');
$this.data('page', 1);
magicSearch.searchData(true).showSearchBox(() => {
$magicsearch_box.on('scroll', function(e) {
if ((this.scrollHeight - $(this).scrollTop()) < 300) {
magicSearch.searchData(true, true);
}
});
});
}
};
$this.off().on('keyup', function(e) {
let $_this = $(this);
if (e.which == KEY.ESC) {
$_this.val('').focus();
magicSearch.hideSearchBox();
} else if (e.which == KEY.DOWN) {
let $li = $magicsearch_box.find('li');
let $ishover = $magicsearch_box.find('li.ishover');
if ($li.length > 0) {
if ($ishover.length > 0) {
$ishover.toggleClass('ishover');
if ($ishover.next().length > 0) {
$ishover.next().toggleClass('ishover');
} else {
$magicsearch_box.find('li:first').toggleClass('ishover');
}
} else {
$magicsearch_box.find('li:first').toggleClass('ishover');
}
}
return false;
} else if (e.which == KEY.UP) {
let $li = $magicsearch_box.find('li');
let $ishover = $magicsearch_box.find('li.ishover');
if ($li.length > 0) {
if ($ishover.length > 0) {
$ishover.toggleClass('ishover');
if ($ishover.prev().length > 0) {
$ishover.prev().toggleClass('ishover');
} else {
$magicsearch_box.find('li:last').toggleClass('ishover');
}
} else {
$magicsearch_box.find('li:last').toggleClass('ishover');
}
}
return false;
} else if (e.which == KEY.ENTER) {
let $ishover = $magicsearch_box.find('li.ishover');
if ($ishover.length > 0) {
$ishover.trigger('click');
}
} else if (e.which == KEY.LEFT || e.which == KEY.RIGHT) {
return true;
} else {
let currentInput = $_this.val();
if ($.trim(preInput) == $.trim(currentInput)) {
return true;
}
if (currentInput !== '') {
if ($.trim(currentInput) === '') {
magicSearch.hideSearchBox((e.which == KEY.BACKSPACE || e.which == KEY.SPACE) ? false : undefined);
return false;
}
//hide search box when key up
if (! selfOptions.multiple && $_this.attr('data-id') !== '') {
magicSearch.hideSearchBox();
return;
}
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
magicSearch.searchData().showSearchBox();
}, SEARCH_DELAY);
} else {
magicSearch.hideSearchBox();
}
}
})
.on('keydown', function(e) {
let $_this = $(this);
if (e.which == KEY.ESC) {
} else if (e.which == KEY.UP) {
return false;
} else if (e.which == KEY.DOWN) {
return false;
} else if (e.which == KEY.ENTER) {
return ! $magicsearch_box.is(':visible');
} else if (e.which == KEY.BACKSPACE) {
preInput = $_this.val();
if (selfOptions.multiple) {
let $last_multi_item = $magicsearch_wrapper.find('.' + doms.items + ' .' + doms.item + ':last');
if (selfOptions.showMultiSkin && $_this.val() === '') {
magicSearch.deleteData($last_multi_item.attr('data-id')).hideSearchBox();
}
} else {
if ($(this).attr('data-id') !== '') {
magicSearch.deleteData();
magicSearch.hideSearchBox();
}
}
} else if (e.which == KEY.LEFT || e.which == KEY.RIGHT) {
return true;
} else {
preInput = $_this.val();
if (preInput !== '') {
//set empty value when key down
if (! selfOptions.multiple && $_this.attr('data-id') !== '') {
magicSearch.deleteData();
return;
}
}
}
})
.on('focus', function() {
$magicsearch_wrapper.addClass('focus');
if (! selfOptions.isClear && $this.val() !== '' && $this.attr('data-id') === '') {
magicSearch.searchData().showSearchBox();
} else if (selfOptions.focusShow) {
dropdown();
}
})
.on('blur', function() {
$magicsearch_wrapper.removeClass('focus');
magicSearch.hideSearchBox();
});
$magicsearch_box.off().on('mousedown', 'ul', () => false)
.on('mouseenter', 'li', function() {
$(this).parent().find('li.ishover').removeClass('ishover');
$(this).addClass('ishover');
})
.on('mouseleave', 'li', function() {
$(this).removeClass('ishover');
})
.on('click', 'li', function() {
let $li = $(this);
if ($li.hasClass('selected') && selfOptions.multiple) {
magicSearch.deleteData($li.attr('data-id')).hideSearchBox();
} else {
magicSearch.setData().hideSearchBox();
}
});
//When the option of dropdownBtn is true,bind the related event
if (selfOptions.dropdownBtn) {
hasDropdownBtn = true;
$magicsearch_wrapper.on('click', '.' + doms.arrow, () => dropdown());
}
$this.on('clear', function() {
$this.val('');
if (selfOptions.multiple) {
if (selfOptions.showMultiSkin) {
magicSearch.deleteData($this.attr('data-id'));
} else {
$this.attr('data-id', '');
if (selfOptions.hidden) {
$magicsearch_wrapper.find('.' + doms.hidden).val('');
}
}
} else {
$this.attr('data-id', '');
if (selfOptions.hidden) {
$magicsearch_wrapper.find('.' + doms.hidden).val('');
}
}
})
.on('update', function(e, options) {
//update dataSource
if (options.dataSource !== undefined) {
let tmpDataSource = options.dataSource;
if (isString(tmpDataSource)) {
if (options.dataSource.toLowerCase() == 'null') {
tmpDataSource = [];
} else {
try {
tmpDataSource = $.parseJSON(options.dataSource);
if (! $.isArray(tmpDataSource)) {
let dataSource = [];
for (let id in tmpDataSource) {
dataSource.push(tmpDataSource[id]);
}
tmpDataSource = dataSource;
}
} catch (err) {
tmpDataSource = [];
console.error('magicsearch: The dataSource you updated is wrong,please check.');
}
}
}
magicSearch.props.isFirstGetDataSource = true;
magicSearch.options.dataSource = tmpDataSource;
}
})
.on('set', function(e, options) {
const originId = magicSearch.$element.attr('data-id');
const multi = magicSearch.options.multiple;
magicSearch.destroy();
magicSearch.$element.attr('data-id', (options.override || !multi) ? options.id : [...new Set((originId ? originId.split(',') : []).concat(options.id.split(',')))]);
magicSearch.$element.magicsearch(magicSearch.options)
})
.on('destroy', function() {
magicSearch.destroy();
});
$magicsearch_wrapper.on('click', '.' + doms.items, function(e) {
if ($(e.target).is('.' + doms.items)) {
$this.focus();
}
})
.on('mousedown', '.' + doms.items, function () {
return false;
});
});
if (hasDropdownBtn && ! window.MagicSearch.hasBindBody) {
$('body').on('mousedown', function(e) {
let $target = $(e.target),
$magicsearch_wrapper = $(this).find(`.${doms.wrapper} .${doms.box}:visible`).first().parent(),
$input = $magicsearch_wrapper.find('input:text');
let index = $magicsearch_wrapper.attr('data-index');
if (index !== undefined && ! $target.is(`.${doms.wrapper}[data-index="${index}"] *`)) {
$.data($input.get(0), 'magicsearch').hideSearchBox();
}
});
window.MagicSearch.hasBindBody = true;
}
return jqo;
};
window.MagicSearch = {
v: '1.0.2',
index: 0,
hasBindBody: false
};
}));