├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── dist ├── multipicker.min.css └── multipicker.min.js ├── gulpfile.js ├── index.html ├── package.json ├── roadmap.md ├── src ├── multipicker.css └── multipicker.js └── tests ├── test.html └── tests.js /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '6' 5 | before_script: 6 | - npm install -g gulp 7 | - npm link gulp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Stepan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Multipicker 2 | 3 | Plugin's [`official website`](http://styopdev.github.io/multiPicker/) with demos, also [`available in Russian`](http://styopdev.github.io/multiPicker/ru.html). 4 | 5 | Multipicker is jQuery plugin for selecting days, numbers or other elements, it supports multi selecting (like checkboxes) or single element selection (like radio buttons). 6 | 7 | #### How to use 8 | 9 | There are several ways for multipicker plugin installation: 10 | 11 | + using npm: `npm install multipicker` 12 | 13 | + using bower: `bower install multipicker` 14 | 15 | + download and unpack zip file from [github repository](https://github.com/styopdev/multiPicker). 16 | 17 | Load the latest version of jQuery library and plugin's files from `dist` folder in the html document. 18 | 19 | ``` 20 | 21 | 22 | 23 | ``` 24 | ##### Multipicker usage basic example. 25 | 26 | ```html 27 | 36 | ``` 37 | 38 | ```javascript 39 | $("#days").multiPicker({ selector : "li" }); 40 | ``` 41 | ![Multipicker usage basic example](https://cloud.githubusercontent.com/assets/6073745/15856615/b172e880-2cc7-11e6-8402-1b739f005c08.gif) 42 | 43 | #### Repository files' structure 44 | 45 | ![Repository files' structure](https://user-images.githubusercontent.com/6073745/60943444-1f769e00-a2f7-11e9-861c-259c0a6a068f.jpeg) 46 | 47 | ### Options 48 | * `selector` (required) - element type used inside of picker (html tag like `li` / `span` / `i`, `checkbox` / `radio` - for input type `checkbox` / `radio`) 49 | * `inputName` - name of input where checked values will be stored. Plugin will create new one if input does not exist on the page (only for non checkbox / radio elements). If `inputName` doesn't specified, picker container's id will be used for input name. For avoiding conflict strongly recommend to provide valid, unique name. 50 | * `valueSource` - source from where plugin should get value for element, possible values are: `index`, `text`, `data-*` attribute, default value is `index` (only for non checkbox / radio elements) 51 | * `prePopulate` - string or array of element(s) which should be selected by default (useful for edit mode), could be `index`, `data-*` or `text` of elements', must match to valueSource 52 | * `disabled` - string or array of element(s) which should be disabled (useful for edit mode), could be `index`, `data-*` or `text` of elements', must match to valueSource. Also its possible to disable elements using checkboxes' and radiobuttons' disabled attribute, like `` 53 | * `isSingle` - allows user to select only one option from picker (like input[type="radio"] in pure html forms) default value is false (only for non checkbox / radio elements) 54 | * `cssOptions` - object with options described below: 55 | 56 | 57 | | Option | Default value | Description | 58 | |-----------|----------------|--------------| 59 | | vertical | false | picker's horizontal / vertical position | 60 | | quadratic | false | by default picker is rounded, specify this option true to make it square | 61 | | size | "medium" | picker's size, available values are "small", "medium", "large" | 62 | | picker | empty object | css styles (key / value js object) will be assigned to the picker | 63 | | element | empty object | css styles (key / value js object) will be assigned to the elements inside of picker | 64 | | selected | empty object | css styles (key / value js object) will be assigned to the selected elements inside of picker | 65 | | hover | empty object | css styles (key / value js object) will be assigned to the hover elements inside of picker | | 66 | 67 | ### Events 68 | * `onInit` - called when picker has finished initialization, doesn't receive any argument 69 | * `onSelect` - called when item selected, function receive 2 arguments: selected item and it's value 70 | * `onUnselect` - called when item deselected, function receive 2 arguments: deselected item and it's value 71 | 72 | ### Methods 73 | Methods are implemented in bootstrap.js style - `.multiPicker("methodname").multiPicker("anotherMethod", options)`. All methods are chainable, some of them accepts arguments. 74 | 75 | * `get` - get picker's current value, receive callback style function as an argument. 76 | ```js 77 | $("#days").multiPicker({ selector: 'li' }).multiPicker('get', function (value) { /* value available here */ }); 78 | ``` 79 | * `select` - select elements, receive array or string of element(s) values which should be selected. 80 | ```js 81 | $("#days").multiPicker({ selector: 'li' }).multiPicker('select', [1, 2]); 82 | ``` 83 | * `unselect` - select elements, receive array or string of element(s) values which should be unselected. 84 | ```js 85 | $("#days").multiPicker({ selector: 'li' }).multiPicker('unselect', "2"); 86 | ``` 87 | * `disable` - disable elements, receive array or string of element(s) values which should be disabled. 88 | ```js 89 | $("#days").multiPicker({ selector: 'li' }).multiPicker('disable', [1, 2]); 90 | ``` 91 | * `enable` - enable elements, receive array or string of element(s) values which should be enabled 92 | ```js 93 | $("#days").multiPicker({ selector: 'li' }).multiPicker('enable', [1, 2]); 94 | ``` 95 | * `clear` - reset picker, doesn't receive any argument. 96 | ```js 97 | $("#days").multiPicker({ selector: 'li' }).multiPicker('clear'); 98 | ``` 99 | 100 | #### Usage with checkboxes and radiobuttons. 101 | In case when html tags like `li` or `span` used in multipicker, it will store values in hidden input, which will be a string separated by commas, like this `"Su, Mo, Fr, Sa"`. 102 | You should split this string on the server (on client in some cases), to store these values in database or make it possible to use them in picker in the future (for example when user wants to edit his choices). 103 | 104 | In case when you are using checkboxes or radiobuttons, selected items will check checkbox/radiobutton in standart html way. Picker will modify your markup, and you will get html code like these: 105 | 106 | multiple items for checkboxes 107 | ```html 108 |
109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |
117 | ``` 118 | or single selected item for radiobuttons 119 | ```html 120 |
121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 |
129 | ``` 130 | 131 | You can access selected values in standart way as you would do with checkboxes and radiobuttons if plugin wouldn't been used. 132 | 133 | ##### More Examples: 134 | 135 | Set `isSingle`: true to make only one element selectable like radiobuttons in html. 136 | ```javascript 137 | $("#days").multiPicker({ 138 | selector: "li", 139 | isSingle: true 140 | }); 141 | ``` 142 | ![Multipicker usage with isSingle=true option example](https://cloud.githubusercontent.com/assets/6073745/15856616/b187b774-2cc7-11e6-8d70-accd5c44aa4c.gif) 143 | 144 | Specify `prePopulate` array and valueSource options to get some options selected by default 145 | 146 | ```javascript 147 | $("#days").multiPicker({ 148 | selector : "li", 149 | prePopulate : ["Tu", "Fr"], 150 | valueSource : "data-value" 151 | }); 152 | ``` 153 | ![Multipicker usage with prepopuated selectors example](https://cloud.githubusercontent.com/assets/6073745/15856614/b153e19c-2cc7-11e6-9f24-ca9f62ec3e20.gif) 154 | 155 | 156 | To make your picker vertical just use `cssOptions` `vertical : true` property 157 | 158 | ```html 159 |
160 | Su 161 | Mo 162 | Tu 163 | We 164 | Th 165 | Fr 166 | Sa 167 |
168 | ``` 169 | ```javascript 170 | $("#days-vertical").multiPicker({ 171 | selector : "span", 172 | cssOptions : { 173 | vertical: true 174 | } 175 | }); 176 | ``` 177 | 178 | ![Multipicker usage with `vertical=true` css option example](https://cloud.githubusercontent.com/assets/6073745/15857611/503ea1fc-2ccd-11e6-95b6-989dff377e66.gif) 179 | 180 | ##### Using radiobuttons 181 | 182 | ```html 183 |
184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 |
192 | ``` 193 | ```javascript 194 | $("#languages").multiPicker({ selector : "radio" }); 195 | ``` 196 | ![Multipicker usage with radiobuttons example](https://cloud.githubusercontent.com/assets/6073745/15859929/1cfc7836-2cd8-11e6-93fe-9c54b9186518.gif) 197 | 198 | ##### Using checkboxes 199 | With vertical, quadratic and prepopulated options: 200 | ```html 201 |
202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 |
210 | ``` 211 | ```javascript 212 | $("#programming-languages").multiPicker({ 213 | selector : "checkbox", 214 | prePopulate : ["C", "C++"], 215 | cssOptions : { 216 | quadratic : true, 217 | vertical : true 218 | } 219 | }); 220 | ``` 221 | ![Multipicker usage with checkboxes, `vertical: true`, and `quadratic = true` options example](https://cloud.githubusercontent.com/assets/6073745/15859318/c19e69c4-2cd5-11e6-8838-fa0b1fdcaf03.gif) 222 | 223 | ##### Design customisation 224 | Plugin allows to use label tags to apply item text and keep input's value hidden from user. To apply custom design to multipicker you should use cssOptions (which is described in options section). 225 | 226 | For Example, this picker's code 227 | 228 | ![Multipicker usage with checkboxes, `vertical : true`, and `quadratic = true` options example](https://cloud.githubusercontent.com/assets/6073745/15861747/660ac798-2cde-11e6-8936-316838bca9d6.gif) 229 | 230 | will be: 231 | 232 | ```html 233 |
234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 |
259 | ``` 260 | ```javascript 261 | $("#clubs").multiPicker({ 262 | selector : "checkbox", 263 | prePopulate : "1", 264 | isSingle : true, 265 | cssOptions : { 266 | size : "large", 267 | element : { 268 | "font-size" : "11px", 269 | "color" : "#3a3a3a", 270 | "font-weight" : "bold" 271 | }, 272 | selected: { 273 | "border-color" : "#ff4c4c", 274 | "font-size" : "14px" 275 | }, 276 | picker: { 277 | "border-color" : "#ff4c4c" 278 | } 279 | } 280 | }); 281 | ``` 282 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multipicker", 3 | "main": "multiPicker.js", 4 | "homepage": "http://styopdev.github.io/multiPicker", 5 | "authors": [ 6 | "Stepan Vardanyan " 7 | ], 8 | "description": "Multipicker is jQuery plugin for elements selecting using mouse clicks and drag, supports multi selecting.", 9 | "moduleType": [ 10 | "globals" 11 | ], 12 | "keywords": [ 13 | "picker", 14 | "rangepicker", 15 | "plugin", 16 | "jquery" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /dist/multipicker.min.css: -------------------------------------------------------------------------------- 1 | .checklist::selection{background:none}.checklist::-moz-selection{background:none}.checklist{border:2px solid #e8e8e8;border-radius:30px;display:inline-block;width:auto;padding:4px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checklist > *{display:inline-block;padding:10px 0;width:38px;cursor:pointer;text-align:center;color:#0036ff;border:2px solid transparent;font-size:13px}.checklist > :not(:first-child){margin-left:-7px}.checklist > .active{border-radius:50px;border:2px solid #0036ff;color:#000}.checklist > .right-side{border-bottom-left-radius:0;border-top-left-radius:0;border-left:2px solid transparent}.checklist > .left-side{border-bottom-right-radius:0;border-top-right-radius:0;border-right:2px solid transparent}.checklist > .center-side{border-bottom-right-radius:0;border-top-right-radius:0;border-bottom-left-radius:0;border-top-left-radius:0;border-right:2px solid transparent;border-left:2px solid transparent}.checklist [data-disabled="true"]{cursor:not-allowed}.checklist.vertical{width:38px;padding:3px 4px}.checklist.vertical > *{margin-left:-2px}.checklist.vertical > :not(:first-child){margin-top:-7px}.checklist.vertical > .right-side{border-radius:50px;border-top-right-radius:0;border-top-left-radius:0;border-left:2px solid #0036ff;border-top:2px solid transparent}.checklist.vertical > .left-side{border-radius:50px;border-bottom-left-radius:0;border-bottom-right-radius:0;border-right:2px solid #0036ff;border-bottom:2px solid transparent}.checklist.vertical > .center-side{border-bottom-right-radius:0;border-top-right-radius:0;border-bottom-left-radius:0;border-top-left-radius:0;border-top:2px solid transparent;border-bottom:2px solid transparent;border-right:2px solid #0036ff;border-left:2px solid #0036ff}.checklist.small > *{width:30px;padding:7px 0;font-size:11px}.checklist.small > .active{border-width:1px;padding:8px 1px}.checklist.vertical.small{width:30px;padding:2px 3px}.checklist.large > *{width:45px;padding:12px 0;font-size:15px}.checklist.large > .active{border-width:2px}.checklist.vertical.large{width:45px;padding:5px}.checklist.quadratic,.checklist.quadratic > *,.checklist.vertical.quadratic > *{border-radius:0}.checklist input{display:none}.checklist.more-padded-l{padding-left:11px}.checklist.more-padded-t{padding-top:10px} -------------------------------------------------------------------------------- /dist/multipicker.min.js: -------------------------------------------------------------------------------- 1 | !function(e){var t=function(){this.options={activeClass:"active",valueSource:"index",prePopulate:null,disabled:null,cssOptions:{vertical:!1,quadratic:!1,size:"medium",picker:null,element:null,hover:null,selected:null}},this.type="inline",this.input=null,this.selector=null,this.isPressed=!1,this.lastElem="",this.mouseUpTimer,this.setEvendHandlers=function(){function e(e){var t=e.target;e.originalEvent.changedTouches&&e.originalEvent.changedTouches[0]&&(t=document.elementFromPoint(e.originalEvent.changedTouches[0].clientX,e.originalEvent.changedTouches[0].clientY)),i.isPressed&&i.lastElem!==t&&(i.hover(t),i.lastElem=t),e.preventDefault()}function t(e){i.isPressed=!1,i.mouseUpTimer=setTimeout(function(){i.isPressed=!0},100)}function s(e){i.isPressed=!1}var i=this;this.items.click(function(){i.select.call(this,i,!1)});var o=this.finishHover.bind(this);this.items.on("touchmove",e),this.items.on("touchend",o),this.selector.on("touchstart",t),this.selector.on("touchcancel",s),this.items.mousemove(e),this.items.mouseup(o),this.selector.mousedown(t),this.selector.mouseleave(s)},this.hover=function(t){e(t).hasClass("checklist")||this.select.call(e(t),this,!1)},this.finishHover=function(e){clearTimeout(this.mouseUpTimer),this.lastElem=null,this.isPressed=!1},this.select=function(s,i,o){var n;if(!e(this).attr("data-disabled")){if(n="index"===s.options.valueSource?e(this).index():"data-"===s.options.valueSource.substring(0,5)?e(this).attr(s.options.valueSource):"text"===s.options.valueSource?e(this).text():e(this).val(),s.options.isSingle)return s.clear(),e(this).siblings("."+s.options.activeClass).removeClass(),e(this).addClass(s.options.activeClass),s.addValue(this,n),void(s.options.onSelect&&"function"==typeof s.options.onSelect&&!i&&s.options.onSelect(this,n));if(void 0!==o&&!0===o||void 0===o&&e(this).hasClass(s.options.activeClass)){if(!e(this).hasClass(s.options.activeClass))return;e(this).removeClass(),s.removeValue(this,n),t.updateClasses(e(this),s.options.activeClass),s.options.onUnselect&&"function"==typeof s.options.onUnselect&&!i&&s.options.onUnselect(this,n)}else{if(e(this).hasClass(s.options.activeClass))return;e(this).addClass(s.options.activeClass),s.addValue(this,n),t.updateClasses(e(this),s.options.activeClass),s.options.onSelect&&"function"==typeof s.options.onSelect&&!i&&s.options.onSelect(this,n)}}},this.addValue=function(t,s){if("inline"===this.type){var i=this.input.val();i&&(i+=","),i+=s,this.input.val(i)}else this.selector.find("input[value='"+e(t).attr("data-value")+"']").attr("checked",!0)},this.removeValue=function(t,s){if("inline"===this.type){var i=this.input.val();i=i.replace(","+s,"").replace(s+",","").replace(s,""),this.input.val(i)}else this.selector.find("input[value='"+e(t).attr("data-value")+"']").attr("checked",!1)},this.getValue=function(t){if("inline"===this.type)return t(this.input.val());var s=[];return this.selector.find("input[checked='checked']").each(function(t,i){s.push(e(i).val())}),t(s)},this.clear=function(){"inline"===this.type?(this.input.val(""),this.selector.find(".active").removeClass()):(this.selector.find(".active").removeClass(),this.selector.find("input").attr("checked",!1))},this.prePopulate=function(){if(t.isArray(this.options.prePopulate)&&this.options.prePopulate.length)for(var s in this.options.prePopulate){var i=this.options.prePopulate[s],o=this.getElementSelector(i);e(o).index()<0?console.warn("Multipicker: prepopulated element doesn`t found `%s`",i):this.select.call(o,this,!0)}else{o=this.getElementSelector(this.options.prePopulate);e(o).index()<0?console.warn("Multipicker: prepopulated element doesn`t found`%s`",this.options.prePopulate):this.select.call(o,this,!0)}},this.disable=function(s,i){if(t.isArray(s)&&s.length)for(var o in s){var n=s[o],a=this.getElementSelector(n);e(a).index()<0?console.warn("Multipicker: prepopulated element doesn`t found `%s`",n):i?e(a).removeAttr("data-disabled"):e(a).attr("data-disabled",!0)}else{a=this.getElementSelector(s);e(a).index()<0?console.warn("Multipicker: disabled element doesn`t found`%s`",s):i?e(a).removeAttr("data-disabled"):e(a).attr("data-disabled",!0)}},this.getElementSelector=function(e){return"index"!==this.options.valueSource&&this.options.valueSource?"data-"===this.options.valueSource.substring(0,5)?this.selector.find(this.options.selector+"["+this.options.valueSource+"='"+e+"']"):"text"===this.options.valueSource?this.selector.find(this.options.selector+":contains('"+e+"')"):void 0:this.items.eq(e)}};t.isArray=function(e){if("[object Array]"===Object.prototype.toString.call(e))return!0},t.updateClasses=function(t,s){var i={item:t=e(t),next:t.next(),prev:t.prev(),nextNext:t.next().next(),prevPrev:t.prev().prev()};i.item.hasClass(s)?i.next.hasClass(s)&&i.prev.hasClass(s)?(i.nextNext.hasClass(s)?i.next.attr("class",s+" center-side"):i.next.attr("class",s+" right-side"),i.prevPrev.hasClass(s)?i.prev.attr("class",s+" center-side"):i.prev.attr("class",s+" left-side"),i.item.attr("class","active center-side")):i.next.hasClass(s)&&!i.prev.hasClass(s)?(i.nextNext.hasClass(s)?i.next.attr("class",s+" center-side"):i.next.attr("class",s+" right-side"),i.item.attr("class","active left-side")):!i.next.hasClass(s)&&i.prev.hasClass(s)&&(i.prevPrev.hasClass(s)?i.prev.attr("class",s+" center-side"):i.prev.attr("class",s+" left-side"),i.item.attr("class",s+" right-side")):(i.next.hasClass("right-side")&&i.next.attr("class",s),i.prev.hasClass("left-side")&&i.prev.attr("class",s),i.prev.hasClass("center-side")&&i.prev.attr("class",s+" right-side"),i.next.hasClass("center-side")&&i.next.attr("class",s+" left-side"))},t.generateStyles=function(t,s){var i="";if(s.picker){i+="#"+t+".checklist {";for(var o in s.picker)i+=o+":"+s.picker[o]+";";i+="}"}if(s.element){i+="#"+t+" > * {";for(var o in s.element)i+=o+":"+s.element[o]+";";i+="}"}if(s.selected){i+="#"+t+" > *.active {";for(var o in s.selected)i+=o+":"+s.selected[o]+";";i+="}"}if(s.hover){i+="#"+t+" > *:hover {";for(var o in s.hover)i+=o+":"+s.hover[o]+";";i+="}"}e("head").append("")},t.API=function(e,s,i){if(~["select","unselect","enable","disable","clear","get"].indexOf(e))return(t.isArray(this)?this:[this]).forEach(function(o){if("function"==typeof s?i=s:t.isArray(s)||(s=[s]),"get"===e||"clear"==e||s)switch(e){case"select":s.forEach(function(e){var t=o.getElementSelector(e);t.length&&o.select.call(t,o,!1,!1)});break;case"unselect":s.forEach(function(e){var t=o.getElementSelector(e);t.length&&o.select.call(t,o,!1,!0)});break;case"enable":o.disable.call(o,s,!0);break;case"disable":o.disable.call(o,s,!1);break;case"clear":o.clear();break;case"get":o.getValue(i)}else console.warn("Empty enable/disable elements")}),this;console.warn("Method "+e+" doesn't exist")},e.fn.extend({multiPicker:function(s,i){if("string"!=typeof s){var o=[],n=e(this).length>1;return e(this).each(function(i,a){var l=new t;if(l.options=e.extend(l.options,s),l.selector=e(a),"checkbox"===l.options.selector||"radio"===l.options.selector?(l.type=l.options.selector,"radio"===l.type&&(l.options.isSingle=!0),l.selector.find("label").css("display","none"),l.options.disabled?t.isArray(l.options.disabled)||(l.options.disabled=[l.options.disabled]):l.options.disabled=[],e(l.selector).find("input").each(function(t,s){var i=e(s).val(),o=e("label[for='"+e(s).attr("id")+"']").text()||i;l.selector.append(""+o+""),e(s).prop("disabled")&&l.options.disabled.push(i)}),l.items=l.selector.find("span"),l.options.valueSource="data-value",l.options.selector="span",l.options.cssOptions.vertical?l.selector.addClass("more-padded-t"):l.selector.addClass("more-padded-l")):(l.options.inputName=l.options.inputName||l.selector.attr("id"),"inline"===l.type&&(e("[name="+l.options.inputName+"]").length?l.input=e("[name="+l.options.inputName+"]"):(l.selector.after(""),l.input=e("[name="+l.options.inputName+"]"))),l.items=l.selector.find(l.options.selector)),l.selector.addClass("checklist"),l.options.cssOptions.vertical&&l.selector.addClass("vertical"),l.options.cssOptions.size&&l.selector.addClass(l.options.cssOptions.size),l.options.cssOptions.quadratic&&l.selector.addClass("quadratic"),(l.options.cssOptions.picker||l.options.cssOptions.element||l.options.cssOptions.hover||l.options.cssOptions.selected)&&t.generateStyles(l.selector.attr("id"),l.options.cssOptions),l.options.prePopulate&&t.isArray(l.options.prePopulate)&&l.options.prePopulate.length>1&&l.options.isSingle)throw"Can not prePopulate more then 1 item, with `isSingle` true option";if(l.options.valueSource&&"index"===l.options.valueSource&&"text"===l.options.valueSource&&"data-"!==l.options.valueSource.substring(0,5))throw"Invalid value source";if("data-disabled"===l.options.valueSource)throw"`data-disabled` attribute is reserved, choose another name";(l.options.prePopulate||0===l.options.prePopulate)&&l.prePopulate(),(t.isArray(l.options.disabled)&&l.options.disabled.length||l.options.disabled&&!t.isArray(l.options.disabled)||0===l.options.disabled)&&l.disable(l.options.disabled,!1),l.selector.attr("ondragstart","return false"),l.setEvendHandlers(),l.options.onInit&&"function"==typeof l.options.onInit&&l.options.onInit(),n&&(l.multiPicker=t.API),o.push(l)}),o.multiPicker=t.API,o}}})}(jQuery); 2 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | qunit = require('gulp-qunit'); 3 | 4 | gulp.task('test', function() { 5 | return gulp.src('tests/test.html') 6 | .pipe(qunit()); 7 | }); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Multi Picker 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 79 | 80 | 81 |

Multipicker demo

82 |
83 | 92 |
93 |
94 | 103 |
104 |
105 | 114 |
115 |
116 |
117 | Su 118 | Mo 119 | Tu 120 | We 121 | Th 122 | Fr 123 | Sa 124 |
125 |
126 |
127 |
128 |
129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 |
154 |
155 |
156 | 157 |
158 |
159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 |
167 |
168 |
169 |
170 |
171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 |
179 |
180 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multipicker", 3 | "version": "1.1.1", 4 | "description": "Multipicker is jQuery plugin for elements selecting using mouse clicks and drag, supports multi selecting.", 5 | "main": "multipicker.js", 6 | "scripts": { 7 | "test": "gulp test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/styopdev/multiPicker.git" 12 | }, 13 | "keywords": [ 14 | "rangepicker", 15 | "picker", 16 | "selectable", 17 | "form", 18 | "checkbox" 19 | ], 20 | "author": "Stepan ", 21 | "license": "MIT ", 22 | "bugs": { 23 | "url": "https://github.com/styopdev/multiPicker/issues" 24 | }, 25 | "homepage": "http://styopdev.github.io/multiPicker", 26 | "devDependencies": { 27 | "gulp-qunit": "^1.5.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /roadmap.md: -------------------------------------------------------------------------------- 1 | - [x] All new browsers compatability. 2 | - [x] Checkbox, radiobuttons support. 3 | - [x] Disabled items. 4 | - [x] Initialize using any jquery selector (not only an id), bulk initialize. 5 | - [x] Make it possible to call: select / unselect / disable / enable / getSelected / reset - programmatically f.e. `$(...).multiPicker('select', [1, 2]);` 6 | - [x] Write tests. 7 | -------------------------------------------------------------------------------- /src/multipicker.css: -------------------------------------------------------------------------------- 1 | .checklist::selection { 2 | background: none; 3 | } 4 | .checklist::-moz-selection { 5 | background: none; 6 | } 7 | .checklist { 8 | border: 2px solid #e8e8e8; 9 | border-radius: 30px; 10 | display: inline-block; 11 | width: auto; 12 | padding: 4px; 13 | -webkit-touch-callout: none; 14 | -webkit-user-select: none; 15 | -khtml-user-select: none; 16 | -moz-user-select: none; 17 | -ms-user-select: none; 18 | user-select: none; 19 | } 20 | .checklist > * { 21 | display: inline-block; 22 | padding: 10px 0px; 23 | width: 38px; 24 | cursor: pointer; 25 | text-align: center; 26 | color: #0036ff; 27 | border: 2px solid transparent; 28 | font-size: 13px; 29 | } 30 | .checklist > *:not(:first-child) { 31 | margin-left: -7px; 32 | } 33 | .checklist > .active { 34 | border-radius: 50px; 35 | border: 2px solid #0036ff; 36 | color: #000000; 37 | } 38 | .checklist > .right-side { 39 | border-bottom-left-radius: 0; 40 | border-top-left-radius: 0; 41 | border-left: 2px solid transparent; 42 | } 43 | .checklist > .left-side { 44 | border-bottom-right-radius: 0; 45 | border-top-right-radius: 0; 46 | border-right: 2px solid transparent; 47 | } 48 | .checklist > .center-side { 49 | border-bottom-right-radius:0; 50 | border-top-right-radius: 0; 51 | border-bottom-left-radius: 0; 52 | border-top-left-radius: 0; 53 | border-right: 2px solid transparent; 54 | border-left: 2px solid transparent; 55 | } 56 | .checklist [data-disabled="true"] { 57 | cursor: not-allowed; 58 | } 59 | .checklist.vertical { 60 | width: 38px; 61 | padding: 3px 4px; 62 | } 63 | .checklist.vertical > * { 64 | margin-left: -2px; 65 | } 66 | .checklist.vertical > *:not(:first-child) { 67 | margin-top: -7px; 68 | } 69 | .checklist.vertical > .right-side { 70 | border-radius: 50px; 71 | border-top-right-radius: 0; 72 | border-top-left-radius: 0; 73 | border-left: 2px solid #0036ff; 74 | border-top: 2px solid transparent; 75 | } 76 | .checklist.vertical > .left-side { 77 | border-radius: 50px; 78 | border-bottom-left-radius: 0; 79 | border-bottom-right-radius: 0; 80 | border-right: 2px solid #0036ff; 81 | border-bottom: 2px solid transparent; 82 | } 83 | .checklist.vertical > .center-side { 84 | border-bottom-right-radius:0; 85 | border-top-right-radius: 0 ; 86 | border-bottom-left-radius: 0; 87 | border-top-left-radius: 0 ; 88 | border-top: 2px solid transparent; 89 | border-bottom: 2px solid transparent; 90 | border-right: 2px solid #0036ff; 91 | border-left: 2px solid #0036ff; 92 | } 93 | .checklist.small > * { 94 | width: 30px; 95 | padding: 7px 0; 96 | font-size: 11px; 97 | } 98 | .checklist.small > .active { 99 | border-width: 1px; 100 | padding: 8px 1px; 101 | } 102 | .checklist.vertical.small { 103 | width: 30px; 104 | } 105 | .checklist.vertical.small { 106 | padding: 2px 3px; 107 | } 108 | .checklist.large > * { 109 | width: 45px; 110 | padding: 12px 0px; 111 | font-size: 15px; 112 | } 113 | .checklist.large > .active { 114 | border-width: 2px; 115 | } 116 | .checklist.vertical.large { 117 | width: 45px; 118 | } 119 | .checklist.vertical.large { 120 | padding: 5px 5px; 121 | } 122 | .checklist.quadratic, .checklist.quadratic > * , .checklist.vertical.quadratic > * { 123 | border-radius: 0; 124 | } 125 | .checklist input { 126 | display: none; 127 | } 128 | .checklist.more-padded-l { 129 | padding-left: 11px; 130 | } 131 | .checklist.more-padded-t { 132 | padding-top: 10px; 133 | } 134 | -------------------------------------------------------------------------------- /src/multipicker.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | var MultiPicker = function () { 3 | this.options = { 4 | activeClass : "active", 5 | valueSource : "index", 6 | prePopulate : null, 7 | disabled : null, 8 | cssOptions : { 9 | vertical : false, 10 | quadratic : false, 11 | size : "medium", 12 | picker : null, 13 | element : null, 14 | hover : null, 15 | selected : null 16 | } 17 | }; 18 | this.type = "inline"; 19 | this.input = null; 20 | this.selector = null; 21 | this.isPressed = false; 22 | this.lastElem = ""; 23 | this.mouseUpTimer; 24 | 25 | this.setEvendHandlers = function () { 26 | var picker = this; 27 | this.items.click(function () { 28 | picker.select.call(this, picker, false); 29 | }); 30 | 31 | function mousemove (e) { 32 | var target = e.target; 33 | // get correct target for touch events 34 | if (e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) { 35 | target = document.elementFromPoint(e.originalEvent.changedTouches[0].clientX, e.originalEvent.changedTouches[0].clientY); 36 | } 37 | 38 | if (picker.isPressed && picker.lastElem !== target) { 39 | picker.hover(target); 40 | picker.lastElem = target; 41 | } 42 | e.preventDefault(); 43 | }; 44 | 45 | function mousedown (e) { 46 | picker.isPressed = false; 47 | picker.mouseUpTimer = setTimeout(function () { 48 | picker.isPressed = true; 49 | }, 100); 50 | }; 51 | 52 | function mouseleave (e) { 53 | picker.isPressed = false; 54 | }; 55 | 56 | var mouseup = this.finishHover.bind(this); 57 | // touch events 58 | this.items.on("touchmove", mousemove); 59 | this.items.on("touchend", mouseup); 60 | this.selector.on("touchstart", mousedown); 61 | this.selector.on("touchcancel", mouseleave); 62 | // mouse events 63 | this.items.mousemove(mousemove); 64 | this.items.mouseup(mouseup); 65 | this.selector.mousedown(mousedown); 66 | this.selector.mouseleave(mouseleave); 67 | }; 68 | 69 | this.hover = function (target) { 70 | if (!$(target).hasClass("checklist")) { 71 | this.select.call($(target), this, false); 72 | } 73 | }; 74 | 75 | this.finishHover = function (e) { 76 | clearTimeout(this.mouseUpTimer); 77 | this.lastElem = null; 78 | this.isPressed = false; 79 | }; 80 | 81 | // arguments: element as this, picker, isPrepopulated flag which is true only on init 82 | this.select = function (picker, isPrepopulated, isUnselect) { 83 | var selectedVal; 84 | if ($(this).attr("data-disabled")) { 85 | return; 86 | } 87 | 88 | if (picker.options.valueSource === "index") { 89 | selectedVal = $(this).index(); 90 | } else if (picker.options.valueSource.substring(0, 5) === "data-") { 91 | selectedVal = $(this).attr(picker.options.valueSource); 92 | } else if (picker.options.valueSource === "text") { 93 | selectedVal = $(this).text(); 94 | } else { 95 | selectedVal = $(this).val(); 96 | } 97 | 98 | if (picker.options.isSingle) { 99 | picker.clear(); 100 | 101 | $(this).siblings("." + picker.options.activeClass).removeClass(); 102 | $(this).addClass(picker.options.activeClass); 103 | 104 | picker.addValue(this, selectedVal); 105 | if (picker.options.onSelect && typeof picker.options.onSelect === "function" && !isPrepopulated) { 106 | picker.options.onSelect(this, selectedVal); 107 | } 108 | return; 109 | } 110 | 111 | if ((typeof isUnselect !== "undefined" && isUnselect === true) || (typeof isUnselect === "undefined" && $(this).hasClass(picker.options.activeClass))) { 112 | // unselect case 113 | if (!$(this).hasClass(picker.options.activeClass)) { 114 | return; 115 | } 116 | $(this).removeClass(); 117 | 118 | picker.removeValue(this, selectedVal); 119 | MultiPicker.updateClasses($(this), picker.options.activeClass); 120 | if (picker.options.onUnselect && typeof picker.options.onUnselect === "function" && !isPrepopulated) { 121 | picker.options.onUnselect(this, selectedVal); 122 | } 123 | } else { 124 | // select case 125 | if ($(this).hasClass(picker.options.activeClass)) { 126 | return; 127 | } 128 | $(this).addClass(picker.options.activeClass); 129 | 130 | picker.addValue(this, selectedVal); 131 | 132 | MultiPicker.updateClasses($(this), picker.options.activeClass); 133 | if (picker.options.onSelect && typeof picker.options.onSelect === "function" && !isPrepopulated) { 134 | picker.options.onSelect(this, selectedVal); 135 | } 136 | } 137 | }; 138 | 139 | this.addValue = function (el, val) { 140 | if (this.type === "inline") { 141 | var currValue = this.input.val(); 142 | if (currValue) { 143 | currValue += ","; 144 | } 145 | currValue += val; 146 | 147 | this.input.val(currValue); 148 | } else { 149 | this.selector.find("input[value='" + $(el).attr("data-value") + "']").attr("checked", true); 150 | } 151 | }; 152 | 153 | this.removeValue = function (el, val) { 154 | if (this.type === "inline") { 155 | var currValue = this.input.val(); 156 | currValue = currValue.replace("," + val, "").replace(val + ",", "").replace(val, ""); 157 | this.input.val(currValue); 158 | } else { 159 | this.selector.find("input[value='" + $(el).attr("data-value") + "']").attr("checked", false); 160 | } 161 | }; 162 | 163 | this.getValue = function (cb) { 164 | if (this.type === "inline") { 165 | return cb(this.input.val()); 166 | } else { 167 | var values = []; 168 | 169 | this.selector.find("input[checked='checked']").each(function (index, elem) { 170 | values.push($(elem).val()); 171 | }); 172 | return cb(values); 173 | } 174 | }; 175 | 176 | this.clear = function () { 177 | if (this.type === "inline") { 178 | this.input.val(""); 179 | this.selector.find(".active").removeClass(); 180 | } else { 181 | this.selector.find(".active").removeClass(); 182 | this.selector.find("input").attr("checked", false); 183 | } 184 | }; 185 | 186 | this.prePopulate = function () { 187 | if (MultiPicker.isArray(this.options.prePopulate) && this.options.prePopulate.length) { 188 | for (var key in this.options.prePopulate) { 189 | var searched = this.options.prePopulate[key]; 190 | var element = this.getElementSelector(searched); 191 | 192 | if ($(element).index() < 0) { 193 | console.warn("Multipicker: prepopulated element doesn`t found `%s`", searched); 194 | } else { 195 | this.select.call(element, this, true); 196 | } 197 | } 198 | } else { 199 | var element = this.getElementSelector(this.options.prePopulate); 200 | if ($(element).index() < 0) { 201 | console.warn("Multipicker: prepopulated element doesn`t found`%s`", this.options.prePopulate); 202 | } else { 203 | this.select.call(element, this, true); 204 | } 205 | } 206 | }; 207 | 208 | this.disable = function (disableItems, isEnable) { 209 | if (MultiPicker.isArray(disableItems) && disableItems.length) { 210 | for (var key in disableItems) { 211 | var searched = disableItems[key]; 212 | var element = this.getElementSelector(searched); 213 | 214 | if ($(element).index() < 0) { 215 | console.warn("Multipicker: prepopulated element doesn`t found `%s`", searched); 216 | } else { 217 | if (isEnable) { 218 | $(element).removeAttr("data-disabled"); 219 | } else { 220 | $(element).attr("data-disabled", true); 221 | } 222 | } 223 | } 224 | } else { 225 | var element = this.getElementSelector(disableItems); 226 | if ($(element).index() < 0) { 227 | console.warn("Multipicker: disabled element doesn`t found`%s`", disableItems); 228 | } else { 229 | if (isEnable) { 230 | $(element).removeAttr("data-disabled"); 231 | } else { 232 | $(element).attr("data-disabled", true); 233 | } 234 | } 235 | } 236 | }; 237 | 238 | this.getElementSelector = function (searched) { 239 | if (this.options.valueSource === "index" || !this.options.valueSource) { 240 | return this.items.eq(searched); 241 | } else if (this.options.valueSource.substring(0, 5) === "data-") { 242 | return this.selector.find(this.options.selector + "[" + this.options.valueSource + "='" + searched + "']"); 243 | } else if (this.options.valueSource === "text") { 244 | return this.selector.find(this.options.selector + ":contains('" + searched + "')"); 245 | } 246 | }; 247 | }; 248 | 249 | MultiPicker.isArray = function (obj) { 250 | if (Object.prototype.toString.call(obj) === '[object Array]') { 251 | return true; 252 | } 253 | }; 254 | 255 | MultiPicker.updateClasses = function (item, className) { 256 | var item = $(item); 257 | var ny = { /* nearbyItems */ 258 | item: item, 259 | next: item.next(), 260 | prev: item.prev(), 261 | nextNext: item.next().next(), 262 | prevPrev: item.prev().prev() 263 | }; 264 | 265 | if (ny.item.hasClass(className)) { 266 | if (ny.next.hasClass(className) && ny.prev.hasClass(className)) { 267 | if (ny.nextNext.hasClass(className)) { 268 | ny.next.attr('class', className + " center-side"); 269 | } else { 270 | ny.next.attr('class', className + " right-side"); 271 | } 272 | if (ny.prevPrev.hasClass(className)) { 273 | ny.prev.attr('class', className + " center-side"); 274 | } else { 275 | ny.prev.attr('class', className + " left-side"); 276 | } 277 | ny.item.attr("class", "active center-side"); 278 | } else if (ny.next.hasClass(className) && !ny.prev.hasClass(className)) { 279 | if (ny.nextNext.hasClass(className)) { 280 | ny.next.attr("class", className + " center-side"); 281 | } else { 282 | ny.next.attr("class", className + " right-side"); 283 | } 284 | ny.item.attr("class", "active left-side"); 285 | } else if (!ny.next.hasClass(className) && ny.prev.hasClass(className)) { 286 | if (ny.prevPrev.hasClass(className)) { 287 | ny.prev.attr("class", className + " center-side"); 288 | } else { 289 | ny.prev.attr("class", className + " left-side"); 290 | } 291 | ny.item.attr("class", className + " right-side"); 292 | } 293 | } else { 294 | if (ny.next.hasClass("right-side")) { 295 | ny.next.attr("class", className); 296 | } 297 | if (ny.prev.hasClass("left-side")) { 298 | ny.prev.attr("class", className); 299 | } 300 | if (ny.prev.hasClass("center-side")) { 301 | ny.prev.attr("class", className + " right-side"); 302 | } 303 | if (ny.next.hasClass("center-side")) { 304 | ny.next.attr("class", className + " left-side"); 305 | } 306 | } 307 | }; 308 | 309 | MultiPicker.generateStyles = function (id, cssOptions) { 310 | var styles = ""; 311 | if (cssOptions.picker) { 312 | styles += "#" + id + ".checklist {"; 313 | for (var key in cssOptions.picker) { 314 | styles += key + ":" + cssOptions.picker[key] + ";"; 315 | } 316 | styles += "}"; 317 | } 318 | 319 | if (cssOptions.element) { 320 | styles += "#" + id + " > * {" 321 | for (var key in cssOptions.element) { 322 | styles += key + ":" + cssOptions.element[key] + ";"; 323 | } 324 | styles += "}"; 325 | } 326 | 327 | if (cssOptions.selected) { 328 | styles += "#" + id + " > *.active {" 329 | for (var key in cssOptions.selected) { 330 | styles += key + ":" + cssOptions.selected[key] + ";"; 331 | } 332 | styles += "}"; 333 | } 334 | 335 | if (cssOptions.hover) { 336 | styles += "#" + id + " > *:hover {" 337 | for (var key in cssOptions.hover) { 338 | styles += key + ":" + cssOptions.hover[key] + ";"; 339 | } 340 | styles += "}"; 341 | } 342 | $("head").append(""); 343 | }; 344 | 345 | MultiPicker.API = function (method, values, cb) { 346 | if (!~["select", "unselect", "enable", "disable", "clear", "get"].indexOf(method)) { 347 | console.warn("Method " + method + " doesn't exist"); 348 | return; 349 | } 350 | var pickers = !MultiPicker.isArray(this) ? [this] : this; 351 | 352 | pickers.forEach(function (picker) { 353 | if (typeof values === "function") { 354 | cb = values; 355 | } else if (!MultiPicker.isArray(values)) { 356 | values = [values]; 357 | } 358 | if (method !== "get" && method != "clear") { 359 | if (!values) { 360 | console.warn("Empty enable/disable elements"); 361 | return; 362 | } 363 | } 364 | switch (method) { 365 | case "select" : 366 | values.forEach(function(value) { 367 | var elSelector = picker.getElementSelector(value); 368 | if (elSelector.length) { 369 | picker.select.call(elSelector, picker, false, false); 370 | } 371 | }); 372 | break; 373 | case "unselect" : 374 | values.forEach(function(value) { 375 | var elSelector = picker.getElementSelector(value); 376 | if (elSelector.length) { 377 | picker.select.call(elSelector, picker, false, true); 378 | } 379 | }); 380 | break; 381 | case "enable" : 382 | picker.disable.call(picker, values, true); 383 | break; 384 | case "disable" : 385 | picker.disable.call(picker, values, false); 386 | break; 387 | case "clear" : 388 | picker.clear(); 389 | break; 390 | case "get" : 391 | picker.getValue(cb); 392 | break; 393 | } 394 | }); 395 | return this; 396 | }; 397 | 398 | $.fn.extend({ 399 | multiPicker: function (opt, values) { 400 | if (typeof opt !== "string") { 401 | var pickers = []; 402 | var isBulkInit = $(this).length > 1 ? true : false; 403 | 404 | $(this).each(function(index, elem) { 405 | var picker = new MultiPicker(); 406 | // init picker instance 407 | picker.options = $.extend(picker.options, opt); 408 | picker.selector = $(elem); 409 | 410 | if (picker.options.selector === "checkbox" || picker.options.selector === "radio") { 411 | // in the case when checkbox / radiobutton used for picker, hide them and append new 412 | // `span` tags for each input, with the same value stored in `data-value` attribute 413 | picker.type = picker.options.selector; 414 | if (picker.type === "radio") { 415 | picker.options.isSingle = true; 416 | } 417 | 418 | // hide all labels inside picker 419 | picker.selector.find("label").css("display", "none"); 420 | 421 | if (picker.options.disabled) { 422 | if (!MultiPicker.isArray(picker.options.disabled)) { 423 | picker.options.disabled = [picker.options.disabled]; 424 | } 425 | } else { 426 | picker.options.disabled = []; 427 | } 428 | $(picker.selector).find("input").each(function (index, item) { 429 | var itemValue = $(item).val(); 430 | // use label text if provided else use input `value` attribute 431 | var labelText = $("label[for='" + $(item).attr("id") + "']").text() || itemValue; 432 | picker.selector.append("" + labelText + ""); 433 | if ($(item).prop('disabled')) { 434 | picker.options.disabled.push(itemValue); 435 | } 436 | }); 437 | 438 | picker.items = picker.selector.find("span"); 439 | picker.options.valueSource = "data-value"; 440 | picker.options.selector = "span"; 441 | 442 | if (picker.options.cssOptions.vertical) { 443 | picker.selector.addClass("more-padded-t"); 444 | } else { 445 | picker.selector.addClass("more-padded-l"); 446 | } 447 | } else { 448 | // non-checkbox/radiobuttons used for picker 449 | picker.options.inputName = picker.options.inputName || picker.selector.attr("id"); 450 | 451 | if (picker.type === "inline") { 452 | if (!$("[name=" + picker.options.inputName + "]").length) { 453 | picker.selector.after(""); 454 | picker.input = $("[name=" + picker.options.inputName + "]"); 455 | } else { 456 | picker.input = $("[name=" + picker.options.inputName + "]"); 457 | } 458 | } 459 | picker.items = picker.selector.find(picker.options.selector); 460 | } 461 | 462 | picker.selector.addClass("checklist"); 463 | 464 | if (picker.options.cssOptions.vertical) { 465 | picker.selector.addClass("vertical"); 466 | } 467 | 468 | if (picker.options.cssOptions.size) { 469 | picker.selector.addClass(picker.options.cssOptions.size); 470 | } 471 | 472 | if (picker.options.cssOptions.quadratic) { 473 | picker.selector.addClass("quadratic"); 474 | } 475 | 476 | if (picker.options.cssOptions.picker || picker.options.cssOptions.element || picker.options.cssOptions.hover || picker.options.cssOptions.selected) { 477 | MultiPicker.generateStyles(picker.selector.attr("id"), picker.options.cssOptions); 478 | } 479 | 480 | if (picker.options.prePopulate && MultiPicker.isArray(picker.options.prePopulate) && picker.options.prePopulate.length > 1 && picker.options.isSingle) { 481 | throw "Can not prePopulate more then 1 item, with `isSingle` true option"; 482 | } 483 | 484 | if (picker.options.valueSource && !(picker.options.valueSource !== "index" || picker.options.valueSource !== "text" || picker.options.valueSource.substring(0, 5) === "data-")) { 485 | throw "Invalid value source"; 486 | } else if (picker.options.valueSource === "data-disabled") { 487 | throw "`data-disabled` attribute is reserved, choose another name"; 488 | } 489 | 490 | if (picker.options.prePopulate || picker.options.prePopulate === 0) { 491 | picker.prePopulate(); 492 | } 493 | 494 | if (MultiPicker.isArray(picker.options.disabled) && picker.options.disabled.length || picker.options.disabled && !MultiPicker.isArray(picker.options.disabled) || picker.options.disabled === 0) { 495 | picker.disable(picker.options.disabled, false); 496 | } 497 | 498 | picker.selector.attr("ondragstart", 'return false'); 499 | picker.setEvendHandlers(); 500 | 501 | if (picker.options.onInit && typeof picker.options.onInit === "function") { 502 | picker.options.onInit(); 503 | } 504 | if (isBulkInit) { 505 | picker.multiPicker = MultiPicker.API; 506 | } 507 | pickers.push(picker); 508 | }); 509 | 510 | pickers.multiPicker = MultiPicker.API; 511 | return pickers; 512 | } 513 | } 514 | }); 515 | })(jQuery); 516 | -------------------------------------------------------------------------------- /tests/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test page 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | 22 | 23 | 30 | 31 | 35 | 36 | 45 | 46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
55 | 56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 | 66 | 73 | 74 | 83 | 84 |
85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 |
93 | 94 |
95 | 96 | 97 | 98 | 99 | 100 | 101 |
102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | QUnit.test("Simple initialization", function(assert) { 2 | assert.expect(4); 3 | 4 | var picker = $("#days").multiPicker({ 5 | selector: "li", 6 | onInit: function () { 7 | assert.ok(true, "onInit called"); 8 | } 9 | }); 10 | 11 | assert.ok($("#days").hasClass("checklist"), "Css class `checklist` added"); 12 | assert.equal($("input[type='hidden'][name='days']").length, 1, "Hidden input has been created"); 13 | assert.equal($("#days .active").length, 0, "No elements has been selected"); 14 | }); 15 | 16 | QUnit.test("Initialization with prepopulate", function(assert) { 17 | assert.expect(5); 18 | 19 | $("#prepopulate").multiPicker({ 20 | selector: "li", 21 | prePopulate: ["0", "1"], 22 | cssOptions: { 23 | size: "large", 24 | element: { 25 | "font-size": "11px" 26 | } 27 | }, 28 | onInit: function () { 29 | assert.ok(true, "on init"); 30 | } 31 | }); 32 | assert.ok($("#prepopulate li:eq(0)").hasClass("active") && $("#prepopulate li:eq(1)").hasClass("active"), "Days 0, 2 have been selected"); 33 | assert.ok(!$("#prepopulate li:eq(3)").hasClass("active") && !$("#prepopulate li:eq(4)").hasClass("active") && !$("#prepopulate li:eq(5)").hasClass("active"), "Days 3, 4, 5 are not selected"); 34 | 35 | assert.ok($("#prepopulate").hasClass("checklist"), "Css class `checklist` added"); 36 | assert.equal($("input[type='hidden'][name='prepopulate']").length, 1, "Hidden input created"); 37 | }); 38 | 39 | QUnit.test("Initializate single picker", function(assert) { 40 | assert.expect(3); 41 | 42 | $("#single").multiPicker({ 43 | selector: "li", 44 | isSingle: true, 45 | onInit: function () { 46 | assert.ok(true, "onInit called"); 47 | } 48 | }); 49 | 50 | assert.ok($("#single").hasClass("checklist"), "Css class `checklist` added"); 51 | assert.equal($("input[type='hidden'][name='single']").length, 1, "Hidden input created"); 52 | }); 53 | 54 | QUnit.test("Initialize with specified input name option", function(assert) { 55 | assert.expect(4); 56 | $("#input-name-specified").multiPicker({ 57 | selector: "li", 58 | inputName: "yes-or-no", 59 | onInit: function() { 60 | assert.ok(true, "onInit called"); 61 | } 62 | }); 63 | 64 | assert.ok($("#input-name-specified").hasClass("checklist"), "Css class `checklist` added"); 65 | assert.equal($("input[type='hidden'][name='yes-or-no']").length, 1, "New input with specified name created"); 66 | assert.equal($("input[type='hidden'][name='input-name-specified']").length, 0, "Id doesnt used for new input name"); 67 | }); 68 | 69 | QUnit.test("Initialize checkbox", function(assert) { 70 | assert.expect(3); 71 | $("#programming-languages").multiPicker({ 72 | selector: "checkbox", 73 | onInit: function() { 74 | assert.ok(true, "onInit called"); 75 | } 76 | }); 77 | 78 | assert.ok($("#programming-languages").hasClass("checklist"), "CSS class `checklist` added"); 79 | assert.equal($("input[type='hidden'][name='programming-languages']").length, 0, "No hidden input created"); 80 | }); 81 | 82 | QUnit.test("Initialize radiobuttons", function(assert) { 83 | assert.expect(3); 84 | $("#languages").multiPicker({ 85 | selector: "radio", 86 | onInit: function() { 87 | assert.ok(true, "onInit called"); 88 | } 89 | }); 90 | 91 | assert.ok($("#languages").hasClass("checklist"), "Css class `checklist` added"); 92 | assert.equal($("input[type='hidden'][name='languages']").length, 0, "No hidden input created"); 93 | }); 94 | 95 | QUnit.test("Test events ", function(assert) { 96 | assert.expect(5); 97 | 98 | var picker = $("#test-events").multiPicker({ 99 | selector: "li", 100 | onInit: function () { 101 | assert.ok(true, "onInit called"); 102 | }, 103 | onSelect: function (el, val) { 104 | assert.ok(true, "on Select called"); 105 | assert.equal(val, "1", "on Select value is ok"); 106 | }, 107 | onUnselect : function (el, val) { 108 | assert.ok(true, "Unselect called"); 109 | assert.equal(val, "1", "Unselect value is ok"); 110 | } 111 | }); 112 | 113 | picker.multiPicker("select", ["1"]); 114 | picker.multiPicker("unselect", ["1"]); 115 | }); 116 | 117 | QUnit.test("Api test", function(assert) { 118 | assert.expect(11); 119 | 120 | var picker = $("#api-test").multiPicker({ 121 | selector: "li" 122 | }); 123 | 124 | picker.multiPicker('select', [1, 3]); 125 | assert.ok($("#api-test li:eq(1)").hasClass("active") && $("#api-test li:eq(3)").hasClass("active"), "Days 1, 3 have been selected"); 126 | 127 | picker.multiPicker('select', 4); 128 | assert.ok($("#api-test li:eq(4)").hasClass("active"), "Day 4 has been selected"); 129 | 130 | picker.multiPicker('unselect', 4); 131 | assert.ok(!$("#api-test li:eq(4)").hasClass("active"), "Day 4 has been unselected"); 132 | 133 | picker.multiPicker('unselect', [1, 3]); 134 | assert.ok(!$("#api-test li:eq(1)").hasClass("active") && !$("#api-test li:eq(3)").hasClass("active"), "Days 1, 3 have been unselected"); 135 | 136 | picker.multiPicker('select', [2, 4]); 137 | picker.multiPicker('clear'); 138 | assert.equal($("#api-test .active").length, 0, "Picker cleared"); 139 | picker.multiPicker('get', function (val) { 140 | assert.ok(!val, 'Pickers value cleared'); 141 | }); 142 | 143 | picker.multiPicker('select', [1, 3]); 144 | picker.multiPicker('get', function (val) { 145 | assert.equal(val, '1,3', "Picker values specified correctly"); 146 | }); 147 | 148 | picker.multiPicker('disable', 0); 149 | assert.ok($("#api-test li:eq(0)").attr("data-disabled"), "Day 0 has been disabled"); 150 | 151 | picker.multiPicker('enable', 0); 152 | assert.ok(!$("#api-test li:eq(0)").attr("data-disabled"), "Day 0 has been enabled"); 153 | 154 | picker.multiPicker('disable', [1, 3, 4]); 155 | assert.ok($("#api-test li:eq(1)").attr("data-disabled") && $("#api-test li:eq(3)").attr("data-disabled") && $("#api-test li:eq(4)").attr("data-disabled"), "Day 1, 3, 4 have been disabled"); 156 | 157 | picker.multiPicker('enable', [1, 3, 4]); 158 | assert.ok(!$("#api-test li:eq(1)").attr("data-disabled") && !$("#api-test li:eq(3)").attr("data-disabled") && !$("#api-test li:eq(4)").attr("data-disabled"), "Day 1, 3, 4 have been enabled"); 159 | }); 160 | $("#api-test-radio").multiPicker({ 161 | selector: "radio" 162 | }); 163 | 164 | QUnit.test("Api test checkboxes", function(assert) { 165 | assert.expect(11); 166 | 167 | var picker = $("#api-test-checkbox").multiPicker({ 168 | selector: "checkbox" 169 | }); 170 | 171 | picker.multiPicker('select', ["EN", "RU"]); 172 | assert.ok($("#api-test-checkbox span[data-value='EN']").hasClass("active") && $("#api-test-checkbox span[data-value='RU']").hasClass("active"), "Langs EN, HY have been selected"); 173 | 174 | picker.multiPicker('select', "HY"); 175 | assert.ok($("#api-test-checkbox span[data-value='HY']").hasClass("active"), "Lang HY has been selected"); 176 | 177 | picker.multiPicker('unselect', "HY"); 178 | assert.ok(!$("#api-test-checkbox span[data-value='HY']").hasClass("active"), "Lang HY has been unselected"); 179 | 180 | picker.multiPicker('unselect', ["EN", "RU"]); 181 | assert.ok(!$("#api-test-checkbox span[data-value='EN']").hasClass("active") && !$("#api-test-checkbox span[data-value='RU']").hasClass("active"), "Langs EN, HY have been unselected"); 182 | 183 | picker.multiPicker('select', ["HY", "JP"]); 184 | picker.multiPicker('clear'); 185 | assert.equal($("#api-test-checkbox .active").length, 0, "Picker cleared"); 186 | picker.multiPicker('get', function (val) { 187 | assert.equal(val.length, 0, 'Pickers value cleared'); 188 | }); 189 | 190 | picker.multiPicker('select', ["EN", "RU"]); 191 | picker.multiPicker('get', function (val) { 192 | assert.deepEqual(val, ["EN", "RU"], "Picker values specified correctly"); 193 | }); 194 | 195 | picker.multiPicker('disable', "HY"); 196 | assert.ok($("#api-test-checkbox span[data-value='HY']").attr("data-disabled"), "Lang HY has been disabled"); 197 | 198 | picker.multiPicker('enable', "HY"); 199 | assert.ok(!$("#api-test-checkbox span[data-value='HY']").attr("data-disabled"), "Lang HY has been enabled"); 200 | 201 | picker.multiPicker('disable', ["EN", "RU", "JP"]); 202 | assert.ok($("#api-test-checkbox span[data-value='EN']").attr("data-disabled") && $("#api-test-checkbox span[data-value='RU']").attr("data-disabled") && $("#api-test-checkbox span[data-value='JP']").attr("data-disabled"), "Langs EN, RU, JP have been disabled"); 203 | 204 | picker.multiPicker('enable', ["EN", "RU", "JP"]); 205 | assert.ok(!$("#api-test-checkbox span[data-value='EN']").attr("data-disabled") && !$("#api-test-checkbox span[data-value='RU']").attr("data-disabled") && !$("#api-test-checkbox span[data-value='JP']").attr("data-disabled"), "Langs EN, RU, JP have been enabled"); 206 | }); 207 | --------------------------------------------------------------------------------