searchable_select.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // Author: David Qin
  2. // E-mail: david@hereapp.cn
  3. // Date: 2014-11-05
  4. (function ($) {
  5. // a case insensitive jQuery :contains selector
  6. $.expr[":"].searchableSelectContains = $.expr.createPseudo(function (arg) {
  7. return function (elem) {
  8. if (arg) return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
  9. else $(".searchable-select-items>div").removeClass("searchable-select-hide");
  10. };
  11. });
  12. $.searchableSelect = function (element, options) {
  13. this.element = element;
  14. this.options = options || {};
  15. this.init();
  16. var _this = this;
  17. this.searchableElement.click(function (event) {
  18. // event.stopPropagation();
  19. _this.show();
  20. }).on('keydown', function (event) {
  21. if (event.which === 13 || event.which === 40 || event.which == 38) {
  22. event.preventDefault();
  23. _this.show();
  24. }
  25. });
  26. $(document).on('click', null, function (event) {
  27. if (_this.searchableElement.has($(event.target)).length === 0)
  28. _this.hide();
  29. });
  30. this.input.on('keydown', function (event) {
  31. event.stopPropagation();
  32. if (event.which === 13) { //enter
  33. event.preventDefault();
  34. _this.selectCurrentHoverItem();
  35. //回车回调
  36. if (_this.options.enterCallback) _this.options.enterCallback(_this.input);
  37. _this.hide();
  38. } else if (event.which == 27) { //ese
  39. _this.hide();
  40. } else if (event.which == 40) { //down
  41. _this.hoverNextItem();
  42. } else if (event.which == 38) { //up
  43. _this.hoverPreviousItem();
  44. }
  45. }).on('keyup', function (event) {
  46. if (event.which != 13 && event.which != 27 && event.which != 38 && event.which != 40)
  47. _this.filter();
  48. //监听搜索输入框回调
  49. if (_this.options.inputKeyUpCallback) _this.options.inputKeyUpCallback($(this));
  50. })
  51. }
  52. var $sS = $.searchableSelect;
  53. $sS.fn = $sS.prototype = {
  54. version: '0.0.1'
  55. };
  56. $sS.fn.extend = $sS.extend = $.extend;
  57. $sS.fn.extend({
  58. init: function () {
  59. var _this = this;
  60. this.element.hide();
  61. this.searchableElement = $('<div tabindex="0" class="searchable-select"></div>');
  62. // this.holder = $('<div class="searchable-select-holder"></div>');//旧的
  63. this.holder = $('<input type="text" readonly class="searchable-select-holder" placeholder="' + (_this.options.placeholder ? _this.options.placeholder : "") + '" />');
  64. this.dropdown = $('<div class="searchable-select-dropdown searchable-select-hide"></div>');
  65. this.input = $('<input type="text" class="searchable-select-input" />');
  66. this.items = $('<div class="searchable-select-items"></div>');
  67. this.caret = $('<i class="layui-edge searchable-select-caret"></i>');
  68. this.scrollPart = $('<div class="searchable-scroll"></div>');
  69. this.hasPrivious = $('<div class="searchable-has-privious">...</div>');
  70. this.hasNext = $('<div class="searchable-has-next">...</div>');
  71. this.hasNext.on('mouseenter', function () {
  72. _this.hasNextTimer = null;
  73. var f = function () {
  74. var scrollTop = _this.items.scrollTop();
  75. _this.items.scrollTop(scrollTop + 20);
  76. _this.hasNextTimer = setTimeout(f, 50);
  77. }
  78. f();
  79. }).on('mouseleave', function (event) {
  80. clearTimeout(_this.hasNextTimer);
  81. });
  82. this.hasPrivious.on('mouseenter', function () {
  83. _this.hasPriviousTimer = null;
  84. var f = function () {
  85. var scrollTop = _this.items.scrollTop();
  86. _this.items.scrollTop(scrollTop - 20);
  87. _this.hasPriviousTimer = setTimeout(f, 50);
  88. }
  89. f();
  90. }).on('mouseleave', function (event) {
  91. clearTimeout(_this.hasPriviousTimer);
  92. });
  93. this.dropdown.append(this.input);
  94. this.dropdown.append(this.scrollPart);
  95. this.scrollPart.append(this.hasPrivious);
  96. this.scrollPart.append(this.items);
  97. this.scrollPart.append(this.hasNext);
  98. this.searchableElement.append(this.caret);
  99. this.searchableElement.append(this.holder);
  100. this.searchableElement.append(this.dropdown);
  101. this.element.after(this.searchableElement);
  102. this.buildItems();
  103. this.setPriviousAndNextVisibility();
  104. },
  105. filter: function () {
  106. var text = this.input.val();
  107. this.items.find('.searchable-select-item').addClass('searchable-select-hide');
  108. this.items.find('.searchable-select-item:searchableSelectContains(' + text + ')').removeClass('searchable-select-hide');
  109. if (this.currentSelectedItem && this.currentSelectedItem.hasClass('searchable-select-hide') && this.items.find('.searchable-select-item:not(.searchable-select-hide)').length > 0) {
  110. this.hoverFirstNotHideItem();
  111. }
  112. this.setPriviousAndNextVisibility();
  113. },
  114. hoverFirstNotHideItem: function () {
  115. this.hoverItem(this.items.find('.searchable-select-item:not(.searchable-select-hide)').first());
  116. },
  117. selectCurrentHoverItem: function () {
  118. if (this.currentHoverItem && !this.currentHoverItem.hasClass('searchable-select-hide'))
  119. this.selectItem(this.currentHoverItem);
  120. },
  121. hoverPreviousItem: function () {
  122. if (!this.hasCurrentHoverItem())
  123. this.hoverFirstNotHideItem();
  124. else {
  125. var prevItem = this.currentHoverItem.prevAll('.searchable-select-item:not(.searchable-select-hide):first')
  126. if (prevItem.length > 0)
  127. this.hoverItem(prevItem);
  128. }
  129. },
  130. hoverNextItem: function () {
  131. if (!this.hasCurrentHoverItem())
  132. this.hoverFirstNotHideItem();
  133. else {
  134. var nextItem = this.currentHoverItem.nextAll('.searchable-select-item:not(.searchable-select-hide):first')
  135. if (nextItem.length > 0)
  136. this.hoverItem(nextItem);
  137. }
  138. },
  139. buildItems: function () {
  140. var _this = this;
  141. this.element.find('option').each(function () {
  142. if ($(this).text().length == 0) return;
  143. var item = $('<div class="searchable-select-item" data-value="' + $(this).attr('value') + '">' + $(this).text() + '</div>');
  144. if (this.selected) {
  145. _this.selectItem(item);
  146. _this.hoverItem(item);
  147. }
  148. item.on('mouseenter', function () {
  149. $(this).addClass('hover');
  150. }).on('mouseleave', function () {
  151. $(this).removeClass('hover');
  152. }).click(function (event) {
  153. event.stopPropagation();
  154. _this.selectItem($(this));
  155. _this.hide();
  156. //选择option后执行回调
  157. if (_this.options.optionCallback) {
  158. var value = $(this).attr("data-value");
  159. var text = $(this).text();
  160. _this.options.optionCallback(value, text);
  161. }
  162. });
  163. _this.items.append(item);
  164. });
  165. this.items.on('scroll', function () {
  166. _this.setPriviousAndNextVisibility();
  167. })
  168. },
  169. show: function () {
  170. this.dropdown.removeClass('searchable-select-hide');
  171. this.input.focus();
  172. this.status = 'show';
  173. this.caret.addClass("selected");//下三角
  174. this.setPriviousAndNextVisibility();
  175. },
  176. hide: function () {
  177. if (!(this.status === 'show'))
  178. return;
  179. if (this.items.find(':not(.searchable-select-hide)').length === 0)
  180. this.input.val('');
  181. this.dropdown.addClass('searchable-select-hide');
  182. this.searchableElement.trigger('focus');
  183. this.status = 'hide';
  184. this.caret.removeClass("selected");//下三角
  185. },
  186. hasCurrentSelectedItem: function () {
  187. return this.currentSelectedItem && this.currentSelectedItem.length > 0;
  188. },
  189. selectItem: function (item) {
  190. if (this.hasCurrentSelectedItem())
  191. this.currentSelectedItem.removeClass('selected');
  192. this.currentSelectedItem = item;
  193. item.addClass('selected');
  194. this.hoverItem(item);
  195. this.holder.val(item.text());
  196. var value = item.data('value');
  197. this.holder.data('value', value);
  198. this.element.val(value);
  199. if (this.options.afterSelectItem) {
  200. this.options.afterSelectItem.apply(this);
  201. }
  202. },
  203. hasCurrentHoverItem: function () {
  204. return this.currentHoverItem && this.currentHoverItem.length > 0;
  205. },
  206. hoverItem: function (item) {
  207. if (this.hasCurrentHoverItem())
  208. this.currentHoverItem.removeClass('hover');
  209. if (item.outerHeight() + item.position().top > this.items.height())
  210. this.items.scrollTop(this.items.scrollTop() + item.outerHeight() + item.position().top - this.items.height());
  211. else if (item.position().top < 0)
  212. this.items.scrollTop(this.items.scrollTop() + item.position().top);
  213. this.currentHoverItem = item;
  214. item.addClass('hover');
  215. },
  216. setPriviousAndNextVisibility: function () {
  217. if (this.items.scrollTop() === 0) {
  218. this.hasPrivious.addClass('searchable-select-hide');
  219. this.scrollPart.removeClass('has-privious');
  220. } else {
  221. this.hasPrivious.removeClass('searchable-select-hide');
  222. this.scrollPart.addClass('has-privious');
  223. }
  224. if (this.items.scrollTop() + this.items.innerHeight() >= this.items[0].scrollHeight) {
  225. this.hasNext.addClass('searchable-select-hide');
  226. this.scrollPart.removeClass('has-next');
  227. } else {
  228. this.hasNext.removeClass('searchable-select-hide');
  229. this.scrollPart.addClass('has-next');
  230. }
  231. }
  232. });
  233. $.fn.searchableSelect = function (options) {
  234. var sS;
  235. this.each(function () {
  236. sS = new $sS($(this), options);
  237. });
  238. return sS;//返回当前对象,那边接收到后可以进行后期的修改对象操作
  239. //return this;//以前的
  240. };
  241. })(jQuery);