├── .gitignore
├── Gruntfile.js
├── LICENSE.txt
├── README.markdown
├── bower.json
├── css
├── multi-select.css
├── multi-select.dev.css
├── multi-select.dev.css.map
└── multi-select.dist.css
├── img
└── switch.png
├── js
└── jquery.multi-select.js
├── multi-select.jquery.json
├── package.json
├── samples
├── Pre-selected-options.html
├── callbacks.html
└── index.html
├── scss
└── multi-select.scss
└── test
├── SpecRunner.html
├── lib
├── jasmine-1.2.0
│ ├── MIT.LICENSE
│ ├── jasmine-html.js
│ ├── jasmine.css
│ └── jasmine.js
└── jasmine-jquery.js
├── spec
├── SpecHelper.js
└── multiSelectSpec.js
└── src
└── jquery.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | .sass-cache/
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 | var config = {};
3 |
4 |
5 | //src ===============================
6 | var src;
7 | config.src = src = {
8 | sassMain: 'scss/multi-select.scss',
9 | distFolder: 'css/multi-select.dist.css',
10 | devFolder: 'css/multi-select.dev.css',
11 | sassFolder: 'scss/**/*.scss',
12 | serverPort: 8000
13 |
14 | };
15 |
16 |
17 | //Watch ===============================
18 | config.watch = {
19 | scripts: {
20 | files: ["<%= src.sassFolder %>"],
21 | tasks: ["dev", "sass:dist"]
22 | //,tasks: ["dev",'sass:dist']
23 | }
24 | }
25 |
26 |
27 | //Sass ===============================
28 | var sass;
29 | config.sass = sass = {};
30 |
31 | //distribution
32 | sass.dist = {
33 | options: {
34 | style: "compressed",
35 | noCache: true,
36 | sourcemap: 'none',
37 | update: true
38 | },
39 | files: {
40 | "<%= src.distFolder %>": "<%= src.sassMain %>"
41 | }
42 | };
43 |
44 | //development env.
45 | sass.dev = {
46 | options: {
47 | style: "expanded",
48 | lineNumber: true,
49 | },
50 | files: {
51 | "<%= src.devFolder %>": "<%= src.sassMain %>"
52 | }
53 | };
54 |
55 | //grunt serve ===============================
56 | config.connect = {
57 | server: {
58 | options: {
59 | livereload: true,
60 | port: "<%= src.serverPort %>"
61 | }
62 | }
63 | };
64 |
65 | //Register custom tasks ===============================
66 | grunt.registerTask('default', ['dev']);
67 | grunt.registerTask('dev', ['sass:dev']);
68 | grunt.registerTask('dist', ['sass:dist']);
69 | grunt.registerTask('serve', ['connect:server', 'watch']);
70 | require('time-grunt')(grunt);
71 | require('load-grunt-tasks')(grunt, {
72 | scope: 'devDependencies'
73 | });
74 |
75 |
76 | //General setup ===============================
77 | grunt.initConfig(config);
78 |
79 | };
80 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Louis CUNY
2 |
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | # [jquery multi-select.js](http://loudev.com/)
2 |
3 | ## Summary
4 |
5 | - [About](#about)
6 | - [Creator](#creator)
7 | - [Usage and demos](#usage)
8 | - [License](#license)
9 |
10 | ## About
11 | I'm a user-friendlier drop-in replacement for the standard select with multiple attribute activated.
12 |
13 | ## Creator
14 | @lou
15 |
16 | ### Usage and demos
17 | [http://loudev.com](http://loudev.com "jquery.multi-select.js")
18 |
19 |
20 | ### License
21 | Multi-select is released under the [MIT License](http://opensource.org/licenses/MIT "MIT License").
22 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "multiselect",
3 | "version": "0.9.12",
4 | "description" : "A user-friendlier drop-in replacement for the standard select with multiple attribute activated.",
5 | "license" : "WTFPL",
6 | "main": [
7 | "./css/multi-select.css",
8 | "./img/switch.png",
9 | "./js/jquery.multi-select.js"
10 | ],
11 | "dependencies" : {
12 | "jquery" : ">= 1.7.1"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/css/multi-select.css:
--------------------------------------------------------------------------------
1 | .ms-container{
2 | background: transparent url('../img/switch.png') no-repeat 50% 50%;
3 | width: 370px;
4 | }
5 |
6 | .ms-container:after{
7 | content: ".";
8 | display: block;
9 | height: 0;
10 | line-height: 0;
11 | font-size: 0;
12 | clear: both;
13 | min-height: 0;
14 | visibility: hidden;
15 | }
16 |
17 | .ms-container .ms-selectable, .ms-container .ms-selection{
18 | background: #fff;
19 | color: #555555;
20 | float: left;
21 | width: 45%;
22 | }
23 | .ms-container .ms-selection{
24 | float: right;
25 | }
26 |
27 | .ms-container .ms-list{
28 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
29 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
30 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
31 | -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
32 | -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
33 | -ms-transition: border linear 0.2s, box-shadow linear 0.2s;
34 | -o-transition: border linear 0.2s, box-shadow linear 0.2s;
35 | transition: border linear 0.2s, box-shadow linear 0.2s;
36 | border: 1px solid #ccc;
37 | -webkit-border-radius: 3px;
38 | -moz-border-radius: 3px;
39 | border-radius: 3px;
40 | position: relative;
41 | height: 200px;
42 | padding: 0;
43 | overflow-y: auto;
44 | }
45 |
46 | .ms-container .ms-list.ms-focus{
47 | border-color: rgba(82, 168, 236, 0.8);
48 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
49 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
50 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
51 | outline: 0;
52 | outline: thin dotted \9;
53 | }
54 |
55 | .ms-container ul{
56 | margin: 0;
57 | list-style-type: none;
58 | padding: 0;
59 | }
60 |
61 | .ms-container .ms-optgroup-container{
62 | width: 100%;
63 | }
64 |
65 | .ms-container .ms-optgroup-label{
66 | margin: 0;
67 | padding: 5px 0px 0px 5px;
68 | cursor: pointer;
69 | color: #999;
70 | }
71 |
72 | .ms-container .ms-selectable li.ms-elem-selectable,
73 | .ms-container .ms-selection li.ms-elem-selection{
74 | border-bottom: 1px #eee solid;
75 | padding: 2px 10px;
76 | color: #555;
77 | font-size: 14px;
78 | }
79 |
80 | .ms-container .ms-selectable li.ms-hover,
81 | .ms-container .ms-selection li.ms-hover{
82 | cursor: pointer;
83 | color: #fff;
84 | text-decoration: none;
85 | background-color: #08c;
86 | }
87 |
88 | .ms-container .ms-selectable li.disabled,
89 | .ms-container .ms-selection li.disabled{
90 | background-color: #eee;
91 | color: #aaa;
92 | cursor: text;
93 | }
--------------------------------------------------------------------------------
/css/multi-select.dev.css:
--------------------------------------------------------------------------------
1 | /* line 1, ../scss/multi-select.scss */
2 | .ms-container {
3 | background: transparent url("../img/switch.png") no-repeat 50% 50%;
4 | width: 370px;
5 | }
6 |
7 | /* line 6, ../scss/multi-select.scss */
8 | .ms-container:after {
9 | content: ".";
10 | display: block;
11 | height: 0;
12 | line-height: 0;
13 | font-size: 0;
14 | clear: both;
15 | min-height: 0;
16 | visibility: hidden;
17 | }
18 |
19 | /* line 17, ../scss/multi-select.scss */
20 | .ms-container .ms-selectable, .ms-container .ms-selection {
21 | background: #fff;
22 | color: #555555;
23 | float: left;
24 | width: 45%;
25 | }
26 |
27 | /* line 23, ../scss/multi-select.scss */
28 | .ms-container .ms-selection {
29 | float: right;
30 | }
31 |
32 | /* line 27, ../scss/multi-select.scss */
33 | .ms-container .ms-list {
34 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
35 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
36 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
37 | -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
38 | -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
39 | -ms-transition: border linear 0.2s, box-shadow linear 0.2s;
40 | -o-transition: border linear 0.2s, box-shadow linear 0.2s;
41 | transition: border linear 0.2s, box-shadow linear 0.2s;
42 | border: 1px solid #ccc;
43 | -webkit-border-radius: 3px;
44 | -moz-border-radius: 3px;
45 | border-radius: 3px;
46 | position: relative;
47 | height: 200px;
48 | padding: 0;
49 | overflow-y: auto;
50 | }
51 |
52 | /* line 46, ../scss/multi-select.scss */
53 | .ms-container .ms-list.ms-focus {
54 | border-color: rgba(82, 168, 236, 0.8);
55 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
56 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
57 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
58 | outline: 0;
59 | outline: thin dotted \9;
60 | }
61 |
62 | /* line 55, ../scss/multi-select.scss */
63 | .ms-container ul {
64 | margin: 0;
65 | list-style-type: none;
66 | padding: 0;
67 | }
68 |
69 | /* line 61, ../scss/multi-select.scss */
70 | .ms-container .ms-optgroup-container {
71 | width: 100%;
72 | }
73 |
74 | /* line 65, ../scss/multi-select.scss */
75 | .ms-container .ms-optgroup-label {
76 | margin: 0;
77 | padding: 5px 0px 0px 5px;
78 | cursor: pointer;
79 | color: #999;
80 | }
81 |
82 | /* line 72, ../scss/multi-select.scss */
83 | .ms-container .ms-selectable li.ms-elem-selectable,
84 | .ms-container .ms-selection li.ms-elem-selection {
85 | border-bottom: 1px #eee solid;
86 | padding: 2px 10px;
87 | color: #555;
88 | font-size: 14px;
89 | }
90 |
91 | /* line 80, ../scss/multi-select.scss */
92 | .ms-container .ms-selectable li.ms-hover,
93 | .ms-container .ms-selection li.ms-hover {
94 | cursor: pointer;
95 | color: #fff;
96 | text-decoration: none;
97 | background-color: #08c;
98 | }
99 |
100 | /* line 88, ../scss/multi-select.scss */
101 | .ms-container .ms-selectable li.disabled,
102 | .ms-container .ms-selection li.disabled {
103 | background-color: #eee;
104 | color: #aaa;
105 | cursor: text;
106 | }
107 |
108 | /*# sourceMappingURL=multi-select.dev.css.map */
109 |
--------------------------------------------------------------------------------
/css/multi-select.dev.css.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "mappings": ";AAAA,aAAa;EACX,UAAU,EAAE,sDAAsD;EAClE,KAAK,EAAE,KAAK;;;;AAGd,mBAAmB;EACjB,OAAO,EAAE,GAAG;EACZ,OAAO,EAAE,KAAK;EACd,MAAM,EAAE,CAAC;EACT,WAAW,EAAE,CAAC;EACd,SAAS,EAAE,CAAC;EACZ,KAAK,EAAE,IAAI;EACX,UAAU,EAAE,CAAC;EACb,UAAU,EAAE,MAAM;;;;AAGpB,yDAAyD;EACvD,UAAU,EAAE,IAAI;EAChB,KAAK,EAAE,OAAO;EACd,KAAK,EAAE,IAAI;EACX,KAAK,EAAE,GAAG;;;;AAEZ,2BAA2B;EACzB,KAAK,EAAE,KAAK;;;;AAGd,sBAAsB;EACpB,kBAAkB,EAAE,oCAAoC;EACxD,eAAe,EAAE,oCAAoC;EACrD,UAAU,EAAE,oCAAoC;EAChD,kBAAkB,EAAE,0CAA0C;EAC9D,eAAe,EAAE,0CAA0C;EAC3D,cAAc,EAAE,0CAA0C;EAC1D,aAAa,EAAE,0CAA0C;EACzD,UAAU,EAAE,0CAA0C;EACtD,MAAM,EAAE,cAAc;EACtB,qBAAqB,EAAE,GAAG;EAC1B,kBAAkB,EAAE,GAAG;EACvB,aAAa,EAAE,GAAG;EAClB,QAAQ,EAAE,QAAQ;EAClB,MAAM,EAAE,KAAK;EACb,OAAO,EAAE,CAAC;EACV,UAAU,EAAE,IAAI;;;;AAGlB,+BAA+B;EAC7B,YAAY,EAAE,uBAAuB;EACrC,kBAAkB,EAAE,qEAAqE;EACzF,eAAe,EAAE,qEAAqE;EACtF,UAAU,EAAE,qEAAqE;EACjF,OAAO,EAAE,CAAC;EACV,OAAO,EAAE,cAAc;;;;AAGzB,gBAAgB;EACd,MAAM,EAAE,CAAC;EACT,eAAe,EAAE,IAAI;EACrB,OAAO,EAAE,CAAC;;;;AAGZ,oCAAoC;EAClC,KAAK,EAAE,IAAI;;;;AAGb,gCAAgC;EAC9B,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,eAAe;EACxB,MAAM,EAAE,OAAO;EACf,KAAK,EAAE,IAAI;;;;AAGb;gDACgD;EAC9C,aAAa,EAAE,cAAc;EAC7B,OAAO,EAAE,QAAQ;EACjB,KAAK,EAAE,IAAI;EACX,SAAS,EAAE,IAAI;;;;AAGjB;uCACuC;EACrC,MAAM,EAAE,OAAO;EACf,KAAK,EAAE,IAAI;EACX,eAAe,EAAE,IAAI;EACrB,gBAAgB,EAAE,IAAI;;;;AAGxB;uCACuC;EACrC,gBAAgB,EAAE,IAAI;EACtB,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI",
4 | "sources": ["../scss/multi-select.scss"],
5 | "names": [],
6 | "file": "multi-select.dev.css"
7 | }
8 |
--------------------------------------------------------------------------------
/css/multi-select.dist.css:
--------------------------------------------------------------------------------
1 | .ms-container{background:transparent url("../img/switch.png") no-repeat 50% 50%;width:370px}.ms-container:after{content:".";display:block;height:0;line-height:0;font-size:0;clear:both;min-height:0;visibility:hidden}.ms-container .ms-selectable,.ms-container .ms-selection{background:#fff;color:#555555;float:left;width:45%}.ms-container .ms-selection{float:right}.ms-container .ms-list{-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear 0.2s, box-shadow linear 0.2s;-moz-transition:border linear 0.2s, box-shadow linear 0.2s;-ms-transition:border linear 0.2s, box-shadow linear 0.2s;-o-transition:border linear 0.2s, box-shadow linear 0.2s;transition:border linear 0.2s, box-shadow linear 0.2s;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;position:relative;height:200px;padding:0;overflow-y:auto}.ms-container .ms-list.ms-focus{border-color:rgba(82,168,236,0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);outline:0;outline:thin dotted \9}.ms-container ul{margin:0;list-style-type:none;padding:0}.ms-container .ms-optgroup-container{width:100%}.ms-container .ms-optgroup-label{margin:0;padding:5px 0px 0px 5px;cursor:pointer;color:#999}.ms-container .ms-selectable li.ms-elem-selectable,.ms-container .ms-selection li.ms-elem-selection{border-bottom:1px #eee solid;padding:2px 10px;color:#555;font-size:14px}.ms-container .ms-selectable li.ms-hover,.ms-container .ms-selection li.ms-hover{cursor:pointer;color:#fff;text-decoration:none;background-color:#08c}.ms-container .ms-selectable li.disabled,.ms-container .ms-selection li.disabled{background-color:#eee;color:#aaa;cursor:text}
2 |
--------------------------------------------------------------------------------
/img/switch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lou/multi-select/57fb8d3f5d27a0e1058f63921cdb26b4d30da361/img/switch.png
--------------------------------------------------------------------------------
/js/jquery.multi-select.js:
--------------------------------------------------------------------------------
1 | /*
2 | * MultiSelect v0.9.12
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 |
12 | !function ($) {
13 |
14 | "use strict";
15 |
16 |
17 | /* MULTISELECT CLASS DEFINITION
18 | * ====================== */
19 |
20 | var MultiSelect = function (element, options) {
21 | this.options = options;
22 | this.$element = $(element);
23 | this.$container = $('
', { 'class': "ms-container" });
24 | this.$selectableContainer = $('', { 'class': 'ms-selectable' });
25 | this.$selectionContainer = $('', { 'class': 'ms-selection' });
26 | this.$selectableUl = $('', { 'class': "ms-list", 'tabindex' : '-1' });
27 | this.$selectionUl = $('', { 'class': "ms-list", 'tabindex' : '-1' });
28 | this.scrollTo = 0;
29 | this.elemsSelector = 'li:visible:not(.ms-optgroup-label,.ms-optgroup-container,.'+options.disabledClass+')';
30 | };
31 |
32 | MultiSelect.prototype = {
33 | constructor: MultiSelect,
34 |
35 | init: function(){
36 | var that = this,
37 | ms = this.$element;
38 |
39 | if (ms.next('.ms-container').length === 0){
40 | ms.css({ position: 'absolute', left: '-9999px' });
41 | ms.attr('id', ms.attr('id') ? ms.attr('id') : Math.ceil(Math.random()*1000)+'multiselect');
42 | this.$container.attr('id', 'ms-'+ms.attr('id'));
43 | this.$container.addClass(that.options.cssClass);
44 | ms.find('option').each(function(){
45 | that.generateLisFromOption(this);
46 | });
47 |
48 | this.$selectionUl.find('.ms-optgroup-label').hide();
49 |
50 | if (that.options.selectableHeader){
51 | that.$selectableContainer.append(that.options.selectableHeader);
52 | }
53 | that.$selectableContainer.append(that.$selectableUl);
54 | if (that.options.selectableFooter){
55 | that.$selectableContainer.append(that.options.selectableFooter);
56 | }
57 |
58 | if (that.options.selectionHeader){
59 | that.$selectionContainer.append(that.options.selectionHeader);
60 | }
61 | that.$selectionContainer.append(that.$selectionUl);
62 | if (that.options.selectionFooter){
63 | that.$selectionContainer.append(that.options.selectionFooter);
64 | }
65 |
66 | that.$container.append(that.$selectableContainer);
67 | that.$container.append(that.$selectionContainer);
68 | ms.after(that.$container);
69 |
70 | that.activeMouse(that.$selectableUl);
71 | that.activeKeyboard(that.$selectableUl);
72 |
73 | var action = that.options.dblClick ? 'dblclick' : 'click';
74 |
75 | that.$selectableUl.on(action, '.ms-elem-selectable', function(){
76 | that.select($(this).data('ms-value'));
77 | });
78 | that.$selectionUl.on(action, '.ms-elem-selection', function(){
79 | that.deselect($(this).data('ms-value'));
80 | });
81 |
82 | that.activeMouse(that.$selectionUl);
83 | that.activeKeyboard(that.$selectionUl);
84 |
85 | ms.on('focus', function(){
86 | that.$selectableUl.focus();
87 | });
88 | }
89 |
90 | var selectedValues = ms.find('option:selected').map(function(){ return $(this).val(); }).get();
91 | that.select(selectedValues, 'init');
92 |
93 | if (typeof that.options.afterInit === 'function') {
94 | that.options.afterInit.call(this, this.$container);
95 | }
96 | },
97 |
98 | 'generateLisFromOption' : function(option, index, $container){
99 | var that = this,
100 | ms = that.$element,
101 | attributes = "",
102 | $option = $(option);
103 |
104 | for (var cpt = 0; cpt < option.attributes.length; cpt++){
105 | var attr = option.attributes[cpt];
106 |
107 | if(attr.name !== 'value' && attr.name !== 'disabled'){
108 | attributes += attr.name+'="'+attr.value+'" ';
109 | }
110 | }
111 | var selectableLi = $(''+that.escapeHTML($option.text())+''),
112 | selectedLi = selectableLi.clone(),
113 | value = $option.val(),
114 | elementId = that.sanitize(value);
115 |
116 | selectableLi
117 | .data('ms-value', value)
118 | .addClass('ms-elem-selectable')
119 | .attr('id', elementId+'-selectable');
120 |
121 | selectedLi
122 | .data('ms-value', value)
123 | .addClass('ms-elem-selection')
124 | .attr('id', elementId+'-selection')
125 | .hide();
126 |
127 | if ($option.attr('disabled') || ms.attr('disabled')){
128 | selectedLi.addClass(that.options.disabledClass);
129 | selectableLi.addClass(that.options.disabledClass);
130 | }
131 |
132 | var $optgroup = $option.parent('optgroup');
133 |
134 | if ($optgroup.length > 0){
135 | var optgroupLabel = $optgroup.attr('label'),
136 | optgroupId = that.sanitize(optgroupLabel),
137 | $selectableOptgroup = that.$selectableUl.find('#optgroup-selectable-'+optgroupId),
138 | $selectionOptgroup = that.$selectionUl.find('#optgroup-selection-'+optgroupId);
139 |
140 | if ($selectableOptgroup.length === 0){
141 | var optgroupContainerTpl = '',
142 | optgroupTpl = '';
143 |
144 | $selectableOptgroup = $(optgroupContainerTpl);
145 | $selectionOptgroup = $(optgroupContainerTpl);
146 | $selectableOptgroup.attr('id', 'optgroup-selectable-'+optgroupId);
147 | $selectionOptgroup.attr('id', 'optgroup-selection-'+optgroupId);
148 | $selectableOptgroup.append($(optgroupTpl));
149 | $selectionOptgroup.append($(optgroupTpl));
150 | if (that.options.selectableOptgroup){
151 | $selectableOptgroup.find('.ms-optgroup-label').on('click', function(){
152 | var values = $optgroup.children(':not(:selected, :disabled)').map(function(){ return $(this).val();}).get();
153 | that.select(values);
154 | });
155 | $selectionOptgroup.find('.ms-optgroup-label').on('click', function(){
156 | var values = $optgroup.children(':selected:not(:disabled)').map(function(){ return $(this).val();}).get();
157 | that.deselect(values);
158 | });
159 | }
160 | that.$selectableUl.append($selectableOptgroup);
161 | that.$selectionUl.append($selectionOptgroup);
162 | }
163 | index = index === undefined ? $selectableOptgroup.find('ul').children().length : index + 1;
164 | selectableLi.insertAt(index, $selectableOptgroup.children());
165 | selectedLi.insertAt(index, $selectionOptgroup.children());
166 | } else {
167 | index = index === undefined ? that.$selectableUl.children().length : index;
168 |
169 | selectableLi.insertAt(index, that.$selectableUl);
170 | selectedLi.insertAt(index, that.$selectionUl);
171 | }
172 | },
173 |
174 | 'addOption' : function(options){
175 | var that = this;
176 |
177 | if (options.value !== undefined && options.value !== null){
178 | options = [options];
179 | }
180 | $.each(options, function(index, option){
181 | if (option.value !== undefined && option.value !== null &&
182 | that.$element.find("option[value='"+option.value+"']").length === 0){
183 | var $option = $(''),
184 | $container = option.nested === undefined ? that.$element : $("optgroup[label='"+option.nested+"']"),
185 | index = parseInt((typeof option.index === 'undefined' ? $container.children().length : option.index));
186 |
187 | if (option.optionClass) {
188 | $option.addClass(option.optionClass);
189 | }
190 |
191 | if (option.disabled) {
192 | $option.prop('disabled', true);
193 | }
194 |
195 | $option.insertAt(index, $container);
196 | that.generateLisFromOption($option.get(0), index, option.nested);
197 | }
198 | });
199 | },
200 |
201 | 'escapeHTML' : function(text){
202 | return $("").text(text).html();
203 | },
204 |
205 | 'activeKeyboard' : function($list){
206 | var that = this;
207 |
208 | $list.on('focus', function(){
209 | $(this).addClass('ms-focus');
210 | })
211 | .on('blur', function(){
212 | $(this).removeClass('ms-focus');
213 | })
214 | .on('keydown', function(e){
215 | switch (e.which) {
216 | case 40:
217 | case 38:
218 | e.preventDefault();
219 | e.stopPropagation();
220 | that.moveHighlight($(this), (e.which === 38) ? -1 : 1);
221 | return;
222 | case 37:
223 | case 39:
224 | e.preventDefault();
225 | e.stopPropagation();
226 | that.switchList($list);
227 | return;
228 | case 9:
229 | if(that.$element.is('[tabindex]')){
230 | e.preventDefault();
231 | var tabindex = parseInt(that.$element.attr('tabindex'), 10);
232 | tabindex = (e.shiftKey) ? tabindex-1 : tabindex+1;
233 | $('[tabindex="'+(tabindex)+'"]').focus();
234 | return;
235 | }else{
236 | if(e.shiftKey){
237 | that.$element.trigger('focus');
238 | }
239 | }
240 | }
241 | if($.inArray(e.which, that.options.keySelect) > -1){
242 | e.preventDefault();
243 | e.stopPropagation();
244 | that.selectHighlighted($list);
245 | return;
246 | }
247 | });
248 | },
249 |
250 | 'moveHighlight': function($list, direction){
251 | var $elems = $list.find(this.elemsSelector),
252 | $currElem = $elems.filter('.ms-hover'),
253 | $nextElem = null,
254 | elemHeight = $elems.first().outerHeight(),
255 | containerHeight = $list.height(),
256 | containerSelector = '#'+this.$container.prop('id');
257 |
258 | $elems.removeClass('ms-hover');
259 | if (direction === 1){ // DOWN
260 |
261 | $nextElem = $currElem.nextAll(this.elemsSelector).first();
262 | if ($nextElem.length === 0){
263 | var $optgroupUl = $currElem.parent();
264 |
265 | if ($optgroupUl.hasClass('ms-optgroup')){
266 | var $optgroupLi = $optgroupUl.parent(),
267 | $nextOptgroupLi = $optgroupLi.next(':visible');
268 |
269 | if ($nextOptgroupLi.length > 0){
270 | $nextElem = $nextOptgroupLi.find(this.elemsSelector).first();
271 | } else {
272 | $nextElem = $elems.first();
273 | }
274 | } else {
275 | $nextElem = $elems.first();
276 | }
277 | }
278 | } else if (direction === -1){ // UP
279 |
280 | $nextElem = $currElem.prevAll(this.elemsSelector).first();
281 | if ($nextElem.length === 0){
282 | var $optgroupUl = $currElem.parent();
283 |
284 | if ($optgroupUl.hasClass('ms-optgroup')){
285 | var $optgroupLi = $optgroupUl.parent(),
286 | $prevOptgroupLi = $optgroupLi.prev(':visible');
287 |
288 | if ($prevOptgroupLi.length > 0){
289 | $nextElem = $prevOptgroupLi.find(this.elemsSelector).last();
290 | } else {
291 | $nextElem = $elems.last();
292 | }
293 | } else {
294 | $nextElem = $elems.last();
295 | }
296 | }
297 | }
298 | if ($nextElem.length > 0){
299 | $nextElem.addClass('ms-hover');
300 | var scrollTo = $list.scrollTop() + $nextElem.position().top -
301 | containerHeight / 2 + elemHeight / 2;
302 |
303 | $list.scrollTop(scrollTo);
304 | }
305 | },
306 |
307 | 'selectHighlighted' : function($list){
308 | var $elems = $list.find(this.elemsSelector),
309 | $highlightedElem = $elems.filter('.ms-hover').first();
310 |
311 | if ($highlightedElem.length > 0){
312 | if ($list.parent().hasClass('ms-selectable')){
313 | this.select($highlightedElem.data('ms-value'));
314 | } else {
315 | this.deselect($highlightedElem.data('ms-value'));
316 | }
317 | $elems.removeClass('ms-hover');
318 | }
319 | },
320 |
321 | 'switchList' : function($list){
322 | $list.blur();
323 | this.$container.find(this.elemsSelector).removeClass('ms-hover');
324 | if ($list.parent().hasClass('ms-selectable')){
325 | this.$selectionUl.focus();
326 | } else {
327 | this.$selectableUl.focus();
328 | }
329 | },
330 |
331 | 'activeMouse' : function($list){
332 | var that = this;
333 |
334 | this.$container.on('mouseenter', that.elemsSelector, function(){
335 | $(this).parents('.ms-container').find(that.elemsSelector).removeClass('ms-hover');
336 | $(this).addClass('ms-hover');
337 | });
338 |
339 | this.$container.on('mouseleave', that.elemsSelector, function () {
340 | $(this).parents('.ms-container').find(that.elemsSelector).removeClass('ms-hover');
341 | });
342 | },
343 |
344 | 'refresh' : function() {
345 | this.destroy();
346 | this.$element.multiSelect(this.options);
347 | },
348 |
349 | 'destroy' : function(){
350 | $("#ms-"+this.$element.attr("id")).remove();
351 | this.$element.off('focus');
352 | this.$element.css('position', '').css('left', '');
353 | this.$element.removeData('multiselect');
354 | },
355 |
356 | 'select' : function(value, method){
357 | if (typeof value === 'string'){ value = [value]; }
358 |
359 | var that = this,
360 | ms = this.$element,
361 | msIds = $.map(value, function(val){ return(that.sanitize(val)); }),
362 | selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable').filter(':not(.'+that.options.disabledClass+')'),
363 | selections = this.$selectionUl.find('#' + msIds.join('-selection, #') + '-selection').filter(':not(.'+that.options.disabledClass+')'),
364 | options = ms.find('option:not(:disabled)').filter(function(){ return($.inArray(this.value, value) > -1); });
365 |
366 | if (method === 'init'){
367 | selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable'),
368 | selections = this.$selectionUl.find('#' + msIds.join('-selection, #') + '-selection');
369 | }
370 |
371 | if (selectables.length > 0){
372 | selectables.addClass('ms-selected').hide();
373 | selections.addClass('ms-selected').show();
374 |
375 | options.attr('selected', 'selected');
376 |
377 | that.$container.find(that.elemsSelector).removeClass('ms-hover');
378 |
379 | var selectableOptgroups = that.$selectableUl.children('.ms-optgroup-container');
380 | if (selectableOptgroups.length > 0){
381 | selectableOptgroups.each(function(){
382 | var selectablesLi = $(this).find('.ms-elem-selectable');
383 | if (selectablesLi.length === selectablesLi.filter('.ms-selected').length){
384 | $(this).find('.ms-optgroup-label').hide();
385 | }
386 | });
387 |
388 | var selectionOptgroups = that.$selectionUl.children('.ms-optgroup-container');
389 | selectionOptgroups.each(function(){
390 | var selectionsLi = $(this).find('.ms-elem-selection');
391 | if (selectionsLi.filter('.ms-selected').length > 0){
392 | $(this).find('.ms-optgroup-label').show();
393 | }
394 | });
395 | } else {
396 | if (that.options.keepOrder && method !== 'init'){
397 | var selectionLiLast = that.$selectionUl.find('.ms-selected');
398 | if((selectionLiLast.length > 1) && (selectionLiLast.last().get(0) != selections.get(0))) {
399 | selections.insertAfter(selectionLiLast.last());
400 | }
401 | }
402 | }
403 | if (method !== 'init'){
404 | ms.trigger('change');
405 | if (typeof that.options.afterSelect === 'function') {
406 | that.options.afterSelect.call(this, value);
407 | }
408 | }
409 | }
410 | },
411 |
412 | 'deselect' : function(value){
413 | if (typeof value === 'string'){ value = [value]; }
414 |
415 | var that = this,
416 | ms = this.$element,
417 | msIds = $.map(value, function(val){ return(that.sanitize(val)); }),
418 | selectables = this.$selectableUl.find('#' + msIds.join('-selectable, #')+'-selectable'),
419 | selections = this.$selectionUl.find('#' + msIds.join('-selection, #')+'-selection').filter('.ms-selected').filter(':not(.'+that.options.disabledClass+')'),
420 | options = ms.find('option').filter(function(){ return($.inArray(this.value, value) > -1); });
421 |
422 | if (selections.length > 0){
423 | selectables.removeClass('ms-selected').show();
424 | selections.removeClass('ms-selected').hide();
425 | options.removeAttr('selected');
426 |
427 | that.$container.find(that.elemsSelector).removeClass('ms-hover');
428 |
429 | var selectableOptgroups = that.$selectableUl.children('.ms-optgroup-container');
430 | if (selectableOptgroups.length > 0){
431 | selectableOptgroups.each(function(){
432 | var selectablesLi = $(this).find('.ms-elem-selectable');
433 | if (selectablesLi.filter(':not(.ms-selected)').length > 0){
434 | $(this).find('.ms-optgroup-label').show();
435 | }
436 | });
437 |
438 | var selectionOptgroups = that.$selectionUl.children('.ms-optgroup-container');
439 | selectionOptgroups.each(function(){
440 | var selectionsLi = $(this).find('.ms-elem-selection');
441 | if (selectionsLi.filter('.ms-selected').length === 0){
442 | $(this).find('.ms-optgroup-label').hide();
443 | }
444 | });
445 | }
446 | ms.trigger('change');
447 | if (typeof that.options.afterDeselect === 'function') {
448 | that.options.afterDeselect.call(this, value);
449 | }
450 | }
451 | },
452 |
453 | 'select_all' : function(){
454 | var ms = this.$element,
455 | values = ms.val();
456 |
457 | ms.find('option:not(":disabled")').attr('selected', 'selected');
458 | this.$selectableUl.find('.ms-elem-selectable').filter(':not(.'+this.options.disabledClass+')').addClass('ms-selected').hide();
459 | this.$selectionUl.find('.ms-optgroup-label').show();
460 | this.$selectableUl.find('.ms-optgroup-label').hide();
461 | this.$selectionUl.find('.ms-elem-selection').filter(':not(.'+this.options.disabledClass+')').addClass('ms-selected').show();
462 | this.$selectionUl.focus();
463 | ms.trigger('change');
464 | if (typeof this.options.afterSelect === 'function') {
465 | var selectedValues = $.grep(ms.val(), function(item){
466 | return $.inArray(item, values) < 0;
467 | });
468 | this.options.afterSelect.call(this, selectedValues);
469 | }
470 | },
471 |
472 | 'deselect_all' : function(){
473 | var ms = this.$element,
474 | values = ms.val();
475 |
476 | ms.find('option').removeAttr('selected');
477 | this.$selectableUl.find('.ms-elem-selectable').removeClass('ms-selected').show();
478 | this.$selectionUl.find('.ms-optgroup-label').hide();
479 | this.$selectableUl.find('.ms-optgroup-label').show();
480 | this.$selectionUl.find('.ms-elem-selection').removeClass('ms-selected').hide();
481 | this.$selectableUl.focus();
482 | ms.trigger('change');
483 | if (typeof this.options.afterDeselect === 'function') {
484 | this.options.afterDeselect.call(this, values);
485 | }
486 | },
487 |
488 | sanitize: function(value){
489 | var hash = 0, i, character;
490 | if (value.length == 0) return hash;
491 | var ls = 0;
492 | for (i = 0, ls = value.length; i < ls; i++) {
493 | character = value.charCodeAt(i);
494 | hash = ((hash<<5)-hash)+character;
495 | hash |= 0; // Convert to 32bit integer
496 | }
497 | return hash;
498 | }
499 | };
500 |
501 | /* MULTISELECT PLUGIN DEFINITION
502 | * ======================= */
503 |
504 | $.fn.multiSelect = function () {
505 | var option = arguments[0],
506 | args = arguments;
507 |
508 | return this.each(function () {
509 | var $this = $(this),
510 | data = $this.data('multiselect'),
511 | options = $.extend({}, $.fn.multiSelect.defaults, $this.data(), typeof option === 'object' && option);
512 |
513 | if (!data){ $this.data('multiselect', (data = new MultiSelect(this, options))); }
514 |
515 | if (typeof option === 'string'){
516 | data[option](args[1]);
517 | } else {
518 | data.init();
519 | }
520 | });
521 | };
522 |
523 | $.fn.multiSelect.defaults = {
524 | keySelect: [32],
525 | selectableOptgroup: false,
526 | disabledClass : 'disabled',
527 | dblClick : false,
528 | keepOrder: false,
529 | cssClass: ''
530 | };
531 |
532 | $.fn.multiSelect.Constructor = MultiSelect;
533 |
534 | $.fn.insertAt = function(index, $parent) {
535 | return this.each(function() {
536 | if (index === 0) {
537 | $parent.prepend(this);
538 | } else {
539 | $parent.children().eq(index - 1).after(this);
540 | }
541 | });
542 | };
543 |
544 | }(window.jQuery);
545 |
--------------------------------------------------------------------------------
/multi-select.jquery.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "multi-select",
3 | "title": "multiselect",
4 | "description": "This is a user-friendlier drop-in replacement for the standard