fselect.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. (function($) {
  2. $.fn.fSelect = function(options) {
  3. if (typeof options == 'string' ) {
  4. var settings = options;
  5. }
  6. else {
  7. var settings = $.extend({
  8. placeholder: 'Select some options',
  9. numDisplayed: 10,
  10. overflowText: '{n} selected',
  11. searchText: 'Search',
  12. showSearch: true
  13. }, options);
  14. }
  15. /**
  16. * Constructor
  17. */
  18. function fSelect(select, settings) {
  19. this.$select = $(select);
  20. this.settings = settings;
  21. this.create();
  22. }
  23. /**
  24. * Prototype class
  25. */
  26. fSelect.prototype = {
  27. create: function() {
  28. var multiple = this.$select.is('[multiple]') ? ' multiple' : '';
  29. this.$select.wrap('<div class="fs-wrap' + multiple + '"></div>');
  30. this.$select.before('<div class="fs-label-wrap"><div class="fs-label">' + this.settings.placeholder + '</div><span class="fs-arrow"></span></div>');
  31. this.$select.before('<div class="fs-dropdown hidden"><div class="fs-options"></div></div>');
  32. this.$select.addClass('hidden');
  33. this.$wrap = this.$select.closest('.fs-wrap');
  34. this.reload();
  35. },
  36. reload: function() {
  37. if (this.settings.showSearch) {
  38. var search = '<div class="fs-search"><input type="search" placeholder="' + this.settings.searchText + '" /></div>';
  39. this.$wrap.find('.fs-dropdown').prepend(search);
  40. }
  41. var choices = this.buildOptions(this.$select);
  42. this.$wrap.find('.fs-options').html(choices);
  43. this.reloadDropdownLabel();
  44. },
  45. destroy: function() {
  46. this.$wrap.find('.fs-label-wrap').remove();
  47. this.$wrap.find('.fs-dropdown').remove();
  48. this.$select.unwrap().removeClass('hidden');
  49. },
  50. buildOptions: function($element) {
  51. var $this = this;
  52. var choices = '';
  53. $element.children().each(function(i, el) {
  54. var $el = $(el);
  55. if ('optgroup' == $el.prop('nodeName').toLowerCase()) {
  56. choices += '<div class="fs-optgroup">';
  57. choices += '<div class="fs-optgroup-label">' + $el.prop('label') + '</div>';
  58. choices += $this.buildOptions($el);
  59. choices += '</div>';
  60. }
  61. else {
  62. var selected = $el.is('[selected]') ? ' selected' : '';
  63. choices += '<div class="fs-option' + selected + '" data-value="' + $el.prop('value') + '"><span class="fs-checkbox"><i></i></span><div class="fs-option-label">' + $el.html() + '</div></div>';
  64. }
  65. });
  66. return choices;
  67. },
  68. reloadDropdownLabel: function() {
  69. var settings = this.settings;
  70. var labelText = [];
  71. this.$wrap.find('.fs-option.selected').each(function(i, el) {
  72. labelText.push($(el).find('.fs-option-label').text());
  73. });
  74. if (labelText.length < 1) {
  75. labelText = settings.placeholder;
  76. }
  77. else if (labelText.length > settings.numDisplayed) {
  78. labelText = settings.overflowText.replace('{n}', labelText.length);
  79. }
  80. else {
  81. labelText = labelText.join(', ');
  82. }
  83. this.$wrap.find('.fs-label').html(labelText);
  84. this.$select.change();
  85. }
  86. }
  87. /**
  88. * Loop through each matching element
  89. */
  90. return this.each(function() {
  91. var data = $(this).data('fSelect');
  92. if (!data) {
  93. data = new fSelect(this, settings);
  94. $(this).data('fSelect', data);
  95. }
  96. if (typeof settings == 'string') {
  97. data[settings]();
  98. }
  99. });
  100. }
  101. /**
  102. * Events
  103. */
  104. window.fSelect = {
  105. 'active': null,
  106. 'idx': -1
  107. };
  108. function setIndexes($wrap) {
  109. $wrap.find('.fs-option:not(.hidden)').each(function(i, el) {
  110. $(el).attr('data-index', i);
  111. $wrap.find('.fs-option').removeClass('hl');
  112. });
  113. $wrap.find('.fs-search input').focus();
  114. window.fSelect.idx = -1;
  115. }
  116. function setScroll($wrap) {
  117. var $container = $wrap.find('.fs-options');
  118. var $selected = $wrap.find('.fs-option.hl');
  119. var itemMin = $selected.offset().top + $container.scrollTop();
  120. var itemMax = itemMin + $selected.outerHeight();
  121. var containerMin = $container.offset().top + $container.scrollTop();
  122. var containerMax = containerMin + $container.outerHeight();
  123. if (itemMax > containerMax) { // scroll down
  124. var to = $container.scrollTop() + itemMax - containerMax;
  125. $container.scrollTop(to);
  126. }
  127. else if (itemMin < containerMin) { // scroll up
  128. var to = $container.scrollTop() - containerMin - itemMin;
  129. $container.scrollTop(to);
  130. }
  131. }
  132. $(document).on('click', '.fs-option', function() {
  133. var $wrap = $(this).closest('.fs-wrap');
  134. if ($wrap.hasClass('multiple')) {
  135. var selected = [];
  136. $(this).toggleClass('selected');
  137. $wrap.find('.fs-option.selected').each(function(i, el) {
  138. selected.push($(el).attr('data-value'));
  139. });
  140. }
  141. else {
  142. var selected = $(this).attr('data-value');
  143. $wrap.find('.fs-option').removeClass('selected');
  144. $(this).addClass('selected');
  145. $wrap.find('.fs-dropdown').hide();
  146. }
  147. $wrap.find('select').val(selected);
  148. $wrap.find('select').fSelect('reloadDropdownLabel');
  149. });
  150. $(document).on('keyup', '.fs-search input', function(e) {
  151. if (40 == e.which) {
  152. $(this).blur();
  153. return;
  154. }
  155. var $wrap = $(this).closest('.fs-wrap');
  156. var keywords = $(this).val();
  157. $wrap.find('.fs-option, .fs-optgroup-label').removeClass('hidden');
  158. if ('' != keywords) {
  159. $wrap.find('.fs-option').each(function() {
  160. var regex = new RegExp(keywords, 'gi');
  161. if (null === $(this).find('.fs-option-label').text().match(regex)) {
  162. $(this).addClass('hidden');
  163. }
  164. });
  165. $wrap.find('.fs-optgroup-label').each(function() {
  166. var num_visible = $(this).closest('.fs-optgroup').find('.fs-option:not(.hidden)').length;
  167. if (num_visible < 1) {
  168. $(this).addClass('hidden');
  169. }
  170. });
  171. }
  172. setIndexes($wrap);
  173. });
  174. $(document).on('click', function(e) {
  175. var $el = $(e.target);
  176. var $wrap = $el.closest('.fs-wrap');
  177. if (0 < $wrap.length) {
  178. if ($el.hasClass('fs-label')) {
  179. window.fSelect.active = $wrap;
  180. var is_hidden = $wrap.find('.fs-dropdown').hasClass('hidden');
  181. $('.fs-dropdown').addClass('hidden');
  182. if (is_hidden) {
  183. $wrap.find('.fs-dropdown').removeClass('hidden');
  184. }
  185. else {
  186. $wrap.find('.fs-dropdown').addClass('hidden');
  187. }
  188. setIndexes($wrap);
  189. }
  190. }
  191. else {
  192. $('.fs-dropdown').addClass('hidden');
  193. window.fSelect.active = null;
  194. }
  195. });
  196. $(document).on('keydown', function(e) {
  197. var $wrap = window.fSelect.active;
  198. if (null === $wrap) {
  199. return;
  200. }
  201. else if (38 == e.which) { // up
  202. e.preventDefault();
  203. $wrap.find('.fs-option').removeClass('hl');
  204. if (window.fSelect.idx > 0) {
  205. window.fSelect.idx--;
  206. $wrap.find('.fs-option[data-index=' + window.fSelect.idx + ']').addClass('hl');
  207. setScroll($wrap);
  208. }
  209. else {
  210. window.fSelect.idx = -1;
  211. $wrap.find('.fs-search input').focus();
  212. }
  213. }
  214. else if (40 == e.which) { // down
  215. e.preventDefault();
  216. var last_index = $wrap.find('.fs-option:last').attr('data-index');
  217. if (window.fSelect.idx < parseInt(last_index)) {
  218. window.fSelect.idx++;
  219. $wrap.find('.fs-option').removeClass('hl');
  220. $wrap.find('.fs-option[data-index=' + window.fSelect.idx + ']').addClass('hl');
  221. setScroll($wrap);
  222. }
  223. }
  224. else if (32 == e.which || 13 == e.which) { // space, enter
  225. $wrap.find('.fs-option.hl').click();
  226. }
  227. else if (27 == e.which) { // esc
  228. $('.fs-dropdown').addClass('hidden');
  229. window.fSelect.active = null;
  230. }
  231. });
  232. })(jQuery);