/** * bootstrap-suggest-plugin - v0.1.29 * @description 这是一个基于 bootstrap 按钮式下拉菜单组件的搜索建议插件,必须使用于按钮式下拉菜单组件上。 * @author lzwme - https://lzw.me * @GitHub https://github.com/lzwme/bootstrap-suggest-plugin.git * @since 2019-11-18 09:30:06 */ (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 isUndefined(val) { return val === void 0; } /** * 错误处理 */ 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 isUndefined(val) ? $input.attr("alt") : $input.attr("alt", val); } /** * 设置或获取输入框的 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); } /** * 显示下拉列表 */ function showDropMenu($input, options) { var $dropdownMenu = $input.parent().find("ul:eq(0)"); if (!$dropdownMenu.is(":visible")) { // $dropdownMenu.css('display', 'block'); $dropdownMenu.show(); $input.trigger("onShowDropdown", [options ? options.data.value : []]); } } /** * 隐藏下拉列表 */ function hideDropMenu($input, options) { var $dropdownMenu = $input.parent().find("ul:eq(0)"); if ($dropdownMenu.is(":visible")) { // $dropdownMenu.css('display', ''); $dropdownMenu.hide(); $input.trigger("onHideDropdown", [options ? options.data.value : []]); } } /** * 下拉列表刷新 * 作为 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(); hideDropMenu($input, options); } return $input; } // 相同数据,不用继续渲染了 if ( options._lastData && JSON.stringify(options._lastData) === JSON.stringify(data) && $dropdownMenu.find("tr").length === len ) { showDropMenu($input, options); return adjustDropMenuPos($input, $dropdownMenu, options); } options._lastData = data; /** 显示于列表中的字段 */ var columns = options.effectiveFields.length ? options.effectiveFields : $.map(dataList[0], function(val, key) { return key; }); // 生成表头 if (options.showHeader) { html.push("<thead><tr>"); $.each(columns, function(index, field) { if (!inEffectiveFields(field, options)) return; 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; var maxOptionCount = Math.min(options.maxOptionCount, len); for (i = 0; i < maxOptionCount; i++) { index = 0; tds = []; dataI = dataList[i]; idValue = dataI[options.idField]; keyValue = dataI[options.keyField]; for (field in dataI) { // 标记作为 value 和 作为 id 的值 if (isUndefined(keyValue) && options.indexKey === index) { keyValue = dataI[field]; } if (isUndefined(idValue) && options.indexId === index) { idValue = dataI[field]; } index++; } $.each(columns, function(index, field) { // 列表中只显示有效的字段 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, '">', tds.join(""), "</tr>" ); } html.push("</tbody></table>"); $dropdownMenu.html(html.join("")); showDropMenu($input, options); //.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 fa fa-plus"></i>' ).prependTo($input.parent()); } return $iClear .css({ position: "absolute", top: "calc(50% - 6px)", transform: "rotate(45deg)", // right: options.showBtn ? Math.max($input.next('.input-group-btn').width(), 33) + 2 : 12, zIndex: 4, cursor: "pointer", width: "14px", lineHeight: "14px", textAlign: "center", 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 搜索时显示的提示内容,当搜索时间较长时给出正在搜索的提示 hideOnSelect: FALSE, // 鼠标从列表单击选择了值时,是否隐藏选择列表 maxOptionCount: 200, // 选择列表最多显示的可选项数量,默认为 200 /* 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", "word-break": "keep-all", "white-space": "nowrap" }, // 列表的样式控制 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 ( isUndefined(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)"); // 兼容 bs4 $dropdownMenu.parent().css("position", "relative"); // 验证输入框对象是否符合条件 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.bs", 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); hideDropMenu($input, options); } else { setOrGetDataId($input, ""); } // 设置值 tipsKeyword // console.log(tipsKeyword); setValue($input, tipsKeyword, options); }) .on("compositionstart.bs", function(event) { // 中文输入开始,锁定 // console.log('compositionstart'); inputLock = TRUE; }) .on("compositionend.bs", function(event) { // 中文输入结束,解除锁定 // console.log('compositionend'); inputLock = FALSE; }) .on("keyup.bs input.bs paste.bs", 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; } options.fnGetData($.trim(word), $input, refreshDropMenu, options); }, options.delay || 300); }) .on("focus.bs", function() { // console.log('input focus'); adjustDropMenuPos($input, $dropdownMenu, options); }) .on("blur.bs", function() { if (!isMouseenterMenu) { // 不是进入下拉列表状态,则隐藏列表 hideDropMenu($input, options); inputLock = true; setTimeout(function() { inputLock = FALSE; }); } }) .on("click.bs", function() { // console.log('input click'); var word = $input.val(); if ( $.trim(word) && word === setOrGetAlt($input) && $dropdownMenu.find("table tr").length ) { return showDropMenu($input, options); } 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() { if (!$dropdownMenu.is(":visible")) { if (options.url) { $input.click().focus(); if (!$dropdownMenu.find("tr").length) { return FALSE; } } else { // 不以 keyword 作为过滤,展示所有的数据 refreshDropMenu($input, options.data, options); } showDropMenu($input, options); } else { hideDropMenu($input, options); } 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); setBackground($input, options); if (options.hideOnSelect) { hideDropMenu($input, options); } }); // 存在清空按钮 if ($iClear.length) { $iClear.click(function() { setOrGetDataId($input, "").val(""); setBackground($input, options); }); $parent .mouseenter(function() { if (!$input.prop(DISABLED)) { $iClear .css( "right", options.showBtn ? Math.max($input.next().width(), 33) + 2 : 12 ) .show(); } }) .mouseleave(function() { $iClear.hide(); }); } }); }, show: function() { return this.each(function() { $(this).click(); }); }, hide: function() { return this.each(function() { hideDropMenu($(this)); }); }, 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() { var evNameList = "click.bs keydown.bs compositionstart.bs compositionend.bs keyup.bs input.bs paste.bs focus.bs click.bs"; $(this) .off(evNameList) .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); } }; });