bootstrap-suggest.min.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. /**
  2. * Bootstrap Search Suggest
  3. * Description: 这是一个基于 bootstrap 按钮式下拉菜单组件的搜索建议插件,必须使用于按钮式下拉菜单组件上。
  4. * Author: renxia <lzwy0820#qq.com>
  5. * Github: https://github.com/lzwme/bootstrap-suggest-plugin
  6. * Date : 2014-10-09
  7. * Update: 2016-02-22
  8. *===============================================================================
  9. * 一、功能说明:
  10. * 1. 搜索方式:从 data.value 的所有字段数据中查询 keyword 的出现,或字段数据包含于 keyword 中
  11. * 2. 支持单关键字、多关键字的输入搜索建议,多关键字可自定义分隔符
  12. * 3. 支持按 data 数据搜索、按 URL 请求搜索和按首次请求URL数据并缓存搜索三种方式【getDataMethod】
  13. * 4. 单关键字会设置输入框内容和 data-id 两个值,以 indexId 和 indexKey 取值 data 数据的次序为准;多关键字不支持 data-id 设置
  14. * 5. 对于单关键字支持,当 data-id 为空时,输入框会添加背景色警告
  15. *
  16. * 二、使用参考:
  17. * 1. 引入 jQuery、bootstrap.min.css、bootstrap.min.js
  18. * 2. 引入插件js:bootstrap-suggest.min.js
  19. * 3. 初始化插件
  20. * var bsSuggest = $("input#test").bsSuggest({
  21. * url: "/rest/sys/getuserlist?keyword="
  22. * });
  23. * 4. 方法参考:
  24. * 禁用提示:bsSuggest.bsSuggest("disable");
  25. * 启用提示:bsSuggest.bsSuggest("enable");
  26. * 销毁插件:bsSuggest.bsSuggest("destroy");
  27. * 5. 事件:
  28. * onDataRequestSuccess: 当 AJAX 请求数据成功时触发,并传回结果到第二个参数,示例:
  29. * bsSuggest.on("onDataRequestSuccess", function (event, result) { console.log(result); });
  30. * onSetSelectValue:当从下拉菜单选取值时触发
  31. * onUnsetSelectValue:当设置了 idField,且自由输入内容时触发(显示背景警告色)
  32. *
  33. *===============================================================================
  34. * (c) Copyright 2015 lzw.me. All Rights Reserved.
  35. ********************************************************************************/
  36. (function ($) {
  37. /**
  38. * 错误处理
  39. */
  40. function handleError(e1, e2){
  41. console.trace(e1);
  42. if(e2) {
  43. console.trace(e2);
  44. }
  45. }
  46. /**
  47. * 获取当前tr列的关键字数据
  48. */
  49. function getPointKeyword(list) {
  50. var data = {};
  51. data.id = list.attr('data-id');
  52. data.key = list.attr('data-key');
  53. data.index = list.attr('data-index');
  54. return data;
  55. }
  56. /**
  57. * 设置取得的值
  58. */
  59. function setValue($input, keywords, opts){
  60. var _keywords = keywords || {},
  61. id = _keywords.id || "",
  62. key = _keywords.key || "",
  63. inputValList/*, inputIdList*/;
  64. if (opts && opts.multiWord) {//多关键字支持,只设置 val
  65. inputValList = $input.val().split(opts.separator || ' ');
  66. inputValList[inputValList.length - 1] = key;
  67. /*inputIdList = $input.attr("data-id").split(opts.separator || ' ');
  68. inputIdList[inputIdList.length - 1] = id;*/
  69. $input.val(inputValList.join(opts.separator || ' '))
  70. //.attr("data-id", inputIdList.join(opts.separator || ' '))
  71. .focus();
  72. } else {
  73. $input.attr("data-id", id).focus().val(key);
  74. }
  75. $input.trigger("onSetSelectValue", [_keywords, (opts.data.value || opts.result.value)[_keywords.index]]);
  76. }
  77. /**
  78. * 调整选择菜单位置
  79. * @param {Object} $input
  80. * @param {Object} $dropdownMenu
  81. * @param {Object} options
  82. */
  83. function adjustDropMenuPos ($input, $dropdownMenu, options) {
  84. if ($dropdownMenu.is(':visible')) {
  85. setTimeout(function(){
  86. if ( //自动判断菜单向上展开
  87. options.autoDropup &&
  88. $(window).height() - $input.offset().top < $dropdownMenu.height() &&
  89. $input.offset().top > $dropdownMenu.height() + $(window).scrollTop()
  90. ) {
  91. $dropdownMenu.parents('.input-group').addClass('dropup');
  92. } else {
  93. $dropdownMenu.parents('.input-group.dropup').removeClass('dropup');
  94. }
  95. }, 100);
  96. //return;
  97. }
  98. //列表对齐方式
  99. var dmcss = {};
  100. if (options.listAlign === "left") {
  101. dmcss = {
  102. "left": $input.siblings("div").width() - $input.parent().width(),
  103. "right": "auto"
  104. };
  105. } else if (options.listAlign === "right") {
  106. dmcss= {
  107. "left": "auto",
  108. "right": "0"
  109. };
  110. }
  111. //是否自动最小宽度
  112. if(options.autoMinWidth === false) {
  113. dmcss['min-width'] = $input.parent().width();
  114. }/* else {
  115. dmcss['width'] = 'auto';
  116. }*/
  117. $dropdownMenu.css(dmcss);
  118. return $input;
  119. }
  120. /**
  121. * 设置输入框背景色
  122. * 当设置了 indexId,而输入框的 data-id 为空时,输入框加载警告色
  123. */
  124. function setBackground ($input, opts) {
  125. //console.log("setBackground", opts);
  126. var inputbg, bg, warnbg;
  127. if ((opts.indexId === -1 && !opts.idField) || opts.multiWord) {
  128. return $input;
  129. }
  130. inputbg = $input.css("background-color").replace(/ /g, "").split(",",3).join(",");
  131. //console.log(inputbg);
  132. bg = opts.inputBgColor || "rgba(255,255,255,0.1)";
  133. warnbg = opts.inputWarnColor || "rgba(255,255,0,0.1)";
  134. if (!$input.val() || $input.attr("data-id")) {
  135. return $input.css("background", bg);
  136. }
  137. //自由输入的内容,设置背景色
  138. if (-1 === warnbg.indexOf(inputbg)) {
  139. $input.trigger("onUnsetSelectValue"); //触发取消data-id事件
  140. $input.css("background", warnbg);
  141. }
  142. return $input;
  143. }
  144. /**
  145. * 调整滑动条
  146. */
  147. function adjustScroll($input, $dropdownMenu, options) {
  148. //控制滑动条
  149. var $hover = $input.parent().find("tbody tr." + options.listHoverCSS), pos, maxHeight;
  150. if ($hover.length > 0) {
  151. pos = ($hover.index() + 3) * $hover.height();
  152. maxHeight = Number($dropdownMenu.css("max-height").replace("px", ""));
  153. if (pos > maxHeight || $dropdownMenu.scrollTop() > maxHeight) {
  154. $dropdownMenu.scrollTop(pos - maxHeight);
  155. } else {
  156. $dropdownMenu.scrollTop(0);
  157. }
  158. }
  159. }
  160. /**
  161. * 解除所有列表 hover 样式
  162. */
  163. function unHoverAll($dropdownMenu, opts) {
  164. $dropdownMenu.find('tr.' + opts.listHoverCSS).removeClass(opts.listHoverCSS);
  165. }
  166. /**
  167. * 验证对象是否符合条件
  168. * 1. 必须为 bootstrap 下拉式菜单
  169. * 2. 必须未初始化过
  170. */
  171. function checkInput(target, options) {
  172. var $input = $(target),
  173. $dropdownMenu = $input.parent(".input-group").find("ul.dropdown-menu"),
  174. data = $input.data('bsSuggest');
  175. //过滤非 bootstrap 下拉式菜单对象
  176. if ($dropdownMenu.length === 0) {
  177. return false;
  178. }
  179. //是否已经初始化的检测
  180. if(data){
  181. return false;
  182. }
  183. $input.data('bsSuggest',{target: target, options: options});
  184. return true;
  185. }
  186. /**
  187. * 数据格式检测
  188. * 检测 ajax 返回成功数据或 data 参数数据是否有效
  189. * data 格式:{"value": [{}, {}...]}
  190. */
  191. function checkData(data) {
  192. var isEmpty = true;
  193. for (var o in data) {
  194. if (o === 'value') {
  195. isEmpty = false;
  196. break;
  197. }
  198. }
  199. if (isEmpty) {
  200. handleError("返回数据格式错误!");
  201. return false;
  202. }
  203. if (data.value.length === 0) {
  204. //handleError("返回数据为空!");
  205. return false;
  206. }
  207. return data;
  208. }
  209. /**
  210. * 判断字段名是否在 effectiveFields 配置项中
  211. * effectiveFields 为空时始终返回 TRUE
  212. */
  213. function inEffectiveFields(filed, opts) {
  214. if(
  215. filed === '__index' ||
  216. $.isArray(opts.effectiveFields) &&
  217. opts.effectiveFields.length > 0 &&
  218. $.inArray(filed, opts.effectiveFields) === -1
  219. ) {
  220. return false;
  221. }
  222. return true;
  223. }
  224. /**
  225. * 判断字段名是否在 searchFields 搜索字段配置中
  226. */
  227. function inSearchFields(filed, opts) {
  228. if ($.inArray(filed, opts.searchFields) !== -1) {
  229. return true;
  230. }
  231. return false;
  232. }
  233. /**
  234. * 绑定列表的 mouseover 事件监听
  235. */
  236. function listEventBind($input, $dropdownMenu, opts) {
  237. $dropdownMenu.find('tbody tr').each(function () {
  238. $(this).off('mouseenter').on("mouseenter", function () {
  239. unHoverAll($dropdownMenu, opts);
  240. $(this).addClass(opts.listHoverCSS);
  241. }).off('mousedown').on("mousedown", function () {
  242. setValue($input, getPointKeyword($(this)), opts);
  243. setBackground ($input, opts);
  244. });
  245. });
  246. }
  247. /**
  248. * 下拉列表刷新
  249. * 作为 getData 的 callback 函数调用
  250. */
  251. function refreshDropMenu($input, data, opts) {
  252. var $dropdownMenu = $input.parent().find("ul.dropdown-menu"),
  253. len, i, j, index = 0,
  254. html = ['<table class="table table-condensed table-sm">'],
  255. thead, tr,
  256. idValue, keyValue; //作为输入框 data-id 和内容的字段值
  257. data = opts.processData(data);
  258. if (data === false || (len = data.value.length) === 0) {
  259. $dropdownMenu.empty().hide();
  260. return $input;
  261. }
  262. var widths = opts.widths;
  263. //生成表头
  264. if (opts.showHeader) {
  265. thead = "<thead><tr>";
  266. for (j in data.value[0]) {
  267. if (inEffectiveFields(j, opts) === false) {
  268. continue;
  269. }
  270. //lfg
  271. var width = '';
  272. if( widths[j]!==undefined && widths[j] !=''){
  273. width=' style="width:' + widths[j] +'"';
  274. }
  275. var headText = opts.effectiveFieldsAlias[j];
  276. if(headText===undefined){
  277. headText = j;
  278. }
  279. if ( index === 0 ) {
  280. //表头第一列记录总数
  281. thead += '<th ' + width +' >' + headText + "(" + len + ")" + '</th>';
  282. } else {
  283. thead += '<th ' + width +' >' + headText + '</th>';
  284. }
  285. index++;
  286. }
  287. thead += "</tr></thead>";
  288. html.push(thead);
  289. }
  290. html.push("<tbody>");
  291. //console.log(data, len);
  292. //按列加数据
  293. for (i = 0; i < len; i++) {
  294. index = 0;
  295. tr = "";
  296. idValue = data.value[i][opts.idField] || "";
  297. keyValue = data.value[i][opts.keyField] || "";
  298. for (j in data.value[i]) {
  299. //标记作为 value 和 作为 id 的值
  300. if (!keyValue && opts.indexKey === index) {
  301. keyValue = data.value[i][j];
  302. }
  303. if (!idValue && opts.indexId === index) {
  304. idValue = data.value[i][j];
  305. }
  306. index++;
  307. //过滤无效字段
  308. if (inEffectiveFields(j, opts) === false) {
  309. continue;
  310. }
  311. tr +='<td data-name="' + j + '">' + data.value[i][j] + '</td>';
  312. }
  313. tr = '<tr data-index="' + (data.value[i].__index || i) + '" data-id="' + idValue +
  314. '" data-key="' + keyValue +'">' + tr + '</tr>';
  315. html.push(tr);
  316. }
  317. html.push('</tbody></table>');
  318. $dropdownMenu.html(html.join("")).show();
  319. listEventBind($input, $dropdownMenu, opts);
  320. //scrollbar 存在时,调整 padding
  321. if (
  322. $dropdownMenu.css("max-height") &&
  323. Number($dropdownMenu.css("max-height").replace("px", "")) <
  324. Number($dropdownMenu.find("table:eq(0)").css("height").replace("px", "")) &&
  325. Number($dropdownMenu.css("min-width").replace("px", "")) <
  326. Number($dropdownMenu.css("width").replace("px", ""))
  327. ) {
  328. $dropdownMenu.css("padding-right", "20px").find("table:eq(0)").css("margin-bottom", "20px");
  329. } else {
  330. $dropdownMenu.css("padding-right", 0).find("table:eq(0)").css("margin-bottom", 0);
  331. }
  332. adjustDropMenuPos ($input, $dropdownMenu, opts);
  333. return $input;
  334. }
  335. /**
  336. * 通过 ajax 或 json 参数获取数据
  337. */
  338. function getData(keyword, $input, callback, options) {
  339. var data, validData, filterData = {value:[]}, i, obj, hyphen, URL, len;
  340. keyword = keyword || "";
  341. /**给了url参数,则从服务器 ajax 请求帮助的 json **/
  342. //console.log(options.url + keyword);
  343. if (options.url) {
  344. hyphen = options.url.indexOf('?') !== -1 ? '&' : "?"; //简单判断,如果url已经存在?,则jsonp的连接符应该为&
  345. URL = options.jsonp ? [options.url + keyword, hyphen, options.jsonp, '=?'].join('') : options.url + keyword; //开启jsonp,则修订url,不可以用param传递,?会被编码为%3F
  346. $.ajax({
  347. type: 'GET',
  348. /*data: "word=" + word,*/
  349. url: URL,
  350. dataType: 'json',
  351. timeout: 3000
  352. }).done(function(result) {
  353. callback($input, result, options); //为 refreshDropMenu
  354. $input.trigger("onDataRequestSuccess", result);
  355. if (options.getDataMethod === "firstByUrl") {
  356. options.data = result;
  357. options.url = null;
  358. } else {
  359. options.result = options.processData(result);
  360. }
  361. }).fail(handleError);
  362. } else {
  363. /**没有给出url 参数,则从 data 参数获取或自行构造data帮助内容 **/
  364. data = options.data;
  365. validData = checkData(data);
  366. //本地的 data 数据,则在本地过滤
  367. if (validData) { //输入不为空时则进行匹配
  368. if (!keyword) {
  369. filterData = data;
  370. } else {
  371. len = data.value.length;
  372. for (i = 0; i < len; i++) {
  373. for (obj in data.value[i]) {
  374. if (
  375. $.trim(data.value[i][obj]) &&
  376. (inSearchFields(obj, options) || inEffectiveFields(obj, options)) &&
  377. (data.value[i][obj].toString().indexOf(keyword) !== -1 || keyword.indexOf(data.value[i][obj]) !== -1)
  378. ){
  379. filterData.value.push(data.value[i]);
  380. filterData.value[filterData.value.length -1].__index = i;
  381. break;
  382. }
  383. }
  384. }
  385. }
  386. }
  387. callback($input, filterData, options);
  388. }//else
  389. }
  390. /**
  391. * 数据处理
  392. * url 获取数据时,对数据的处理,作为 getData 之后的回调处理
  393. */
  394. function processData(data) {
  395. return checkData(data);
  396. }
  397. var methods = {
  398. init: function(opts) {
  399. //参数设置
  400. var self = this,
  401. options = $.extend({
  402. url: null, //请求数据的 URL 地址
  403. jsonp: null, //设置此参数名,将开启jsonp功能,否则使用json数据结构
  404. data: {}, //提示所用的数据
  405. getDataMethod: "firstByUrl", //获取数据的方式,url:一直从url请求;data:从 options.data 获取;firstByUrl:第一次从Url获取全部数据,之后从options.data获取
  406. delayUntilKeyup: false, //获取数据的方式 为 firstByUrl 时,是否延迟到有输入时才请求数据
  407. indexId: 0, //每组数据的第几个数据,作为input输入框的 data-id,设为 -1 且 idField 为空则不设置此值
  408. indexKey: 0, //每组数据的第几个数据,作为input输入框的内容
  409. idField: "", //每组数据的哪个字段作为 data-id,优先级高于 indexId 设置(推荐)
  410. keyField: "", //每组数据的哪个字段作为输入框内容,优先级高于 indexKey 设置(推荐)
  411. effectiveFields: [], //有效显示于列表中的字段,非有效字段都会过滤,默认全部,对自定义getData方法无效
  412. effectiveFieldsAlias: {}, //有效字段的别名对象,用于 header 的显示
  413. searchFields: [], //有效搜索字段,从前端搜索过滤数据时使用。effectiveFields 配置字段也会用于搜索过滤
  414. showHeader: false, //是否显示选择列表的 header。为 true 时,有效字段大于一列则显示表头
  415. showBtn: true, //是否显示下拉按钮
  416. allowNoKeyword: true, //是否允许无关键字时请求数据
  417. multiWord: false, //以分隔符号分割的多关键字支持
  418. separator: ",", //多关键字支持时的分隔符,默认为半角逗号
  419. processData: processData, //格式化数据的方法,返回数据格式参考 data 参数
  420. getData: getData, //获取数据的方法
  421. autoMinWidth: false, //是否自动最小宽度,设为 false 则最小宽度不小于输入框宽度
  422. autoDropup: false, //选择菜单是否自动判断向上展开。设为 true,则当下拉菜单高度超过窗体,且向上方向不会被窗体覆盖,则选择菜单向上弹出
  423. autoSelect: true, //键盘向上/下方向键时,是否自动选择值
  424. listAlign: "left", //提示列表对齐位置,left/right/auto
  425. inputBgColor: '', //输入框背景色,当与容器背景色不同时,可能需要该项的配置
  426. inputWarnColor: "rgba(255,0,0,.1)", //输入框内容不是下拉列表选择时的警告色
  427. listStyle: {
  428. "padding-top": 0, "max-height": "375px", "max-width": "800px",
  429. "overflow": "auto", "width": "auto",
  430. "transition": "0.3s", "-webkit-transition": "0.3s", "-moz-transition": "0.3s", "-o-transition": "0.3s"
  431. }, //列表的样式控制
  432. listHoverStyle: 'background: #07d; color:#fff', //提示框列表鼠标悬浮的样式
  433. listHoverCSS: "jhover", //提示框列表鼠标悬浮的样式名称
  434. keyLeft: 37, //向左方向键
  435. keyUp: 38, //向上方向键
  436. keyRight: 39, //向右方向键
  437. keyDown: 40, //向下方向键
  438. keyEnter: 13 //回车键,
  439. ,widths:{} //下拉列表宽度
  440. }, opts);
  441. //默认配置,配置有效显示字段多于一个,则显示列表表头,否则不显示
  442. if (!opts.showHeader && options.effectiveFields && options.effectiveFields.length > 1) {
  443. options.showHeader = true;
  444. }
  445. if (options.getDataMethod === "firstByUrl" && options.url && ! options.delayUntilKeyup) {
  446. var hyphen = opts.url.indexOf('?') !== -1 ? '&' : "?", //简单判断,如果url已经存在?,则jsonp的连接符应该为&
  447. URL = opts.jsonp ? [opts.url, hyphen, opts.jsonp, '=?'].join('') : opts.url; //开启jsonp,则修订url,不可以用param传递,?会被编码为%3F
  448. $.ajax({
  449. type: 'GET',
  450. url: URL,
  451. dataType: 'json',
  452. timeout: 5000
  453. }).done(function(result) {
  454. options.data = result;
  455. options.url = null;
  456. $(self).trigger("onDataRequestSuccess", result);
  457. }).fail(function (o, err) {
  458. console.error(URL + " : " + err);
  459. });
  460. }
  461. //鼠标滑动到条目样式
  462. $("head:eq(0)").append('<style>.' + options.listHoverCSS + '{' + options.listHoverStyle + '}</style>');
  463. return self.each(function(){
  464. var $input = $(this),
  465. $dropdownMenu = $input.parents(".input-group:eq(0)").find("ul.dropdown-menu");
  466. //验证输入框对象是否符合条件
  467. if(checkInput(this, options) === false){
  468. console.warn('不是一个标准的 bootstrap 下拉式菜单:', this);
  469. return;
  470. }
  471. //是否显示 button 按钮
  472. if (! options.showBtn) {
  473. $input.css('border-radius', '4px')
  474. .parents(".input-group:eq(0)").css('width', '100%')
  475. .find('.input-group-btn>.btn').hide();
  476. }
  477. //移除 disabled 类,并禁用自动完成
  478. $input.removeClass("disabled").attr("disabled", false).attr("autocomplete", "off");
  479. //dropdown-menu 增加修饰
  480. $dropdownMenu.css(options.listStyle);
  481. //默认背景色
  482. if (! options.inputBgColor) {
  483. options.inputBgColor = $input.css("background-color");
  484. }
  485. //开始事件处理
  486. $input.on("keydown", function (event) {
  487. var currentList, tipsKeyword = '';//提示列表上被选中的关键字
  488. //console.log("input keydown");
  489. //$(this).attr("data-id", "");
  490. if ($dropdownMenu.css('display') !== 'none') { //当提示层显示时才对键盘事件处理
  491. currentList = $dropdownMenu.find('.' + options.listHoverCSS);
  492. tipsKeyword = '';//提示列表上被选中的关键字
  493. if (event.keyCode === options.keyDown) { //如果按的是向下方向键
  494. if (currentList.length === 0) {
  495. //如果提示列表没有一个被选中,则将列表第一个选中
  496. tipsKeyword = getPointKeyword($dropdownMenu.find('table tbody tr:first').mouseover());
  497. } else if (currentList.next().length === 0) {
  498. //如果是最后一个被选中,则取消选中,即可认为是输入框被选中,并恢复输入的值
  499. unHoverAll($dropdownMenu, options);
  500. if (options.autoSelect) {
  501. $(this).val($(this).attr('alt')).attr("data-id", "");
  502. }
  503. } else {
  504. unHoverAll($dropdownMenu, options);
  505. //将原先选中列的下一列选中
  506. if (currentList.next().length !== 0) {
  507. tipsKeyword = getPointKeyword(currentList.next().mouseover());
  508. }
  509. }
  510. //控制滑动条
  511. adjustScroll($input, $dropdownMenu, options);
  512. if (! options.autoSelect) {
  513. return;
  514. }
  515. } else if (event.keyCode === options.keyUp) { //如果按的是向上方向键
  516. if (currentList.length === 0) {
  517. tipsKeyword = getPointKeyword($dropdownMenu.find('table tbody tr:last').mouseover());
  518. } else if (currentList.prev().length === 0) {
  519. unHoverAll($dropdownMenu, options);
  520. if (options.autoSelect) {
  521. $(this).val($(this).attr('alt')).attr("data-id", "");
  522. }
  523. } else {
  524. unHoverAll($dropdownMenu, options);
  525. if (currentList.prev().length !== 0) {
  526. tipsKeyword = getPointKeyword(currentList.prev().mouseover());
  527. }
  528. }
  529. //控制滑动条
  530. adjustScroll($input, $dropdownMenu, options);
  531. if (! options.autoSelect) {
  532. return;
  533. }
  534. } else if (event.keyCode === options.keyEnter) {
  535. tipsKeyword = getPointKeyword(currentList);
  536. $dropdownMenu.hide().empty();
  537. } else {
  538. $(this).attr("data-id", "");
  539. }
  540. //设置值 tipsKeyword
  541. //console.log(tipsKeyword);
  542. if (tipsKeyword && tipsKeyword.key !== ''){
  543. setValue($(this), tipsKeyword, options);
  544. }
  545. }
  546. }).on("keyup", function (event) {
  547. var word, words;
  548. //console.log("input keyup");
  549. //如果弹起的键是回车、向上或向下方向键则返回
  550. if (event.keyCode === options.keyDown || event.keyCode === options.keyUp || event.keyCode === options.keyEnter) {
  551. $(this).val($(this).val());//让鼠标输入跳到最后
  552. setBackground ($input, options);
  553. return;
  554. } else {
  555. $(this).attr("data-id", "");
  556. setBackground ($input, options);
  557. }
  558. word = $(this).val();
  559. //若输入框值没有改变或变为空则返回
  560. if ($.trim(word) !== '' && word === $(this).attr('alt')) {
  561. return;
  562. }
  563. //当按下键之前记录输入框值,以方便查看键弹起时值有没有变
  564. $(this).attr('alt', $(this).val());
  565. if (opts.multiWord) {
  566. words = word.split( options.separator || ' ');
  567. word = words[words.length-1];
  568. }
  569. //是否允许空数据查询
  570. if (word.length === 0 && !options.allowNoKeyword) {
  571. return;
  572. }
  573. options.getData($.trim(word), $input, refreshDropMenu, options);
  574. }).on("focus", function () {
  575. //console.log("input focus");
  576. adjustDropMenuPos($input, $dropdownMenu, options);
  577. }).on("blur", function () {
  578. //console.log("blur");
  579. $dropdownMenu.css("display", "");
  580. }).on("click", function () {
  581. //console.log("input click");
  582. var word = $(this).val(), words;
  583. if(
  584. $.trim(word) !== '' &&
  585. word === $(this).attr('alt') &&
  586. $dropdownMenu.find("table tr").length
  587. ) {
  588. return $dropdownMenu.show();
  589. }
  590. if ($dropdownMenu.css('display') !== 'none') {
  591. return;
  592. }
  593. if (options.multiWord) {
  594. words = word.split( options.separator || ' ');
  595. word = words[words.length-1];
  596. }
  597. //是否允许空数据查询
  598. if (word.length === 0 && !options.allowNoKeyword) {
  599. return;
  600. }
  601. //console.log("word", word);
  602. options.getData($.trim(word), $input, refreshDropMenu, options);
  603. });
  604. //下拉按钮点击时
  605. $input.parent().find("button:eq(0)").attr("data-toggle", "").on("click", function(){
  606. var display;
  607. if ($dropdownMenu.css("display") === "none") {
  608. display = "block";
  609. if (options.url) {
  610. $input.click().focus();
  611. } else {
  612. refreshDropMenu($input, options.data, options);
  613. adjustDropMenuPos ($input, $dropdownMenu, options);
  614. }
  615. } else {
  616. display = "none";
  617. }
  618. $dropdownMenu.css("display", display);
  619. });
  620. //列表中滑动时,输入框失去焦点
  621. $dropdownMenu.on("mouseenter", function(){
  622. //console.log('mouseenter')
  623. //$input.blur();
  624. //$(this).show();
  625. //$input.parents(".input-group:eq(0)").find('.input-group-btn>.btn').click();
  626. }).on("mouseleave", function(){
  627. //console.log('mouseleave')
  628. $input.focus();
  629. });
  630. });
  631. },
  632. show: function(){
  633. var data = this.data("bsSuggest");
  634. if (data && data.options) {
  635. this.parent().find("ul.dropdown-menu").show();
  636. }
  637. return this;
  638. },
  639. hide: function(){
  640. var data = this.data('bsSuggest');
  641. if (data && data.options) {
  642. this.parent().find("ul.dropdown-menu").css("display","");
  643. }
  644. return this;
  645. },
  646. disable: function () {
  647. if(!$(this).data("bsSuggest")) {
  648. return false;
  649. }
  650. $(this).attr("disabled", true).parent().find(".input-group-btn>.btn").addClass("disabled");
  651. },
  652. enable: function () {
  653. if(!$(this).data("bsSuggest")) {
  654. return false;
  655. }
  656. $(this).attr("disabled", false).parent().find(".input-group-btn>.btn").removeClass("disabled");
  657. },
  658. destroy: function(){
  659. $(this).off().removeData("bsSuggest").parent().find(".input-group-btn>.btn").off();//.addClass("disabled");
  660. },
  661. version: function() {
  662. return '0.0.1';
  663. }
  664. };
  665. /* 搜索建议插件 */
  666. $.fn.bsSuggest = function(opts) {
  667. //方法判断
  668. if (typeof opts === 'string' && methods[opts] ) {
  669. //如果是方法,则参数第一个为函数名,从第二个开始为函数参数
  670. return methods[opts].apply( this, Array.prototype.slice.call( arguments, 1 ));
  671. }else if(typeof opts === 'object' || !opts){
  672. //调用初始化方法
  673. return methods.init.apply(this, arguments);
  674. }
  675. }
  676. })(window.jQuery);