/**
 * 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);
    }
  };
});