You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

249 lines
8.6 KiB

  1. /**
  2. * jQuery Editable Select
  3. * Indri Muska <indrimuska@gmail.com>
  4. *
  5. * Source on GitHub @ https://github.com/indrimuska/jquery-editable-select
  6. */
  7. +(function ($) {
  8. // jQuery Editable Select
  9. EditableSelect = function (select, options) {
  10. var that = this;
  11. this.options = options;
  12. this.$select = $(select);
  13. this.$input = $('<input type="text" autocomplete="off">');
  14. this.$list = $('<ul class="es-list">');
  15. this.utility = new EditableSelectUtility(this);
  16. if (['focus', 'manual'].indexOf(this.options.trigger) < 0) this.options.trigger = 'focus';
  17. if (['default', 'fade', 'slide'].indexOf(this.options.effects) < 0) this.options.effects = 'default';
  18. if (isNaN(this.options.duration) && ['fast', 'slow'].indexOf(this.options.duration) < 0) this.options.duration = 'fast';
  19. // create text input
  20. var events = $._data(select, "events");
  21. if (events) {
  22. Object.keys(events).forEach(key => {
  23. var event = events[key][0];
  24. this.$input.bind(event.type + "." + event.namespace, event.handler);
  25. });
  26. }
  27. this.$select.replaceWith(this.$input);
  28. this.$list.appendTo(this.options.appendTo || this.$input.parent());
  29. // initalization
  30. this.utility.initialize();
  31. this.utility.initializeList();
  32. this.utility.initializeInput();
  33. this.utility.trigger('created');
  34. }
  35. EditableSelect.DEFAULTS = { filter: true, effects: 'default', duration: 'fast', trigger: 'focus' };
  36. EditableSelect.prototype.filter = function () {
  37. var hiddens = 0;
  38. var search = this.$input.val().toLowerCase().trim();
  39. this.$list.find('li').addClass('es-visible').show();
  40. if (this.options.filter) {
  41. hiddens = this.$list.find('li').filter(function (i, li) { return $(li).text().toLowerCase().indexOf(search) < 0; }).hide().removeClass('es-visible').length;
  42. if (this.$list.find('li').length == hiddens) this.hide();
  43. }
  44. };
  45. EditableSelect.prototype.show = function () {
  46. this.$list.css({
  47. top: this.$input.position().top + this.$input.outerHeight() - 1,
  48. left: this.$input.position().left,
  49. width: this.$input.outerWidth()
  50. });
  51. if (!this.$list.is(':visible') && this.$list.find('li.es-visible').length > 0) {
  52. var fns = { default: 'show', fade: 'fadeIn', slide: 'slideDown' };
  53. var fn = fns[this.options.effects];
  54. this.utility.trigger('show');
  55. this.$input.addClass('open');
  56. this.$list[fn](this.options.duration, $.proxy(this.utility.trigger, this.utility, 'shown'));
  57. }
  58. };
  59. EditableSelect.prototype.hide = function () {
  60. var fns = { default: 'hide', fade: 'fadeOut', slide: 'slideUp' };
  61. var fn = fns[this.options.effects];
  62. this.utility.trigger('hide');
  63. this.$input.removeClass('open');
  64. this.$list[fn](this.options.duration, $.proxy(this.utility.trigger, this.utility, 'hidden'));
  65. };
  66. EditableSelect.prototype.select = function ($li) {
  67. if (!this.$list.has($li) || !$li.is('li.es-visible:not([disabled])')) return;
  68. this.$input.val($li.text());
  69. if (this.options.filter) this.hide();
  70. this.filter();
  71. this.utility.trigger('select', $li);
  72. };
  73. EditableSelect.prototype.add = function (text, index, attrs, data) {
  74. var $li = $('<li>').html(text);
  75. var $option = $('<option>').text(text);
  76. var last = this.$list.find('li').length;
  77. if (isNaN(index)) index = last;
  78. else index = Math.min(Math.max(0, index), last);
  79. if (index == 0) {
  80. this.$list.prepend($li);
  81. this.$select.prepend($option);
  82. } else {
  83. this.$list.find('li').eq(index - 1).after($li);
  84. this.$select.find('option').eq(index - 1).after($option);
  85. }
  86. this.utility.setAttributes($li, attrs, data);
  87. this.utility.setAttributes($option, attrs, data);
  88. this.filter();
  89. };
  90. EditableSelect.prototype.remove = function (index) {
  91. var last = this.$list.find('li').length;
  92. if (isNaN(index)) index = last;
  93. else index = Math.min(Math.max(0, index), last - 1);
  94. this.$list.find('li').eq(index).remove();
  95. this.$select.find('option').eq(index).remove();
  96. this.filter();
  97. };
  98. EditableSelect.prototype.clear = function () {
  99. this.$list.find('li').remove();
  100. this.$select.find('option').remove();
  101. this.filter();
  102. };
  103. EditableSelect.prototype.destroy = function () {
  104. this.$list.off('mousemove mousedown mouseup');
  105. this.$input.off('focus blur input keydown');
  106. this.$input.replaceWith(this.$select);
  107. this.$list.remove();
  108. this.$select.removeData('editable-select');
  109. };
  110. // Utility
  111. EditableSelectUtility = function (es) {
  112. this.es = es;
  113. }
  114. EditableSelectUtility.prototype.initialize = function () {
  115. var that = this;
  116. that.setAttributes(that.es.$input, that.es.$select[0].attributes, that.es.$select.data());
  117. that.es.$input.addClass('es-input').data('editable-select', that.es);
  118. that.es.$select.find('option').each(function (i, option) {
  119. var $option = $(option).remove();
  120. that.es.add($option.text(), i, option.attributes, $option.data());
  121. if ($option.attr('selected')) that.es.$input.val($option.text());
  122. });
  123. that.es.filter();
  124. };
  125. EditableSelectUtility.prototype.initializeList = function () {
  126. var that = this;
  127. that.es.$list
  128. .on('mousemove', 'li:not([disabled])', function () {
  129. that.es.$list.find('.selected').removeClass('selected');
  130. $(this).addClass('selected');
  131. })
  132. .on('mousedown', 'li', function (e) {
  133. if ($(this).is('[disabled]')) e.preventDefault();
  134. else that.es.select($(this));
  135. })
  136. .on('mouseup', function () {
  137. that.es.$list.find('li.selected').removeClass('selected');
  138. });
  139. };
  140. EditableSelectUtility.prototype.initializeInput = function () {
  141. var that = this;
  142. switch (this.es.options.trigger) {
  143. default:
  144. case 'focus':
  145. that.es.$input
  146. .on('focus', $.proxy(that.es.show, that.es))
  147. .on("blur", $.proxy(function() {
  148. if ($(".es-list:hover").length === 0) {
  149. that.es.hide();
  150. } else {
  151. this.$input.focus();
  152. }
  153. }, that.es
  154. ));
  155. break;
  156. case 'manual':
  157. break;
  158. }
  159. that.es.$input.on('input keydown', function (e) {
  160. switch (e.keyCode) {
  161. case 38: // Up
  162. var visibles = that.es.$list.find('li.es-visible:not([disabled])');
  163. var selectedIndex = visibles.index(visibles.filter('li.selected'));
  164. that.highlight(selectedIndex - 1);
  165. e.preventDefault();
  166. break;
  167. case 40: // Down
  168. var visibles = that.es.$list.find('li.es-visible:not([disabled])');
  169. var selectedIndex = visibles.index(visibles.filter('li.selected'));
  170. that.highlight(selectedIndex + 1);
  171. e.preventDefault();
  172. break;
  173. case 13: // Enter
  174. if (that.es.$list.is(':visible')) {
  175. that.es.select(that.es.$list.find('li.selected'));
  176. e.preventDefault();
  177. }
  178. break;
  179. case 9: // Tab
  180. case 27: // Esc
  181. that.es.hide();
  182. break;
  183. default:
  184. that.es.filter();
  185. that.highlight(0);
  186. break;
  187. }
  188. });
  189. };
  190. EditableSelectUtility.prototype.highlight = function (index) {
  191. var that = this;
  192. that.es.show();
  193. setTimeout(function () {
  194. var visibles = that.es.$list.find('li.es-visible');
  195. var oldSelected = that.es.$list.find('li.selected').removeClass('selected');
  196. var oldSelectedIndex = visibles.index(oldSelected);
  197. if (visibles.length > 0) {
  198. var selectedIndex = (visibles.length + index) % visibles.length;
  199. var selected = visibles.eq(selectedIndex);
  200. var top = selected.position().top;
  201. selected.addClass('selected');
  202. if (selectedIndex < oldSelectedIndex && top < 0)
  203. that.es.$list.scrollTop(that.es.$list.scrollTop() + top);
  204. if (selectedIndex > oldSelectedIndex && top + selected.outerHeight() > that.es.$list.outerHeight())
  205. that.es.$list.scrollTop(that.es.$list.scrollTop() + selected.outerHeight() + 2 * (top - that.es.$list.outerHeight()));
  206. }
  207. });
  208. };
  209. EditableSelectUtility.prototype.setAttributes = function ($element, attrs, data) {
  210. $.each(attrs || {}, function (i, attr) { $element.attr(attr.name, attr.value); });
  211. $element.data(data);
  212. };
  213. EditableSelectUtility.prototype.trigger = function (event) {
  214. var params = Array.prototype.slice.call(arguments, 1);
  215. var args = [event + '.editable-select'];
  216. args.push(params);
  217. this.es.$select.trigger.apply(this.es.$select, args);
  218. this.es.$input.trigger.apply(this.es.$input, args);
  219. };
  220. // Plugin
  221. Plugin = function (option) {
  222. var args = Array.prototype.slice.call(arguments, 1);
  223. return this.each(function () {
  224. var $this = $(this);
  225. var data = $this.data('editable-select');
  226. var options = $.extend({}, EditableSelect.DEFAULTS, $this.data(), typeof option == 'object' && option);
  227. if (!data) data = new EditableSelect(this, options);
  228. if (typeof option == 'string') data[option].apply(data, args);
  229. });
  230. }
  231. $.fn.editableSelect = Plugin;
  232. $.fn.editableSelect.Constructor = EditableSelect;
  233. })(jQuery);