jquery.searchableSelect.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  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. return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
  9. };
  10. });
  11. $.searchableSelect = function(element, options) {
  12. this.element = element;
  13. this.options = options || {};
  14. this.init();
  15. var _this = this;
  16. this.searchableElement.click(function(event){
  17. // event.stopPropagation();
  18. _this.show();
  19. }).on('keydown', function(event){
  20. if (event.which === 13 || event.which === 40 || event.which == 38){
  21. event.preventDefault();
  22. _this.show();
  23. }
  24. });
  25. $(document).on('click', null, function(event){
  26. if(_this.searchableElement.has($(event.target)).length === 0)
  27. _this.hide();
  28. });
  29. this.input.on('keydown', function(event){
  30. event.stopPropagation();
  31. if(event.which === 13){ //enter
  32. event.preventDefault();
  33. _this.selectCurrentHoverItem();
  34. _this.hide();
  35. } else if (event.which == 27) { //ese
  36. _this.hide();
  37. } else if (event.which == 40) { //down
  38. _this.hoverNextItem();
  39. } else if (event.which == 38) { //up
  40. _this.hoverPreviousItem();
  41. }
  42. }).on('keyup', function(event){
  43. if(event.which != 13 && event.which != 27 && event.which != 38 && event.which != 40)
  44. _this.filter();
  45. })
  46. }
  47. var $sS = $.searchableSelect;
  48. $sS.fn = $sS.prototype = {
  49. version: '0.0.1'
  50. };
  51. $sS.fn.extend = $sS.extend = $.extend;
  52. $sS.fn.extend({
  53. init: function(){
  54. var _this = this;
  55. this.element.hide();
  56. this.searchableElement = $('<div tabindex="0" class="searchable-select"></div>');
  57. this.holder = $('<div class="searchable-select-holder"></div>');
  58. this.dropdown = $('<div class="searchable-select-dropdown searchable-select-hide"></div>');
  59. this.input = $('<input type="text" class="searchable-select-input" />');
  60. this.items = $('<div class="searchable-select-items"></div>');
  61. this.caret = $('<span class="searchable-select-caret"></span>');
  62. this.scrollPart = $('<div class="searchable-scroll"></div>');
  63. this.hasPrivious = $('<div class="searchable-has-privious">...</div>');
  64. this.hasNext = $('<div class="searchable-has-next">...</div>');
  65. this.hasNext.on('mouseenter', function(){
  66. _this.hasNextTimer = null;
  67. var f = function(){
  68. var scrollTop = _this.items.scrollTop();
  69. _this.items.scrollTop(scrollTop + 20);
  70. _this.hasNextTimer = setTimeout(f, 50);
  71. }
  72. f();
  73. }).on('mouseleave', function(event) {
  74. clearTimeout(_this.hasNextTimer);
  75. });
  76. this.hasPrivious.on('mouseenter', function(){
  77. _this.hasPriviousTimer = null;
  78. var f = function(){
  79. var scrollTop = _this.items.scrollTop();
  80. _this.items.scrollTop(scrollTop - 20);
  81. _this.hasPriviousTimer = setTimeout(f, 50);
  82. }
  83. f();
  84. }).on('mouseleave', function(event) {
  85. clearTimeout(_this.hasPriviousTimer);
  86. });
  87. this.dropdown.append(this.input);
  88. this.dropdown.append(this.scrollPart);
  89. this.scrollPart.append(this.hasPrivious);
  90. this.scrollPart.append(this.items);
  91. this.scrollPart.append(this.hasNext);
  92. this.searchableElement.append(this.caret);
  93. this.searchableElement.append(this.holder);
  94. this.searchableElement.append(this.dropdown);
  95. this.element.after(this.searchableElement);
  96. this.buildItems();
  97. this.setPriviousAndNextVisibility();
  98. },
  99. filter: function(){
  100. var text = this.input.val();
  101. this.items.find('.searchable-select-item').addClass('searchable-select-hide');
  102. this.items.find('.searchable-select-item:searchableSelectContains('+text+')').removeClass('searchable-select-hide');
  103. if(this.currentSelectedItem.hasClass('searchable-select-hide') && this.items.find('.searchable-select-item:not(.searchable-select-hide)').length > 0){
  104. this.hoverFirstNotHideItem();
  105. }
  106. this.setPriviousAndNextVisibility();
  107. },
  108. hoverFirstNotHideItem: function(){
  109. this.hoverItem(this.items.find('.searchable-select-item:not(.searchable-select-hide)').first());
  110. },
  111. selectCurrentHoverItem: function(){
  112. if(!this.currentHoverItem.hasClass('searchable-select-hide'))
  113. this.selectItem(this.currentHoverItem);
  114. },
  115. hoverPreviousItem: function(){
  116. if(!this.hasCurrentHoverItem())
  117. this.hoverFirstNotHideItem();
  118. else{
  119. var prevItem = this.currentHoverItem.prevAll('.searchable-select-item:not(.searchable-select-hide):first')
  120. if(prevItem.length > 0)
  121. this.hoverItem(prevItem);
  122. }
  123. },
  124. hoverNextItem: function(){
  125. if(!this.hasCurrentHoverItem())
  126. this.hoverFirstNotHideItem();
  127. else{
  128. var nextItem = this.currentHoverItem.nextAll('.searchable-select-item:not(.searchable-select-hide):first')
  129. if(nextItem.length > 0)
  130. this.hoverItem(nextItem);
  131. }
  132. },
  133. buildItems: function(){
  134. var _this = this;
  135. this.element.find('option').each(function(){
  136. var item = $('<div class="searchable-select-item" data-value="'+$(this).attr('value')+'">'+$(this).text()+'</div>');
  137. if(this.selected){
  138. _this.selectItem(item);
  139. _this.hoverItem(item);
  140. }
  141. item.on('mouseenter', function(){
  142. $(this).addClass('hover');
  143. }).on('mouseleave', function(){
  144. $(this).removeClass('hover');
  145. }).click(function(event){
  146. event.stopPropagation();
  147. _this.selectItem($(this));
  148. _this.hide();
  149. });
  150. _this.items.append(item);
  151. });
  152. this.items.on('scroll', function(){
  153. _this.setPriviousAndNextVisibility();
  154. })
  155. },
  156. show: function(){
  157. this.dropdown.removeClass('searchable-select-hide');
  158. this.input.focus();
  159. this.status = 'show';
  160. this.setPriviousAndNextVisibility();
  161. },
  162. hide: function(){
  163. if(!(this.status === 'show'))
  164. return;
  165. if(this.items.find(':not(.searchable-select-hide)').length === 0)
  166. this.input.val('');
  167. this.dropdown.addClass('searchable-select-hide');
  168. this.searchableElement.trigger('focus');
  169. this.status = 'hide';
  170. },
  171. hasCurrentSelectedItem: function(){
  172. return this.currentSelectedItem && this.currentSelectedItem.length > 0;
  173. },
  174. selectItem: function(item){
  175. if(this.hasCurrentSelectedItem())
  176. this.currentSelectedItem.removeClass('selected');
  177. this.currentSelectedItem = item;
  178. item.addClass('selected');
  179. this.hoverItem(item);
  180. this.holder.text(item.text());
  181. var value = item.data('value');
  182. this.holder.data('value', value);
  183. this.element.val(value);
  184. if(this.options.afterSelectItem){
  185. this.options.afterSelectItem.apply(this);
  186. }
  187. },
  188. hasCurrentHoverItem: function(){
  189. return this.currentHoverItem && this.currentHoverItem.length > 0;
  190. },
  191. hoverItem: function(item){
  192. if(this.hasCurrentHoverItem())
  193. this.currentHoverItem.removeClass('hover');
  194. if(item.outerHeight() + item.position().top > this.items.height())
  195. this.items.scrollTop(this.items.scrollTop() + item.outerHeight() + item.position().top - this.items.height());
  196. else if(item.position().top < 0)
  197. this.items.scrollTop(this.items.scrollTop() + item.position().top);
  198. this.currentHoverItem = item;
  199. item.addClass('hover');
  200. },
  201. setPriviousAndNextVisibility: function(){
  202. if(this.items.scrollTop() === 0){
  203. this.hasPrivious.addClass('searchable-select-hide');
  204. this.scrollPart.removeClass('has-privious');
  205. } else {
  206. this.hasPrivious.removeClass('searchable-select-hide');
  207. this.scrollPart.addClass('has-privious');
  208. }
  209. if(this.items.scrollTop() + this.items.innerHeight() >= this.items[0].scrollHeight){
  210. this.hasNext.addClass('searchable-select-hide');
  211. this.scrollPart.removeClass('has-next');
  212. } else {
  213. this.hasNext.removeClass('searchable-select-hide');
  214. this.scrollPart.addClass('has-next');
  215. }
  216. }
  217. });
  218. $.fn.searchableSelect = function(options){
  219. this.each(function(){
  220. var sS = new $sS($(this), options);
  221. });
  222. return this;
  223. };
  224. })(jQuery);