jquery.multi-select.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. /*
  2. * MultiSelect v0.9.5
  3. * Copyright (c) 2012 Louis Cuny
  4. *
  5. * This program is free software. It comes without any warranty, to
  6. * the extent permitted by applicable law. You can redistribute it
  7. * and/or modify it under the terms of the Do What The Fuck You Want
  8. * To Public License, Version 2, as published by Sam Hocevar. See
  9. * http://sam.zoy.org/wtfpl/COPYING for more details.
  10. */
  11. !function ($) {
  12. "use strict";
  13. /* MULTISELECT CLASS DEFINITION
  14. * ====================== */
  15. var MultiSelect = function (element, options) {
  16. this.options = options;
  17. this.$element = $(element);
  18. var id = this.$element.attr('id');
  19. this.$container = $('<div/>', { 'id': "ms-"+id, 'class': "ms-container" });
  20. this.$selectableContainer = $('<div/>', { 'class': 'ms-selectable' });
  21. this.$selectionContainer = $('<div/>', { 'class': 'ms-selection' });
  22. this.$selectableUl = $('<ul/>', { 'class': "ms-list" });
  23. this.$selectionUl = $('<ul/>', { 'class': "ms-list" });
  24. this.scrollTo = 0;
  25. this.sanitizeRegexp = new RegExp("\\W+", 'gi');
  26. };
  27. MultiSelect.prototype = {
  28. constructor: MultiSelect,
  29. init: function(){
  30. var that = this,
  31. ms = this.$element;
  32. if (ms.next('.ms-container').length === 0){
  33. ms.css({ position: 'absolute', left: '-9999px' });
  34. ms.attr('id', ms.attr('id') ? ms.attr('id') : 'ms-'+Math.ceil(Math.random()*1000));
  35. var optgroupLabel = null,
  36. optgroupId = null,
  37. optgroupCpt = 0,
  38. optgroupContainerTemplate = '<li class="ms-optgroup-container"></li>',
  39. optgroupUlTemplate = '<ul class="ms-optgroup"></ul>',
  40. optgroupLiTemplate = '<li class="ms-optgroup-label"><span></span></li>';
  41. ms.find('optgroup, option').each(function(){
  42. if ($(this).is('optgroup')){
  43. optgroupLabel = '<span>'+$(this).attr('label')+'</span>';
  44. optgroupId = 'ms-'+ms.attr('id')+'-optgroup-'+optgroupCpt;
  45. var optgroup = $(this),
  46. optgroupSelectable = $(optgroupContainerTemplate),
  47. optgroupSelection = $(optgroupContainerTemplate),
  48. optgroupSelectionLi = $(optgroupLiTemplate),
  49. optgroupSelectableLi = $(optgroupLiTemplate);
  50. if (that.options.selectableOptgroup){
  51. optgroupSelectableLi.on('click', function(){
  52. var values = optgroup.children(':not(:selected)').map(function(){ return $(this).val(); }).get();
  53. that.select(values);
  54. });
  55. optgroupSelectionLi.on('click', function(){
  56. var values = optgroup.children(':selected').map(function(){ return $(this).val(); }).get();
  57. that.deselect(values);
  58. });
  59. }
  60. optgroupSelectableLi.html(optgroupLabel);
  61. optgroupSelectable.attr('id', optgroupId+'-selectable')
  62. .append($(optgroupUlTemplate)
  63. .append(optgroupSelectableLi));
  64. that.$selectableUl.append(optgroupSelectable);
  65. optgroupSelectionLi.html(optgroupLabel);
  66. optgroupSelection.attr('id', optgroupId+'-selection')
  67. .append($(optgroupUlTemplate)
  68. .append(optgroupSelectionLi));
  69. that.$selectionUl.append(optgroupSelection);
  70. optgroupCpt++;
  71. } else {
  72. var attributes = "";
  73. for (var cpt = 0; cpt < this.attributes.length; cpt++){
  74. var attr = this.attributes[cpt];
  75. if(that.isDomNode(attr.name)){
  76. attributes += attr.name+'="'+attr.value+'" ';
  77. }
  78. }
  79. var selectableLi = $('<li '+attributes+'><span>'+$(this).text()+'</span></li>'),
  80. selectedLi = selectableLi.clone();
  81. var value = $(this).val(),
  82. msId = that.sanitize(value, that.sanitizeRegexp);
  83. selectableLi
  84. .data('ms-value', value)
  85. .addClass('ms-elem-selectable')
  86. .attr('id', msId+'-selectable');
  87. selectedLi
  88. .data('ms-value', value)
  89. .addClass('ms-elem-selection')
  90. .attr('id', msId+'-selection')
  91. .hide();
  92. that.$selectionUl.find('.ms-optgroup-label').hide();
  93. if ($(this).prop('disabled') || ms.prop('disabled')){
  94. if (this.selected) {
  95. selectedLi.prop('disabled', true);
  96. selectedLi.addClass(that.options.disabledClass);
  97. } else {
  98. selectableLi.prop('disabled', true);
  99. selectableLi.addClass(that.options.disabledClass);
  100. }
  101. }
  102. if (optgroupId){
  103. that.$selectableUl.children('#'+optgroupId+'-selectable').find('ul').first().append(selectableLi);
  104. that.$selectionUl.children('#'+optgroupId+'-selection').find('ul').first().append(selectedLi);
  105. } else {
  106. that.$selectableUl.append(selectableLi);
  107. that.$selectionUl.append(selectedLi);
  108. }
  109. }
  110. });
  111. if (that.options.selectableHeader){
  112. that.$selectableContainer.append(that.options.selectableHeader);
  113. }
  114. that.$selectableContainer.append(that.$selectableUl);
  115. if (that.options.selectableFooter){
  116. that.$selectableContainer.append(that.options.selectableFooter);
  117. }
  118. if (that.options.selectionHeader){
  119. that.$selectionContainer.append(that.options.selectionHeader);
  120. }
  121. that.$selectionContainer.append(that.$selectionUl);
  122. if (that.options.selectionFooter){
  123. that.$selectionContainer.append(that.options.selectionFooter);
  124. }
  125. that.$container.append(that.$selectableContainer);
  126. that.$container.append(that.$selectionContainer);
  127. ms.after(that.$container);
  128. that.$selectableUl.on('mouseenter', '.ms-elem-selectable', function(){
  129. $('li', that.$container).removeClass('ms-hover');
  130. $(this).addClass('ms-hover');
  131. }).on('mouseleave', function(){
  132. $('li', that.$container).removeClass('ms-hover');
  133. });
  134. var action = that.options.dblClick ? 'dblclick' : 'click';
  135. that.$selectableUl.on(action, '.ms-elem-selectable', function(){
  136. that.select($(this).data('ms-value'));
  137. });
  138. that.$selectionUl.on(action, '.ms-elem-selection', function(){
  139. that.deselect($(this).data('ms-value'));
  140. });
  141. that.$selectionUl.on('mouseenter', '.ms-elem-selection', function(){
  142. $('li', that.$selectionUl).removeClass('ms-hover');
  143. $(this).addClass('ms-hover');
  144. }).on('mouseleave', function(){
  145. $('li', that.$selectionUl).removeClass('ms-hover');
  146. });
  147. that.$selectableUl.on('focusin', function(){
  148. $(this).addClass('ms-focus');
  149. that.$selectionUl.focusout();
  150. }).on('focusout', function(){
  151. $(this).removeClass('ms-focus');
  152. $('li', that.$container).removeClass('ms-hover');
  153. });
  154. that.$selectionUl.on('focusin', function(){
  155. $(this).addClass('ms-focus');
  156. }).on('focusout', function(){
  157. $(this).removeClass('ms-focus');
  158. $('li', that.$container).removeClass('ms-hover');
  159. });
  160. ms.on('focusin', function(){
  161. ms.focusout();
  162. that.$selectableUl.focusin();
  163. }).on('focusout', function(){
  164. that.$selectableUl.removeClass('ms-focus');
  165. that.$selectionUl.removeClass('ms-focus');
  166. });
  167. ms.onKeyDown = function(e, keyContainer){
  168. var ul = that.$container.find('.'+keyContainer).find('.ms-list'),
  169. lis = ul.find('li:visible:not(.ms-optgroup-label, .ms-optgroup-container)'),
  170. lisNumber = lis.length,
  171. liFocused = ul.find('li.ms-hover'),
  172. liFocusedIndex = liFocused.length > 0 ? lis.index(liFocused) : -1,
  173. ulHeight = ul.innerHeight(),
  174. liHeight = lis.first().outerHeight(true),
  175. numberOfLisDisplayed = Math.floor(ulHeight / liHeight),
  176. ulPosition = null;
  177. if (e.keyCode === 32){ // space
  178. if (liFocused.length >0){
  179. if (keyContainer === 'ms-selectable'){
  180. that.select(liFocused.data('ms-value'));
  181. } else {
  182. that.deselect(liFocused.data('ms-value'));
  183. }
  184. lis.removeClass('ms-hover');
  185. that.scrollTo = 0;
  186. ul.scrollTop(that.scrollTo);
  187. }
  188. } else if (e.keyCode === 40){ // Down
  189. if (lis.length > 0){
  190. var nextLiIndex = liFocusedIndex+1,
  191. nextLi = (lisNumber !== nextLiIndex) ? lis.eq(nextLiIndex) : lis.first(),
  192. nextLiPosition = nextLi.position().top;
  193. ulPosition = ul.position().top;
  194. lis.removeClass('ms-hover');
  195. nextLi.addClass('ms-hover');
  196. if (lisNumber === nextLiIndex){
  197. that.scrollTo = 0;
  198. } else if (nextLiPosition >= (ulPosition + (numberOfLisDisplayed * liHeight))){
  199. that.scrollTo += liHeight;
  200. }
  201. ul.scrollTop(that.scrollTo);
  202. }
  203. } else if (e.keyCode === 38){ // Up
  204. if (lis.length > 0){
  205. var prevLiIndex = Math.max(liFocusedIndex-1, -1),
  206. prevLi = lis.eq(prevLiIndex),
  207. prevLiPosition = prevLi.position().top;
  208. ulPosition = ul.position().top;
  209. lis.removeClass('ms-hover');
  210. prevLi.addClass('ms-hover');
  211. if (prevLiPosition <= ulPosition){
  212. that.scrollTo -= liHeight;
  213. } else if (prevLiIndex < 0){
  214. that.scrollTo = (lisNumber - numberOfLisDisplayed) * liHeight;
  215. }
  216. ul.scrollTop(that.scrollTo);
  217. }
  218. } else if (e.keyCode === 37 || e.keyCode === 39){
  219. if (that.$selectableUl.hasClass('ms-focus')){
  220. that.$selectableUl.focusout();
  221. that.$selectionUl.focusin();
  222. } else {
  223. that.$selectableUl.focusin();
  224. that.$selectionUl.focusout();
  225. }
  226. }
  227. };
  228. ms.on('keydown', function(e){
  229. if (ms.is(':focus')){
  230. var keyContainer = that.$selectableUl.hasClass('ms-focus') ? 'ms-selectable' : 'ms-selection';
  231. ms.onKeyDown(e, keyContainer);
  232. }
  233. });
  234. }
  235. var selectedValues = ms.find('option:selected').map(function(){ return $(this).val(); }).get();
  236. that.select(selectedValues, 'init');
  237. if (typeof that.options.afterInit === 'function') {
  238. that.options.afterInit.call(this, this.$container);
  239. }
  240. },
  241. 'refresh' : function() {
  242. $("#ms-"+this.$element.attr("id")).remove();
  243. this.init(this.options);
  244. },
  245. 'select' : function(value, method){
  246. if (typeof value === 'string'){ value = [value]; }
  247. var that = this,
  248. ms = this.$element,
  249. msIds = $.map(value, function(val){ return(that.sanitize(val, that.sanitizeRegexp)); }),
  250. selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable').filter(':not(.'+that.options.disabledClass+')'),
  251. selections = this.$selectionUl.find('#' + msIds.join('-selection, #') + '-selection'),
  252. options = ms.find('option').filter(function(){ return($.inArray(this.value, value) > -1); });
  253. if (selectables.length > 0){
  254. selectables.addClass('ms-selected').hide();
  255. selections.addClass('ms-selected').show();
  256. options.prop('selected', true);
  257. var selectableOptgroups = that.$selectableUl.children('.ms-optgroup-container');
  258. if (selectableOptgroups.length > 0){
  259. selectableOptgroups.each(function(){
  260. var selectablesLi = $(this).find('.ms-elem-selectable');
  261. if (selectablesLi.length === selectablesLi.filter('.ms-selected').length){
  262. $(this).find('.ms-optgroup-label').hide();
  263. }
  264. });
  265. var selectionOptgroups = that.$selectionUl.children('.ms-optgroup-container');
  266. selectionOptgroups.each(function(){
  267. var selectionsLi = $(this).find('.ms-elem-selection');
  268. if (selectionsLi.filter('.ms-selected').length > 0){
  269. $(this).find('.ms-optgroup-label').show();
  270. }
  271. });
  272. }
  273. if (method !== 'init'){
  274. that.$selectionUl.focusout();
  275. that.$selectableUl.focusin();
  276. ms.trigger('change');
  277. if (typeof that.options.afterSelect === 'function') {
  278. that.options.afterSelect.call(this, value);
  279. }
  280. }
  281. }
  282. },
  283. 'deselect' : function(value){
  284. if (typeof value === 'string'){ value = [value]; }
  285. var that = this,
  286. ms = this.$element,
  287. msIds = $.map(value, function(val){ return(that.sanitize(val, that.sanitizeRegexp)); }),
  288. selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable'),
  289. selections = this.$selectionUl.find('#' + msIds.join('-selection, #')+'-selection').filter('.ms-selected'),
  290. options = ms.find('option').filter(function(){ return($.inArray(this.value, value) > -1); });
  291. if (selections.length > 0){
  292. selectables.removeClass('ms-selected').show();
  293. selections.removeClass('ms-selected').hide();
  294. options.prop('selected', false);
  295. var selectableOptgroups = that.$selectableUl.children('.ms-optgroup-container');
  296. if (selectableOptgroups.length > 0){
  297. selectableOptgroups.each(function(){
  298. var selectablesLi = $(this).find('.ms-elem-selectable');
  299. if (selectablesLi.filter(':not(.ms-selected)').length > 0){
  300. $(this).find('.ms-optgroup-label').show();
  301. }
  302. });
  303. var selectionOptgroups = that.$selectionUl.children('.ms-optgroup-container');
  304. selectionOptgroups.each(function(){
  305. var selectionsLi = $(this).find('.ms-elem-selection');
  306. if (selectionsLi.filter('.ms-selected').length === 0){
  307. $(this).find('.ms-optgroup-label').hide();
  308. }
  309. });
  310. }
  311. this.$selectableUl.focusout();
  312. this.$selectionUl.focusin();
  313. ms.trigger('change');
  314. if (typeof that.options.afterDeselect === 'function') {
  315. that.options.afterDeselect.call(this, value);
  316. }
  317. }
  318. },
  319. 'select_all' : function(){
  320. var ms = this.$element,
  321. values = ms.val();
  322. ms.find('option').prop('selected', true);
  323. this.$selectableUl.find('.ms-elem-selectable').addClass('ms-selected').hide();
  324. this.$selectionUl.find('.ms-optgroup-label').show();
  325. this.$selectableUl.find('.ms-optgroup-label').hide();
  326. this.$selectionUl.find('.ms-elem-selection').addClass('ms-selected').show();
  327. this.$selectionUl.focusin();
  328. this.$selectableUl.focusout();
  329. ms.trigger('change');
  330. if (typeof this.options.afterSelect === 'function') {
  331. var selectedValues = $.grep(ms.val(), function(item){
  332. return $.inArray(item, values) < 0;
  333. });
  334. this.options.afterSelect.call(this, selectedValues);
  335. }
  336. },
  337. 'deselect_all' : function(){
  338. var ms = this.$element,
  339. values = ms.val();
  340. ms.find('option').prop('selected', false);
  341. this.$selectableUl.find('.ms-elem-selectable').removeClass('ms-selected').show();
  342. this.$selectionUl.find('.ms-optgroup-label').hide();
  343. this.$selectableUl.find('.ms-optgroup-label').show();
  344. this.$selectionUl.find('.ms-elem-selection').removeClass('ms-selected').hide();
  345. this.$selectableUl.focusin();
  346. this.$selectionUl.focusout();
  347. ms.trigger('change');
  348. if (typeof this.options.afterDeselect === 'function') {
  349. this.options.afterDeselect.call(this, values);
  350. }
  351. },
  352. isDomNode: function (attr){
  353. return (
  354. attr &&
  355. typeof attr === "object" &&
  356. typeof attr.nodeType === "number" &&
  357. typeof attr.nodeName === "string"
  358. );
  359. },
  360. sanitize: function(value, reg){
  361. return(value.replace(reg, '_'));
  362. }
  363. };
  364. /* MULTISELECT PLUGIN DEFINITION
  365. * ======================= */
  366. $.fn.multiSelect = function () {
  367. var option = arguments[0],
  368. args = arguments;
  369. return this.each(function () {
  370. var $this = $(this),
  371. data = $this.data('multiselect'),
  372. options = $.extend({}, $.fn.multiSelect.defaults, $this.data(), typeof option === 'object' && option);
  373. if (!data){ $this.data('multiselect', (data = new MultiSelect(this, options))); }
  374. if (typeof option === 'string'){
  375. data[option](args[1]);
  376. } else {
  377. data.init();
  378. }
  379. });
  380. };
  381. $.fn.multiSelect.defaults = {
  382. selectableOptgroup: false,
  383. disabledClass : 'disabled',
  384. dblClick : false
  385. };
  386. $.fn.multiSelect.Constructor = MultiSelect;
  387. }(window.jQuery);