|  | @ -0,0 +1,1013 @@
 | 
	
		
			
				|  |  | /**
 | 
	
		
			
				|  |  |  * Bootstrap Search Suggest
 | 
	
		
			
				|  |  |  * @desc    这是一个基于 bootstrap 按钮式下拉菜单组件的搜索建议插件,必须使用于按钮式下拉菜单组件上。
 | 
	
		
			
				|  |  |  * @author  renxia <lzwy0820#qq.com>
 | 
	
		
			
				|  |  |  * @github  https://github.com/lzwme/bootstrap-suggest-plugin.git
 | 
	
		
			
				|  |  |  * @since   2014-10-09
 | 
	
		
			
				|  |  |  *===============================================================================
 | 
	
		
			
				|  |  |  * (c) Copyright 2014-2016 http://lzw.me All Rights Reserved.
 | 
	
		
			
				|  |  |  ********************************************************************************/
 | 
	
		
			
				|  |  | (function (factory) {
 | 
	
		
			
				|  |  |     if (typeof define === "function" && define.amd) {
 | 
	
		
			
				|  |  |         define(['jquery'], factory);
 | 
	
		
			
				|  |  |     } else if (typeof exports === 'object' && typeof module === 'object') {
 | 
	
		
			
				|  |  |         factory(require('jquery'));
 | 
	
		
			
				|  |  |     } else if (window.jQuery) {
 | 
	
		
			
				|  |  |         factory(window.jQuery);
 | 
	
		
			
				|  |  |     } else {
 | 
	
		
			
				|  |  |         throw new Error('Not found jQuery.');
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  | })(function($) {
 | 
	
		
			
				|  |  |     var VERSION = 'VERSION_PLACEHOLDER';
 | 
	
		
			
				|  |  |     var $window = $(window);
 | 
	
		
			
				|  |  |     var isIe = 'ActiveXObject' in window; // 用于对 IE 的兼容判断
 | 
	
		
			
				|  |  |     var inputLock; // 用于中文输入法输入时锁定搜索
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |     // ie 下和 chrome 51 以上浏览器版本,出现滚动条时不计算 padding
 | 
	
		
			
				|  |  |     var chromeVer = navigator.userAgent.match(/Chrome\/(\d+)/);
 | 
	
		
			
				|  |  |     if (chromeVer) {
 | 
	
		
			
				|  |  |         chromeVer = +chromeVer[1];
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     var notNeedCalcPadding = isIe || chromeVer > 51;
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |     // 一些常量
 | 
	
		
			
				|  |  |     var BSSUGGEST = 'bsSuggest';
 | 
	
		
			
				|  |  |     var onDataRequestSuccess = 'onDataRequestSuccess';
 | 
	
		
			
				|  |  |     var DISABLED = 'disabled';
 | 
	
		
			
				|  |  |     var TRUE = true;
 | 
	
		
			
				|  |  |     var FALSE = false;
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 错误处理
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function handleError(e1, e2) {
 | 
	
		
			
				|  |  |         if (!window.console || !window.console.trace) {
 | 
	
		
			
				|  |  |             return;
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  |         console.trace(e1);
 | 
	
		
			
				|  |  |         if (e2) {
 | 
	
		
			
				|  |  |             console.trace(e2);
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 获取当前 tr 列的关键字数据
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function getPointKeyword($list) {
 | 
	
		
			
				|  |  |         return $list.data();
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 设置或获取输入框的 alt 值
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function setOrGetAlt($input, val) {
 | 
	
		
			
				|  |  |         return val !== undefined ? $input.attr('alt', val) : $input.attr('alt');
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 设置或获取输入框的 data-json 值
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function setJson($input, val) {
 | 
	
		
			
				|  |  |         return val !== undefined ? $input.attr('data-json', val) : $input.attr('data-json');
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 设置或获取输入框的 data-id 值
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function setOrGetDataId($input, val) {
 | 
	
		
			
				|  |  |         return val !== (void 0) ? $input.attr('data-id', val) : $input.attr('data-id');
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 设置选中的值
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function setValue($input, keywords, options) {
 | 
	
		
			
				|  |  |         if (!keywords || !keywords.key) {
 | 
	
		
			
				|  |  |             return;
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         var separator = options.separator || ',',
 | 
	
		
			
				|  |  |             inputValList,
 | 
	
		
			
				|  |  |             inputIdList,
 | 
	
		
			
				|  |  |             dataId = setOrGetDataId($input);
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         if (options && options.multiWord) {
 | 
	
		
			
				|  |  |             inputValList = $input.val().split(separator);
 | 
	
		
			
				|  |  |             inputValList[inputValList.length - 1] = keywords.key;
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             //多关键字检索支持设置id --- 存在 bug,不建议使用
 | 
	
		
			
				|  |  |             if (!dataId) {
 | 
	
		
			
				|  |  |                 inputIdList = [keywords.id];
 | 
	
		
			
				|  |  |             } else {
 | 
	
		
			
				|  |  |                 inputIdList = dataId.split(separator);
 | 
	
		
			
				|  |  |                 inputIdList.push(keywords.id);
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             setOrGetDataId($input, inputIdList.join(separator))
 | 
	
		
			
				|  |  |                 .val(inputValList.join(separator))
 | 
	
		
			
				|  |  |                 .focus();
 | 
	
		
			
				|  |  |         } else {
 | 
	
		
			
				|  |  |             setOrGetDataId($input, keywords.id || '').val(keywords.key).focus();
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         $input.data('pre-val', $input.val())
 | 
	
		
			
				|  |  |             .trigger('onSetSelectValue', [keywords, (options.data.value || options._lastData.value)[keywords.index]]);
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 调整选择菜单位置
 | 
	
		
			
				|  |  |      * @param {Object} $input
 | 
	
		
			
				|  |  |      * @param {Object} $dropdownMenu
 | 
	
		
			
				|  |  |      * @param {Object} options
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function adjustDropMenuPos($input, $dropdownMenu, options) {
 | 
	
		
			
				|  |  |         if (!$dropdownMenu.is(':visible')) {
 | 
	
		
			
				|  |  |             return;
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         var $parent = $input.parent();
 | 
	
		
			
				|  |  |         var parentHeight = $parent.height();
 | 
	
		
			
				|  |  |         var parentWidth = $parent.width();
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         if (options.autoDropup) {
 | 
	
		
			
				|  |  |             setTimeout(function() {
 | 
	
		
			
				|  |  |                 var offsetTop = $input.offset().top;
 | 
	
		
			
				|  |  |                 var winScrollTop = $window.scrollTop();
 | 
	
		
			
				|  |  |                 var menuHeight = $dropdownMenu.height();
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                 if ( // 自动判断菜单向上展开
 | 
	
		
			
				|  |  |                     ($window.height() + winScrollTop - offsetTop) < menuHeight && // 假如向下会撑长页面
 | 
	
		
			
				|  |  |                     offsetTop > (menuHeight + winScrollTop) // 而且向上不会撑到顶部
 | 
	
		
			
				|  |  |                 ) {
 | 
	
		
			
				|  |  |                     $parent.addClass('dropup');
 | 
	
		
			
				|  |  |                 } else {
 | 
	
		
			
				|  |  |                     $parent.removeClass('dropup');
 | 
	
		
			
				|  |  |                 }
 | 
	
		
			
				|  |  |             }, 10);
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         // 列表对齐方式
 | 
	
		
			
				|  |  |         var dmcss = {};
 | 
	
		
			
				|  |  |         if (options.listAlign === 'left') {
 | 
	
		
			
				|  |  |             dmcss = {
 | 
	
		
			
				|  |  |                 'left': $input.siblings('div').width() - parentWidth,
 | 
	
		
			
				|  |  |                 'right': 'auto'
 | 
	
		
			
				|  |  |             };
 | 
	
		
			
				|  |  |         } else if (options.listAlign === 'right') {
 | 
	
		
			
				|  |  |             dmcss = {
 | 
	
		
			
				|  |  |                 'left': 'auto',
 | 
	
		
			
				|  |  |                 'right': 0
 | 
	
		
			
				|  |  |             };
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         // ie 下,不显示按钮时的 top/bottom
 | 
	
		
			
				|  |  |         if (isIe && !options.showBtn) {
 | 
	
		
			
				|  |  |             if (!$parent.hasClass('dropup')) {
 | 
	
		
			
				|  |  |                 dmcss.top = parentHeight;
 | 
	
		
			
				|  |  |                 dmcss.bottom = 'auto';
 | 
	
		
			
				|  |  |             } else {
 | 
	
		
			
				|  |  |                 dmcss.top = 'auto';
 | 
	
		
			
				|  |  |                 dmcss.bottom = parentHeight;
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         // 是否自动最小宽度
 | 
	
		
			
				|  |  |         if (!options.autoMinWidth) {
 | 
	
		
			
				|  |  |             dmcss.minWidth = parentWidth;
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  |         /* else {
 | 
	
		
			
				|  |  |             dmcss['width'] = 'auto';
 | 
	
		
			
				|  |  |         }*/
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         $dropdownMenu.css(dmcss);
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         return $input;
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 设置输入框背景色
 | 
	
		
			
				|  |  |      * 当设置了 indexId,而输入框的 data-id 为空时,输入框加载警告色
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function setBackground($input, options) {
 | 
	
		
			
				|  |  |         var inputbg, bg, warnbg;
 | 
	
		
			
				|  |  |         if ((options.indexId === -1 && !options.idField) || options.multiWord) {
 | 
	
		
			
				|  |  |             return $input;
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         bg = options.inputBgColor;
 | 
	
		
			
				|  |  |         warnbg = options.inputWarnColor;
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         var curVal = $input.val();
 | 
	
		
			
				|  |  |         var preVal = $input.data('pre-val');
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         if (setOrGetDataId($input) || !curVal) {
 | 
	
		
			
				|  |  |             $input.css('background', bg || '');
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             if (!curVal && preVal) {
 | 
	
		
			
				|  |  |                 $input.trigger('onUnsetSelectValue').data('pre-val', '');
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             return $input;
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         inputbg = $input.css('backgroundColor').replace(/ /g, '').split(',', 3).join(',');
 | 
	
		
			
				|  |  |         // 自由输入的内容,设置背景色
 | 
	
		
			
				|  |  |         if (!~warnbg.indexOf(inputbg)) {
 | 
	
		
			
				|  |  |             $input.trigger('onUnsetSelectValue') // 触发取消data-id事件
 | 
	
		
			
				|  |  |                 .data('pre-val', '')
 | 
	
		
			
				|  |  |                 .css('background', warnbg);
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         return $input;
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 调整滑动条
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function adjustScroll($input, $dropdownMenu, options) {
 | 
	
		
			
				|  |  |         // 控制滑动条
 | 
	
		
			
				|  |  |         var $hover = $input.parent().find('tbody tr.' + options.listHoverCSS),
 | 
	
		
			
				|  |  |             pos, maxHeight;
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         if ($hover.length) {
 | 
	
		
			
				|  |  |             pos = ($hover.index() + 3) * $hover.height();
 | 
	
		
			
				|  |  |             maxHeight = +$dropdownMenu.css('maxHeight').replace('px', '');
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             if (pos > maxHeight || $dropdownMenu.scrollTop() > maxHeight) {
 | 
	
		
			
				|  |  |                 pos = pos - maxHeight;
 | 
	
		
			
				|  |  |             } else {
 | 
	
		
			
				|  |  |                 pos = 0;
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             $dropdownMenu.scrollTop(pos);
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 解除所有列表 hover 样式
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function unHoverAll($dropdownMenu, options) {
 | 
	
		
			
				|  |  |         $dropdownMenu.find('tr.' + options.listHoverCSS).removeClass(options.listHoverCSS);
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 验证 $input 对象是否符合条件
 | 
	
		
			
				|  |  |      *   1. 必须为 bootstrap 下拉式菜单
 | 
	
		
			
				|  |  |      *   2. 必须未初始化过
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function checkInput($input, $dropdownMenu, options) {
 | 
	
		
			
				|  |  |         if (
 | 
	
		
			
				|  |  |             !$dropdownMenu.length || // 过滤非 bootstrap 下拉式菜单对象
 | 
	
		
			
				|  |  |             $input.data(BSSUGGEST) // 是否已经初始化的检测
 | 
	
		
			
				|  |  |         ) {
 | 
	
		
			
				|  |  |             return FALSE;
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         $input.data(BSSUGGEST, {
 | 
	
		
			
				|  |  |             options: options
 | 
	
		
			
				|  |  |         });
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         return TRUE;
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 数据格式检测
 | 
	
		
			
				|  |  |      * 检测 ajax 返回成功数据或 data 参数数据是否有效
 | 
	
		
			
				|  |  |      * data 格式:{"value": [{}, {}...]}
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function checkData(data) {
 | 
	
		
			
				|  |  |         var isEmpty = TRUE, o;
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         for (o in data) {
 | 
	
		
			
				|  |  |             if (o === 'value') {
 | 
	
		
			
				|  |  |                 isEmpty = FALSE;
 | 
	
		
			
				|  |  |                 break;
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  |         if (isEmpty) {
 | 
	
		
			
				|  |  |             handleError('返回数据格式错误!');
 | 
	
		
			
				|  |  |             return FALSE;
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  |         if (!data.value.length) {
 | 
	
		
			
				|  |  |             // handleError('返回数据为空!');
 | 
	
		
			
				|  |  |             return FALSE;
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         return data;
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 判断字段名是否在 options.effectiveFields 配置项中
 | 
	
		
			
				|  |  |      * @param  {String} field   要判断的字段名
 | 
	
		
			
				|  |  |      * @param  {Object} options
 | 
	
		
			
				|  |  |      * @return {Boolean}        effectiveFields 为空时始终返回 true
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function inEffectiveFields(field, options) {
 | 
	
		
			
				|  |  |         var effectiveFields = options.effectiveFields;
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         return !(field === '__index' ||
 | 
	
		
			
				|  |  |             effectiveFields.length &&
 | 
	
		
			
				|  |  |             !~$.inArray(field, effectiveFields));
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 判断字段名是否在 options.searchFields 搜索字段配置中
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function inSearchFields(field, options) {
 | 
	
		
			
				|  |  |         return ~$.inArray(field, options.searchFields);
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 通过下拉菜单显示提示文案
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function showTip(tip, $input, $dropdownMenu, options) {
 | 
	
		
			
				|  |  |         $dropdownMenu.html('<div style="padding:10px 5px 5px">' + tip + '</div>').show();
 | 
	
		
			
				|  |  |         adjustDropMenuPos($input, $dropdownMenu, options);
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 下拉列表刷新
 | 
	
		
			
				|  |  |      * 作为 fnGetData 的 callback 函数调用
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function refreshDropMenu($input, data, options) {
 | 
	
		
			
				|  |  |         var $dropdownMenu = $input.parent().find('ul:eq(0)'),
 | 
	
		
			
				|  |  |             len, i, field, index = 0,
 | 
	
		
			
				|  |  |             tds,
 | 
	
		
			
				|  |  |             html = ['<table class="table table-condensed table-sm" style="margin:0">'],
 | 
	
		
			
				|  |  |             idValue, keyValue; // 作为输入框 data-id 和内容的字段值
 | 
	
		
			
				|  |  |         var dataList = data.value;
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         if (!data || !(len = dataList.length)) {
 | 
	
		
			
				|  |  |             if (options.emptyTip) {
 | 
	
		
			
				|  |  |                 showTip(options.emptyTip, $input, $dropdownMenu, options);
 | 
	
		
			
				|  |  |             } else {
 | 
	
		
			
				|  |  |                 $dropdownMenu.empty().hide();
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  |             return $input;
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         // 相同数据,不用继续渲染了
 | 
	
		
			
				|  |  |         if (
 | 
	
		
			
				|  |  |             options._lastData &&
 | 
	
		
			
				|  |  |             JSON.stringify(options._lastData) === JSON.stringify(data) &&
 | 
	
		
			
				|  |  |             $dropdownMenu.find('tr').length === len
 | 
	
		
			
				|  |  |         ) {
 | 
	
		
			
				|  |  |             $dropdownMenu.show();
 | 
	
		
			
				|  |  |             return adjustDropMenuPos($input, $dropdownMenu, options);
 | 
	
		
			
				|  |  |             // return $input;
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  |         options._lastData = data;
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         // 生成表头
 | 
	
		
			
				|  |  |         if (options.showHeader) {
 | 
	
		
			
				|  |  |             html.push('<thead><tr>');
 | 
	
		
			
				|  |  |             for (field in dataList[0]) {
 | 
	
		
			
				|  |  |                 if (!inEffectiveFields(field, options)) {
 | 
	
		
			
				|  |  |                     continue;
 | 
	
		
			
				|  |  |                 }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                 html.push('<th>', (options.effectiveFieldsAlias[field] || field),
 | 
	
		
			
				|  |  |                     index === 0 ? ('(' + len + ')') : '' , // 表头第一列记录总数
 | 
	
		
			
				|  |  |                     '</th>');
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                 index++;
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  |             html.push('</tr></thead>');
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  |         html.push('<tbody>');
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         // console.log(data, len);
 | 
	
		
			
				|  |  |         // 按列加数据
 | 
	
		
			
				|  |  |         var dataI;
 | 
	
		
			
				|  |  |         for (i = 0; i < len; i++) {
 | 
	
		
			
				|  |  |             index = 0;
 | 
	
		
			
				|  |  |             tds = [];
 | 
	
		
			
				|  |  |             dataI = dataList[i];
 | 
	
		
			
				|  |  |             idValue = dataI[options.idField] || '';
 | 
	
		
			
				|  |  |             keyValue = dataI[options.keyField] || '';
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             for (field in dataI) {
 | 
	
		
			
				|  |  |                 // 标记作为 value 和 作为 id 的值
 | 
	
		
			
				|  |  |                 if (!keyValue && options.indexKey === index) {
 | 
	
		
			
				|  |  |                     keyValue = dataI[field];
 | 
	
		
			
				|  |  |                 }
 | 
	
		
			
				|  |  |                 if (!idValue && options.indexId === index) {
 | 
	
		
			
				|  |  |                     idValue = dataI[field];
 | 
	
		
			
				|  |  |                 }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                 index++;
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                 // 列表中只显示有效的字段
 | 
	
		
			
				|  |  |                 if (inEffectiveFields(field, options)) {
 | 
	
		
			
				|  |  |                     tds.push('<td data-name="', field, '">', dataI[field], '</td>');
 | 
	
		
			
				|  |  |                 }
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             html.push('<tr data-index="', (dataI.__index || i),
 | 
	
		
			
				|  |  |                 '" data-id="', idValue,
 | 
	
		
			
				|  |  |                 '" data-key="', keyValue, 
 | 
	
		
			
				|  |  |                 '" data-json="', encodeURIComponent(JSON.stringify(dataI)), '">',
 | 
	
		
			
				|  |  |                 tds.join(''), '</tr>');
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  |         html.push('</tbody></table>');
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         $dropdownMenu.html(html.join('')).show();
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         // scrollbar 存在时,延时到动画结束时调整 padding
 | 
	
		
			
				|  |  |         setTimeout(function() {
 | 
	
		
			
				|  |  |             if (notNeedCalcPadding) {
 | 
	
		
			
				|  |  |                 return;
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             var $table = $dropdownMenu.find('table:eq(0)'),
 | 
	
		
			
				|  |  |                 pdr = 0,
 | 
	
		
			
				|  |  |                 mgb = 0;
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             if (
 | 
	
		
			
				|  |  |                 $dropdownMenu.height() < $table.height() &&
 | 
	
		
			
				|  |  |                 +$dropdownMenu.css('minWidth').replace('px', '') < $dropdownMenu.width()
 | 
	
		
			
				|  |  |             ) {
 | 
	
		
			
				|  |  |                 pdr = 18;
 | 
	
		
			
				|  |  |                 mgb = 20;
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             $dropdownMenu.css('paddingRight', pdr);
 | 
	
		
			
				|  |  |             $table.css('marginBottom', mgb);
 | 
	
		
			
				|  |  |         }, 301);
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         adjustDropMenuPos($input, $dropdownMenu, options);
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         return $input;
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * ajax 获取数据
 | 
	
		
			
				|  |  |      * @param  {Object} options
 | 
	
		
			
				|  |  |      * @return {Object}         $.Deferred
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function ajax(options, keyword) {
 | 
	
		
			
				|  |  |         keyword = keyword || '';
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         var preAjax = options._preAjax;
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         if (preAjax && preAjax.abort && preAjax.readyState !== 4) {
 | 
	
		
			
				|  |  |             // console.log('abort pre ajax');
 | 
	
		
			
				|  |  |             preAjax.abort();
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         var ajaxParam = {
 | 
	
		
			
				|  |  |             type: 'GET',
 | 
	
		
			
				|  |  |             dataType: options.jsonp ? 'jsonp' : 'json',
 | 
	
		
			
				|  |  |             timeout: 5000,
 | 
	
		
			
				|  |  |         };
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         // jsonp
 | 
	
		
			
				|  |  |         if (options.jsonp) {
 | 
	
		
			
				|  |  |             ajaxParam.jsonp = options.jsonp;
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         // 自定义 ajax 请求参数生成方法
 | 
	
		
			
				|  |  |         var adjustAjaxParam,
 | 
	
		
			
				|  |  |             fnAdjustAjaxParam = options.fnAdjustAjaxParam;
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         if ($.isFunction(fnAdjustAjaxParam)) {
 | 
	
		
			
				|  |  |             adjustAjaxParam = fnAdjustAjaxParam(keyword, options);
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             // options.fnAdjustAjaxParam 返回false,则终止 ajax 请求
 | 
	
		
			
				|  |  |             if (FALSE === adjustAjaxParam) {
 | 
	
		
			
				|  |  |                 return;
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             $.extend(ajaxParam, adjustAjaxParam);
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         // url 调整
 | 
	
		
			
				|  |  |         ajaxParam.url = function() {
 | 
	
		
			
				|  |  |             if (!keyword || ajaxParam.data) {
 | 
	
		
			
				|  |  |                 return ajaxParam.url || options.url;
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             var type = '?';
 | 
	
		
			
				|  |  |             if (/=$/.test(options.url)) {
 | 
	
		
			
				|  |  |                 type = '';
 | 
	
		
			
				|  |  |             } else if (/\?/.test(options.url)) {
 | 
	
		
			
				|  |  |                 type = '&';
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             return options.url + type + encodeURIComponent(keyword);
 | 
	
		
			
				|  |  |         }();
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         return options._preAjax = $.ajax(ajaxParam).done(function(result) {
 | 
	
		
			
				|  |  |             options.data = options.fnProcessData(result);
 | 
	
		
			
				|  |  |         }).fail(function(err) {
 | 
	
		
			
				|  |  |             if (options.fnAjaxFail) {
 | 
	
		
			
				|  |  |                 options.fnAjaxFail(err, options);
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  |         });
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 检测 keyword 与 value 是否存在互相包含
 | 
	
		
			
				|  |  |      * @param  {String}  keyword 用户输入的关键字
 | 
	
		
			
				|  |  |      * @param  {String}  key     匹配字段的 key
 | 
	
		
			
				|  |  |      * @param  {String}  value   key 字段对应的值
 | 
	
		
			
				|  |  |      * @param  {Object}  options
 | 
	
		
			
				|  |  |      * @return {Boolean}         包含/不包含
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function isInWord(keyword, key, value, options) {
 | 
	
		
			
				|  |  |         value = $.trim(value);
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         if (options.ignorecase) {
 | 
	
		
			
				|  |  |             keyword = keyword.toLocaleLowerCase();
 | 
	
		
			
				|  |  |             value = value.toLocaleLowerCase();
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         return value &&
 | 
	
		
			
				|  |  |             (inEffectiveFields(key, options) || inSearchFields(key, options)) && // 必须在有效的搜索字段中
 | 
	
		
			
				|  |  |             (
 | 
	
		
			
				|  |  |                 ~value.indexOf(keyword) || // 匹配值包含关键字
 | 
	
		
			
				|  |  |                 options.twoWayMatch && ~keyword.indexOf(value) // 关键字包含匹配值
 | 
	
		
			
				|  |  |             );
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 通过 ajax 或 json 参数获取数据
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function getData(keyword, $input, callback, options) {
 | 
	
		
			
				|  |  |         var data, validData, filterData = {
 | 
	
		
			
				|  |  |                 value: []
 | 
	
		
			
				|  |  |             },
 | 
	
		
			
				|  |  |             i, key, len,
 | 
	
		
			
				|  |  |             fnPreprocessKeyword = options.fnPreprocessKeyword;
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         keyword = keyword || '';
 | 
	
		
			
				|  |  |         // 获取数据前对关键字预处理方法
 | 
	
		
			
				|  |  |         if ($.isFunction(fnPreprocessKeyword)) {
 | 
	
		
			
				|  |  |             keyword = fnPreprocessKeyword(keyword, options);
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         // 给了url参数,则从服务器 ajax 请求
 | 
	
		
			
				|  |  |         // console.log(options.url + keyword);
 | 
	
		
			
				|  |  |         if (options.url) {
 | 
	
		
			
				|  |  |             var timer;
 | 
	
		
			
				|  |  |             if (options.searchingTip) {
 | 
	
		
			
				|  |  |                 timer = setTimeout(function() {
 | 
	
		
			
				|  |  |                     showTip(options.searchingTip, $input, $input.parent().find('ul'), options);
 | 
	
		
			
				|  |  |                 }, 600);
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             ajax(options, keyword).done(function(result) {
 | 
	
		
			
				|  |  |                 callback($input, options.data, options); // 为 refreshDropMenu
 | 
	
		
			
				|  |  |                 $input.trigger(onDataRequestSuccess, result);
 | 
	
		
			
				|  |  |                 if (options.getDataMethod === 'firstByUrl') {
 | 
	
		
			
				|  |  |                     options.url = null;
 | 
	
		
			
				|  |  |                 }
 | 
	
		
			
				|  |  |             }).always(function() {
 | 
	
		
			
				|  |  |                 timer && clearTimeout(timer);
 | 
	
		
			
				|  |  |             });
 | 
	
		
			
				|  |  |         } else {
 | 
	
		
			
				|  |  |             // 没有给出 url 参数,则从 data 参数获取
 | 
	
		
			
				|  |  |             data = options.data;
 | 
	
		
			
				|  |  |             validData = checkData(data);
 | 
	
		
			
				|  |  |             // 本地的 data 数据,则在本地过滤
 | 
	
		
			
				|  |  |             if (validData) {
 | 
	
		
			
				|  |  |                 if (keyword) {
 | 
	
		
			
				|  |  |                     // 输入不为空时则进行匹配
 | 
	
		
			
				|  |  |                     len = data.value.length;
 | 
	
		
			
				|  |  |                     for (i = 0; i < len; i++) {
 | 
	
		
			
				|  |  |                         for (key in data.value[i]) {
 | 
	
		
			
				|  |  |                             if (
 | 
	
		
			
				|  |  |                                 data.value[i][key] &&
 | 
	
		
			
				|  |  |                                 isInWord(keyword, key, data.value[i][key] + '', options)
 | 
	
		
			
				|  |  |                             ) {
 | 
	
		
			
				|  |  |                                 filterData.value.push(data.value[i]);
 | 
	
		
			
				|  |  |                                 filterData.value[filterData.value.length - 1].__index = i;
 | 
	
		
			
				|  |  |                                 break;
 | 
	
		
			
				|  |  |                             }
 | 
	
		
			
				|  |  |                         }
 | 
	
		
			
				|  |  |                     }
 | 
	
		
			
				|  |  |                 } else {
 | 
	
		
			
				|  |  |                     filterData = data;
 | 
	
		
			
				|  |  |                 }
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             callback($input, filterData, options);
 | 
	
		
			
				|  |  |         } // else
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 数据处理
 | 
	
		
			
				|  |  |      * url 获取数据时,对数据的处理,作为 fnGetData 之后的回调处理
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function processData(data) {
 | 
	
		
			
				|  |  |         return checkData(data);
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 取得 clearable 清除按钮
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     function getIClear($input, options) {
 | 
	
		
			
				|  |  |         var $iClear = $input.prev('i.clearable');
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         // 是否可清除已输入的内容(添加清除按钮)
 | 
	
		
			
				|  |  |         if (options.clearable && !$iClear.length) {
 | 
	
		
			
				|  |  |                 $iClear = $('<i class="clearable glyphicon glyphicon-remove"></i>')
 | 
	
		
			
				|  |  |                     .prependTo($input.parent());
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         return $iClear.css({
 | 
	
		
			
				|  |  |             position: 'absolute',
 | 
	
		
			
				|  |  |             top: 12,
 | 
	
		
			
				|  |  |             right: options.showBtn ? ($input.next('.input-group-btn').width() || 33) + 2 : 12,
 | 
	
		
			
				|  |  |             zIndex: 4,
 | 
	
		
			
				|  |  |             cursor: 'pointer',
 | 
	
		
			
				|  |  |             fontSize: 12
 | 
	
		
			
				|  |  |         }).hide();
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  |     /**
 | 
	
		
			
				|  |  |      * 默认的配置选项
 | 
	
		
			
				|  |  |      * @type {Object}
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |     var defaultOptions = {
 | 
	
		
			
				|  |  |         url: null,                      // 请求数据的 URL 地址
 | 
	
		
			
				|  |  |         jsonp: null,                    // 设置此参数名,将开启jsonp功能,否则使用json数据结构
 | 
	
		
			
				|  |  |         data: {
 | 
	
		
			
				|  |  |             value: []
 | 
	
		
			
				|  |  |         },                              // 提示所用的数据,注意格式
 | 
	
		
			
				|  |  |         indexId: 0,                     // 每组数据的第几个数据,作为input输入框的 data-id,设为 -1 且 idField 为空则不设置此值
 | 
	
		
			
				|  |  |         indexKey: 0,                    // 每组数据的第几个数据,作为input输入框的内容
 | 
	
		
			
				|  |  |         idField: '',                    // 每组数据的哪个字段作为 data-id,优先级高于 indexId 设置(推荐)
 | 
	
		
			
				|  |  |         keyField: '',                   // 每组数据的哪个字段作为输入框内容,优先级高于 indexKey 设置(推荐)
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         /* 搜索相关 */
 | 
	
		
			
				|  |  |         autoSelect: TRUE,               // 键盘向上/下方向键时,是否自动选择值
 | 
	
		
			
				|  |  |         allowNoKeyword: TRUE,           // 是否允许无关键字时请求数据
 | 
	
		
			
				|  |  |         getDataMethod: 'firstByUrl',    // 获取数据的方式,url:一直从url请求;data:从 options.data 获取;firstByUrl:第一次从Url获取全部数据,之后从options.data获取
 | 
	
		
			
				|  |  |         delayUntilKeyup: FALSE,         // 获取数据的方式 为 firstByUrl 时,是否延迟到有输入时才请求数据
 | 
	
		
			
				|  |  |         ignorecase: FALSE,              // 前端搜索匹配时,是否忽略大小写
 | 
	
		
			
				|  |  |         effectiveFields: [],            // 有效显示于列表中的字段,非有效字段都会过滤,默认全部有效。
 | 
	
		
			
				|  |  |         effectiveFieldsAlias: {},       // 有效字段的别名对象,用于 header 的显示
 | 
	
		
			
				|  |  |         searchFields: [],               // 有效搜索字段,从前端搜索过滤数据时使用,但不一定显示在列表中。effectiveFields 配置字段也会用于搜索过滤
 | 
	
		
			
				|  |  |         twoWayMatch: TRUE,              // 是否双向匹配搜索。为 true 即输入关键字包含或包含于匹配字段均认为匹配成功,为 false 则输入关键字包含于匹配字段认为匹配成功
 | 
	
		
			
				|  |  |         multiWord: FALSE,               // 以分隔符号分割的多关键字支持
 | 
	
		
			
				|  |  |         separator: ',',                 // 多关键字支持时的分隔符,默认为半角逗号
 | 
	
		
			
				|  |  |         delay: 300,                     // 搜索触发的延时时间间隔,单位毫秒
 | 
	
		
			
				|  |  |         emptyTip: '',                   // 查询为空时显示的内容,可为 html
 | 
	
		
			
				|  |  |         searchingTip: '搜索中...',      // ajax 搜索时显示的提示内容,当搜索时间较长时给出正在搜索的提示
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         /* UI */
 | 
	
		
			
				|  |  |         autoDropup: FALSE,              // 选择菜单是否自动判断向上展开。设为 true,则当下拉菜单高度超过窗体,且向上方向不会被窗体覆盖,则选择菜单向上弹出
 | 
	
		
			
				|  |  |         autoMinWidth: FALSE,            // 是否自动最小宽度,设为 false 则最小宽度不小于输入框宽度
 | 
	
		
			
				|  |  |         showHeader: FALSE,              // 是否显示选择列表的 header。为 true 时,有效字段大于一列则显示表头
 | 
	
		
			
				|  |  |         showBtn: TRUE,                  // 是否显示下拉按钮
 | 
	
		
			
				|  |  |         inputBgColor: '',               // 输入框背景色,当与容器背景色不同时,可能需要该项的配置
 | 
	
		
			
				|  |  |         inputWarnColor: 'rgba(255,0,0,.1)', // 输入框内容不是下拉列表选择时的警告色
 | 
	
		
			
				|  |  |         listStyle: {
 | 
	
		
			
				|  |  |             'padding-top': 0,
 | 
	
		
			
				|  |  |             'max-height': '375px',
 | 
	
		
			
				|  |  |             'max-width': '800px',
 | 
	
		
			
				|  |  |             'overflow': 'auto',
 | 
	
		
			
				|  |  |             'width': 'auto',
 | 
	
		
			
				|  |  |             'transition': '0.3s',
 | 
	
		
			
				|  |  |             '-webkit-transition': '0.3s',
 | 
	
		
			
				|  |  |             '-moz-transition': '0.3s',
 | 
	
		
			
				|  |  |             '-o-transition': '0.3s'
 | 
	
		
			
				|  |  |         },                              // 列表的样式控制
 | 
	
		
			
				|  |  |         listAlign: 'left',              // 提示列表对齐位置,left/right/auto
 | 
	
		
			
				|  |  |         listHoverStyle: 'background: #07d; color:#fff', // 提示框列表鼠标悬浮的样式
 | 
	
		
			
				|  |  |         listHoverCSS: 'jhover',         // 提示框列表鼠标悬浮的样式名称
 | 
	
		
			
				|  |  |         clearable: FALSE,               // 是否可清除已输入的内容
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         /* key */
 | 
	
		
			
				|  |  |         keyLeft: 37,                    // 向左方向键,不同的操作系统可能会有差别,则自行定义
 | 
	
		
			
				|  |  |         keyUp: 38,                      // 向上方向键
 | 
	
		
			
				|  |  |         keyRight: 39,                   // 向右方向键
 | 
	
		
			
				|  |  |         keyDown: 40,                    // 向下方向键
 | 
	
		
			
				|  |  |         keyEnter: 13,                   // 回车键
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |         /* methods */
 | 
	
		
			
				|  |  |         fnProcessData: processData,     // 格式化数据的方法,返回数据格式参考 data 参数
 | 
	
		
			
				|  |  |         fnGetData: getData,             // 获取数据的方法,无特殊需求一般不作设置
 | 
	
		
			
				|  |  |         fnAdjustAjaxParam: null,        // 调整 ajax 请求参数方法,用于更多的请求配置需求。如对请求关键字作进一步处理、修改超时时间等
 | 
	
		
			
				|  |  |         fnPreprocessKeyword: null,      // 搜索过滤数据前,对输入关键字作进一步处理方法。注意,应返回字符串
 | 
	
		
			
				|  |  |         fnAjaxFail: null,               // ajax 失败时回调方法
 | 
	
		
			
				|  |  |     };
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |     var methods = {
 | 
	
		
			
				|  |  |         init: function(options) {
 | 
	
		
			
				|  |  |             // 参数设置
 | 
	
		
			
				|  |  |             var self = this;
 | 
	
		
			
				|  |  |             options = options || {};
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             // 默认配置有效显示字段多于一个,则显示列表表头,否则不显示
 | 
	
		
			
				|  |  |             if (undefined === options.showHeader && options.effectiveFields && options.effectiveFields.length > 1) {
 | 
	
		
			
				|  |  |                 options.showHeader = TRUE;
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             options = $.extend(TRUE, {}, defaultOptions, options);
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             // 旧的方法兼容
 | 
	
		
			
				|  |  |             if (options.processData) {
 | 
	
		
			
				|  |  |                 options.fnProcessData = options.processData;
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             if (options.getData) {
 | 
	
		
			
				|  |  |                 options.fnGetData = options.getData;
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             if (options.getDataMethod === 'firstByUrl' && options.url && !options.delayUntilKeyup) {
 | 
	
		
			
				|  |  |                 ajax(options).done(function(result) {
 | 
	
		
			
				|  |  |                     options.url = null;
 | 
	
		
			
				|  |  |                     self.trigger(onDataRequestSuccess, result);
 | 
	
		
			
				|  |  |                 });
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             // 鼠标滑动到条目样式
 | 
	
		
			
				|  |  |             if (!$('#' + BSSUGGEST).length) {
 | 
	
		
			
				|  |  |                 $('head:eq(0)').append('<style id="' + BSSUGGEST + '">.' + options.listHoverCSS + '{' + options.listHoverStyle + '}</style>');
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             return self.each(function() {
 | 
	
		
			
				|  |  |                 var $input = $(this),
 | 
	
		
			
				|  |  |                     $parent = $input.parent(),
 | 
	
		
			
				|  |  |                     $iClear = getIClear($input, options),
 | 
	
		
			
				|  |  |                     isMouseenterMenu,
 | 
	
		
			
				|  |  |                     keyupTimer, // keyup 与 input 事件延时定时器
 | 
	
		
			
				|  |  |                     $dropdownMenu = $parent.find('ul:eq(0)');
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                 // 验证输入框对象是否符合条件
 | 
	
		
			
				|  |  |                 if (!checkInput($input, $dropdownMenu, options)) {
 | 
	
		
			
				|  |  |                     console.warn('不是一个标准的 bootstrap 下拉式菜单或已初始化:', $input);
 | 
	
		
			
				|  |  |                     return;
 | 
	
		
			
				|  |  |                 }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                 // 是否显示 button 按钮
 | 
	
		
			
				|  |  |                 if (!options.showBtn) {
 | 
	
		
			
				|  |  |                     $input.css('borderRadius', 4);
 | 
	
		
			
				|  |  |                     $parent.css('width', '100%')
 | 
	
		
			
				|  |  |                         .find('.btn:eq(0)').hide();
 | 
	
		
			
				|  |  |                 }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                 // 移除 disabled 类,并禁用自动完成
 | 
	
		
			
				|  |  |                 $input.removeClass(DISABLED).prop(DISABLED, FALSE).attr('autocomplete', 'off');
 | 
	
		
			
				|  |  |                 // dropdown-menu 增加修饰
 | 
	
		
			
				|  |  |                 $dropdownMenu.css(options.listStyle);
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                 // 默认背景色
 | 
	
		
			
				|  |  |                 if (!options.inputBgColor) {
 | 
	
		
			
				|  |  |                     options.inputBgColor = $input.css('backgroundColor');
 | 
	
		
			
				|  |  |                 }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                 // 开始事件处理
 | 
	
		
			
				|  |  |                 $input.on('keydown', function(event) {
 | 
	
		
			
				|  |  |                     var currentList, tipsKeyword; // 提示列表上被选中的关键字
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     // 当提示层显示时才对键盘事件处理
 | 
	
		
			
				|  |  |                     if (!$dropdownMenu.is(':visible')) {
 | 
	
		
			
				|  |  |                         setOrGetDataId($input, '');
 | 
	
		
			
				|  |  |                         return;
 | 
	
		
			
				|  |  |                     }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     currentList = $dropdownMenu.find('.' + options.listHoverCSS);
 | 
	
		
			
				|  |  |                     tipsKeyword = ''; // 提示列表上被选中的关键字
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     unHoverAll($dropdownMenu, options);
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     if (event.keyCode === options.keyDown) { // 如果按的是向下方向键
 | 
	
		
			
				|  |  |                         if (!currentList.length) {
 | 
	
		
			
				|  |  |                             // 如果提示列表没有一个被选中,则将列表第一个选中
 | 
	
		
			
				|  |  |                             tipsKeyword = getPointKeyword($dropdownMenu.find('tbody tr:first').mouseover());
 | 
	
		
			
				|  |  |                         } else if (!currentList.next().length) {
 | 
	
		
			
				|  |  |                             // 如果是最后一个被选中,则取消选中,即可认为是输入框被选中,并恢复输入的值
 | 
	
		
			
				|  |  |                             if (options.autoSelect) {
 | 
	
		
			
				|  |  |                                 setOrGetDataId($input, '').val(setOrGetAlt($input));
 | 
	
		
			
				|  |  |                             }
 | 
	
		
			
				|  |  |                         } else {
 | 
	
		
			
				|  |  |                             // 选中下一行
 | 
	
		
			
				|  |  |                             tipsKeyword = getPointKeyword(currentList.next().mouseover());
 | 
	
		
			
				|  |  |                         }
 | 
	
		
			
				|  |  |                         // 控制滑动条
 | 
	
		
			
				|  |  |                         adjustScroll($input, $dropdownMenu, options);
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                         if (!options.autoSelect) {
 | 
	
		
			
				|  |  |                             return;
 | 
	
		
			
				|  |  |                         }
 | 
	
		
			
				|  |  |                     } else if (event.keyCode === options.keyUp) { // 如果按的是向上方向键
 | 
	
		
			
				|  |  |                         if (!currentList.length) {
 | 
	
		
			
				|  |  |                             tipsKeyword = getPointKeyword($dropdownMenu.find('tbody tr:last').mouseover());
 | 
	
		
			
				|  |  |                         } else if (!currentList.prev().length) {
 | 
	
		
			
				|  |  |                             if (options.autoSelect) {
 | 
	
		
			
				|  |  |                                 setOrGetDataId($input, '').val(setOrGetAlt($input));
 | 
	
		
			
				|  |  |                             }
 | 
	
		
			
				|  |  |                         } else {
 | 
	
		
			
				|  |  |                             // 选中前一行
 | 
	
		
			
				|  |  |                             tipsKeyword = getPointKeyword(currentList.prev().mouseover());
 | 
	
		
			
				|  |  |                         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                         // 控制滑动条
 | 
	
		
			
				|  |  |                         adjustScroll($input, $dropdownMenu, options);
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                         if (!options.autoSelect) {
 | 
	
		
			
				|  |  |                             return;
 | 
	
		
			
				|  |  |                         }
 | 
	
		
			
				|  |  |                     } else if (event.keyCode === options.keyEnter) {
 | 
	
		
			
				|  |  |                         tipsKeyword = getPointKeyword(currentList);
 | 
	
		
			
				|  |  |                         $dropdownMenu.hide(); // .empty();
 | 
	
		
			
				|  |  |                     } else {
 | 
	
		
			
				|  |  |                         setOrGetDataId($input, '');
 | 
	
		
			
				|  |  |                     }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     // 设置值 tipsKeyword
 | 
	
		
			
				|  |  |                     // console.log(tipsKeyword);
 | 
	
		
			
				|  |  |                     setValue($input, tipsKeyword, options);
 | 
	
		
			
				|  |  |                 }).on('compositionstart', function(event) {
 | 
	
		
			
				|  |  |                     // 中文输入开始,锁定
 | 
	
		
			
				|  |  |                     // console.log('compositionstart');
 | 
	
		
			
				|  |  |                     inputLock = TRUE;
 | 
	
		
			
				|  |  |                 }).on('compositionend', function(event) {
 | 
	
		
			
				|  |  |                     // 中文输入结束,解除锁定
 | 
	
		
			
				|  |  |                     // console.log('compositionend');
 | 
	
		
			
				|  |  |                     inputLock = FALSE;
 | 
	
		
			
				|  |  |                 }).on('keyup input paste', function(event) {
 | 
	
		
			
				|  |  |                     var word;
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     if (event.keyCode) {
 | 
	
		
			
				|  |  |                         setBackground($input, options);
 | 
	
		
			
				|  |  |                     }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     // 如果弹起的键是回车、向上或向下方向键则返回
 | 
	
		
			
				|  |  |                     if (~$.inArray(event.keyCode, [options.keyDown, options.keyUp, options.keyEnter])) {
 | 
	
		
			
				|  |  |                         $input.val($input.val()); // 让鼠标输入跳到最后
 | 
	
		
			
				|  |  |                         return;
 | 
	
		
			
				|  |  |                     }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     clearTimeout(keyupTimer);
 | 
	
		
			
				|  |  |                     keyupTimer = setTimeout(function() {
 | 
	
		
			
				|  |  |                         // console.log('input keyup', event);
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                         // 锁定状态,返回
 | 
	
		
			
				|  |  |                         if (inputLock) {
 | 
	
		
			
				|  |  |                             return;
 | 
	
		
			
				|  |  |                         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                         word = $input.val();
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                         // 若输入框值没有改变则返回
 | 
	
		
			
				|  |  |                         if ($.trim(word) && word === setOrGetAlt($input)) {
 | 
	
		
			
				|  |  |                             return;
 | 
	
		
			
				|  |  |                         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                         // 当按下键之前记录输入框值,以方便查看键弹起时值有没有变
 | 
	
		
			
				|  |  |                         setOrGetAlt($input, word);
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                         if (options.multiWord) {
 | 
	
		
			
				|  |  |                             word = word.split(options.separator).reverse()[0];
 | 
	
		
			
				|  |  |                         }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                         // 是否允许空数据查询
 | 
	
		
			
				|  |  |                         if (!word.length && !options.allowNoKeyword) {
 | 
	
		
			
				|  |  |                             return;
 | 
	
		
			
				|  |  |                         }
 | 
	
		
			
				|  |  | 						
 | 
	
		
			
				|  |  | 						if (options.url) {
 | 
	
		
			
				|  |  |                         	options.fnGetData($.trim(word), $input, refreshDropMenu, options);
 | 
	
		
			
				|  |  |                         }
 | 
	
		
			
				|  |  |                     }, options.delay || 300);
 | 
	
		
			
				|  |  |                 }).on('focus', function() {
 | 
	
		
			
				|  |  |                     // console.log('input focus');
 | 
	
		
			
				|  |  |                     adjustDropMenuPos($input, $dropdownMenu, options);
 | 
	
		
			
				|  |  |                 }).on('blur', function() {
 | 
	
		
			
				|  |  |                     if (!isMouseenterMenu) { // 不是进入下拉列表状态,则隐藏列表
 | 
	
		
			
				|  |  |                         $dropdownMenu.css('display', '');
 | 
	
		
			
				|  |  |                     }
 | 
	
		
			
				|  |  |                 }).on('click', function() {
 | 
	
		
			
				|  |  |                     // console.log('input click');
 | 
	
		
			
				|  |  |                     var word = $input.val();
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     if (
 | 
	
		
			
				|  |  |                         $.trim(word) &&
 | 
	
		
			
				|  |  |                         word === setOrGetAlt($input) &&
 | 
	
		
			
				|  |  |                         $dropdownMenu.find('table tr').length
 | 
	
		
			
				|  |  |                     ) {
 | 
	
		
			
				|  |  |                         return $dropdownMenu.show();
 | 
	
		
			
				|  |  |                     }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     // if ($dropdownMenu.css('display') !== 'none') {
 | 
	
		
			
				|  |  |                     if ($dropdownMenu.is(':visible')) {
 | 
	
		
			
				|  |  |                         return;
 | 
	
		
			
				|  |  |                     }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     if (options.multiWord) {
 | 
	
		
			
				|  |  |                         word = word.split(options.separator).reverse()[0];
 | 
	
		
			
				|  |  |                     }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     // 是否允许空数据查询
 | 
	
		
			
				|  |  |                     if (!word.length && !options.allowNoKeyword) {
 | 
	
		
			
				|  |  |                         return;
 | 
	
		
			
				|  |  |                     }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     // console.log('word', word);
 | 
	
		
			
				|  |  |                     options.fnGetData($.trim(word), $input, refreshDropMenu, options);
 | 
	
		
			
				|  |  |                 });
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                 // 下拉按钮点击时
 | 
	
		
			
				|  |  |                 $parent.find('.btn:eq(0)').attr('data-toggle', '').click(function() {
 | 
	
		
			
				|  |  |                     var display = 'none';
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     // if ($dropdownMenu.is(':visible')) {
 | 
	
		
			
				|  |  |                     if ($dropdownMenu.css('display') === display) {
 | 
	
		
			
				|  |  |                         display = 'block';
 | 
	
		
			
				|  |  |                         if (options.url) {
 | 
	
		
			
				|  |  |                             $input.click().focus();
 | 
	
		
			
				|  |  |                             if (!$dropdownMenu.find('tr').length) {
 | 
	
		
			
				|  |  |                                 display = 'none';
 | 
	
		
			
				|  |  |                             }
 | 
	
		
			
				|  |  |                         } else {
 | 
	
		
			
				|  |  |                             // 不以 keyword 作为过滤,展示所有的数据
 | 
	
		
			
				|  |  |                             refreshDropMenu($input, options.data, options);
 | 
	
		
			
				|  |  |                         }
 | 
	
		
			
				|  |  |                     }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     $dropdownMenu.css('display', display);
 | 
	
		
			
				|  |  |                     return FALSE;
 | 
	
		
			
				|  |  |                 });
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                 // 列表中滑动时,输入框失去焦点
 | 
	
		
			
				|  |  |                 $dropdownMenu.mouseenter(function() {
 | 
	
		
			
				|  |  |                         // console.log('mouseenter')
 | 
	
		
			
				|  |  |                         isMouseenterMenu = 1;
 | 
	
		
			
				|  |  |                         $input.blur();
 | 
	
		
			
				|  |  |                     }).mouseleave(function() {
 | 
	
		
			
				|  |  |                         // console.log('mouseleave')
 | 
	
		
			
				|  |  |                         isMouseenterMenu = 0;
 | 
	
		
			
				|  |  |                         $input.focus();
 | 
	
		
			
				|  |  |                     }).on('mouseenter', 'tbody tr', function() {
 | 
	
		
			
				|  |  |                         // 行上的移动事件
 | 
	
		
			
				|  |  |                         unHoverAll($dropdownMenu, options);
 | 
	
		
			
				|  |  |                         $(this).addClass(options.listHoverCSS);
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                         return FALSE; // 阻止冒泡
 | 
	
		
			
				|  |  |                     })
 | 
	
		
			
				|  |  |                     .on('mousedown', 'tbody tr', function() {
 | 
	
		
			
				|  |  |                         var keywords = getPointKeyword($(this));
 | 
	
		
			
				|  |  |                         setValue($input, keywords, options);
 | 
	
		
			
				|  |  |                         setOrGetAlt($input, keywords.key);
 | 
	
		
			
				|  |  |                         setJson($input, keywords.json);
 | 
	
		
			
				|  |  |                         setBackground($input, options);
 | 
	
		
			
				|  |  |                         $dropdownMenu.hide();
 | 
	
		
			
				|  |  |                     });
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                 // 存在清空按钮
 | 
	
		
			
				|  |  |                 if ($iClear.length) {
 | 
	
		
			
				|  |  |                     $iClear.click(function () {
 | 
	
		
			
				|  |  |                         setOrGetDataId($input, '').val('');
 | 
	
		
			
				|  |  |                         setBackground($input, options);
 | 
	
		
			
				|  |  |                     });
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |                     $parent.mouseenter(function() {
 | 
	
		
			
				|  |  |                         if (!$input.prop(DISABLED)) {
 | 
	
		
			
				|  |  |                             $iClear.show();
 | 
	
		
			
				|  |  |                         }
 | 
	
		
			
				|  |  |                     }).mouseleave(function() {
 | 
	
		
			
				|  |  |                         $iClear.hide();
 | 
	
		
			
				|  |  |                     });
 | 
	
		
			
				|  |  |                 }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             });
 | 
	
		
			
				|  |  |         },
 | 
	
		
			
				|  |  |         show: function() {
 | 
	
		
			
				|  |  |             return this.each(function() {
 | 
	
		
			
				|  |  |                 $(this).click();
 | 
	
		
			
				|  |  |             });
 | 
	
		
			
				|  |  |         },
 | 
	
		
			
				|  |  |         hide: function() {
 | 
	
		
			
				|  |  |             return this.each(function() {
 | 
	
		
			
				|  |  |                 $(this).parent().find('ul:eq(0)').css('display', '');
 | 
	
		
			
				|  |  |             });
 | 
	
		
			
				|  |  |         },
 | 
	
		
			
				|  |  |         disable: function() {
 | 
	
		
			
				|  |  |             return this.each(function() {
 | 
	
		
			
				|  |  |                 $(this).attr(DISABLED, TRUE)
 | 
	
		
			
				|  |  |                     .parent().find('.btn:eq(0)').prop(DISABLED, TRUE);
 | 
	
		
			
				|  |  |             });
 | 
	
		
			
				|  |  |         },
 | 
	
		
			
				|  |  |         enable: function() {
 | 
	
		
			
				|  |  |             return this.each(function() {
 | 
	
		
			
				|  |  |                 $(this).attr(DISABLED, FALSE)
 | 
	
		
			
				|  |  |                     .parent().find('.btn:eq(0)').prop(DISABLED, FALSE);
 | 
	
		
			
				|  |  |             });
 | 
	
		
			
				|  |  |         },
 | 
	
		
			
				|  |  |         destroy: function() {
 | 
	
		
			
				|  |  |             return this.each(function() {
 | 
	
		
			
				|  |  |                 $(this).off().removeData(BSSUGGEST).removeAttr('style')
 | 
	
		
			
				|  |  |                     .parent().find('.btn:eq(0)').off().show().attr('data-toggle', 'dropdown').prop(DISABLED, FALSE) // .addClass(DISABLED);
 | 
	
		
			
				|  |  |                     .next().css('display', '').off();
 | 
	
		
			
				|  |  |             });
 | 
	
		
			
				|  |  |         },
 | 
	
		
			
				|  |  |         version: function() {
 | 
	
		
			
				|  |  |             return VERSION;
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  |     };
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |     $.fn[BSSUGGEST] = function(options) {
 | 
	
		
			
				|  |  |         // 方法判断
 | 
	
		
			
				|  |  |         if (typeof options === 'string' && methods[options]) {
 | 
	
		
			
				|  |  |             var inited = TRUE;
 | 
	
		
			
				|  |  |             this.each(function() {
 | 
	
		
			
				|  |  |                 if (!$(this).data(BSSUGGEST)) {
 | 
	
		
			
				|  |  |                     return inited = FALSE;
 | 
	
		
			
				|  |  |                 }
 | 
	
		
			
				|  |  |             });
 | 
	
		
			
				|  |  |             // 只要有一个未初始化,则全部都不执行方法,除非是 init 或 version
 | 
	
		
			
				|  |  |             if (!inited && 'init' !== options && 'version' !== options) {
 | 
	
		
			
				|  |  |                 return this;
 | 
	
		
			
				|  |  |             }
 | 
	
		
			
				|  |  | 
 | 
	
		
			
				|  |  |             // 如果是方法,则参数第一个为函数名,从第二个开始为函数参数
 | 
	
		
			
				|  |  |             return methods[options].apply(this, [].slice.call(arguments, 1));
 | 
	
		
			
				|  |  |         } else {
 | 
	
		
			
				|  |  |             // 调用初始化方法
 | 
	
		
			
				|  |  |             return methods.init.apply(this, arguments);
 | 
	
		
			
				|  |  |         }
 | 
	
		
			
				|  |  |     }
 | 
	
		
			
				|  |  | });
 |