jQuery UI MultiSelect Widget Demos
7 | 8 |9 | Download 10 | | 11 | npm 12 |
13 | 14 | 52 | 53 |├── .eslintrc ├── .github ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── GPL-LICENSE ├── Gemfile ├── Gemfile.lock ├── MIT-LICENSE ├── README.MD ├── css ├── jquery.multiselect.css └── jquery.multiselect.filter.css ├── docs ├── _config.yml ├── _includes │ ├── demo-styles.html │ └── jquery-ui.html ├── _layouts │ └── default.html ├── animations.html ├── basic.html ├── callbacks.html ├── css │ ├── jquery.multiselect.css │ ├── jquery.multiselect.filter.css │ ├── prettify.css │ └── style.css ├── enabledisable.html ├── filter.html ├── headers.html ├── index.html ├── js │ ├── prettify.js │ └── widget.min.js ├── maxchecked.html ├── position.html ├── preselected.html ├── refresh.html ├── selectedlist.html └── single.html ├── i18n ├── jquery.multiselect.br.js ├── jquery.multiselect.cs.js ├── jquery.multiselect.de.js ├── jquery.multiselect.es.js ├── jquery.multiselect.filter.br.js ├── jquery.multiselect.filter.cs.js ├── jquery.multiselect.filter.de.js ├── jquery.multiselect.filter.es.js ├── jquery.multiselect.filter.fr.js ├── jquery.multiselect.filter.hu.js ├── jquery.multiselect.filter.it.js ├── jquery.multiselect.filter.ja.js ├── jquery.multiselect.filter.pl.js ├── jquery.multiselect.filter.ru.js ├── jquery.multiselect.filter.tr.js ├── jquery.multiselect.filter.zh-cn.js ├── jquery.multiselect.filter.zh-tw.js ├── jquery.multiselect.fr.js ├── jquery.multiselect.hu.js ├── jquery.multiselect.it.js ├── jquery.multiselect.ja.js ├── jquery.multiselect.pl.js ├── jquery.multiselect.ru.js ├── jquery.multiselect.tr.js ├── jquery.multiselect.zh-cn.js └── jquery.multiselect.zh-tw.js ├── package-lock.json ├── package.json ├── src ├── jquery.multiselect.filter.js └── jquery.multiselect.js └── tests ├── unit ├── core.js ├── events.js ├── filter.js ├── html.js ├── index.htm ├── methods.js ├── options.js └── setup.js └── visual ├── form-reset.htm ├── formsubmission-single.php ├── formsubmission.cfm ├── formsubmission.htm ├── formsubmission.php ├── style.css └── widget-containers.htm /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "google", 3 | "parserOptions": { 4 | "ecmaVersion": 5 5 | }, 6 | "rules": { 7 | "semi": "error", 8 | "no-var": "off", 9 | "max-len": "off", 10 | }, 11 | "env": { 12 | "es6": false 13 | } 14 | } -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ### What is the current behavior? 2 | 3 | 4 | 5 | ### What is the expected behavior? 6 | 7 | 8 | 9 | ### If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo 10 | _Common options for demos are [jsfiddle](https://jsfiddle.net/) and [codepen](https://codepen.io)_ 11 | 12 | 13 | 14 | ### If you are requesting a new or changed feature, please provide a rationale 15 | 16 | 17 | ### Will you submit a corresponding Pull Request to fix the bug or implement the feature? 18 | 19 | 20 | ### Please tell us about your setup 21 | 22 | * Version: 23 | * Browser: 24 | * Other frameworks in use: 25 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### What changes are you proposing? Why are they needed? 2 | 3 | 4 | ### Related Issue numbers 5 | 6 | 7 | 8 | ### Pull Request Approval Checklist: 9 | - [ ] Tests Ran and 0 Failing 10 | - [ ] Tests Added/Updated 11 | - [ ] Impacted Demos Updated 12 | - [ ] Impacted i18n Code Updated 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | docs/public 3 | docs/_site 4 | docs/.sass-cache 5 | .vscode 6 | node_modules 7 | tests/unit/debug.log 8 | -------------------------------------------------------------------------------- /GPL-LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | a 280 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'github-pages', group: :jekyll_plugins 3 | 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (4.2.9) 5 | i18n (~> 0.7) 6 | minitest (~> 5.1) 7 | thread_safe (~> 0.3, >= 0.3.4) 8 | tzinfo (~> 1.1) 9 | addressable (2.5.2) 10 | public_suffix (>= 2.0.2, < 4.0) 11 | coffee-script (2.4.1) 12 | coffee-script-source 13 | execjs 14 | coffee-script-source (1.11.1) 15 | colorator (1.1.0) 16 | commonmarker (0.17.9) 17 | ruby-enum (~> 0.5) 18 | concurrent-ruby (1.0.5) 19 | dnsruby (1.60.2) 20 | em-websocket (0.5.1) 21 | eventmachine (>= 0.12.9) 22 | http_parser.rb (~> 0.6.0) 23 | ethon (0.11.0) 24 | ffi (>= 1.3.0) 25 | eventmachine (1.2.7) 26 | execjs (2.7.0) 27 | faraday (0.15.1) 28 | multipart-post (>= 1.2, < 3) 29 | ffi (1.9.23) 30 | forwardable-extended (2.6.0) 31 | gemoji (3.0.0) 32 | github-pages (185) 33 | activesupport (= 4.2.9) 34 | github-pages-health-check (= 1.8.1) 35 | jekyll (= 3.7.3) 36 | jekyll-avatar (= 0.5.0) 37 | jekyll-coffeescript (= 1.1.1) 38 | jekyll-commonmark-ghpages (= 0.1.5) 39 | jekyll-default-layout (= 0.1.4) 40 | jekyll-feed (= 0.9.3) 41 | jekyll-gist (= 1.5.0) 42 | jekyll-github-metadata (= 2.9.4) 43 | jekyll-mentions (= 1.3.0) 44 | jekyll-optional-front-matter (= 0.3.0) 45 | jekyll-paginate (= 1.1.0) 46 | jekyll-readme-index (= 0.2.0) 47 | jekyll-redirect-from (= 0.13.0) 48 | jekyll-relative-links (= 0.5.3) 49 | jekyll-remote-theme (= 0.3.1) 50 | jekyll-sass-converter (= 1.5.2) 51 | jekyll-seo-tag (= 2.4.0) 52 | jekyll-sitemap (= 1.2.0) 53 | jekyll-swiss (= 0.4.0) 54 | jekyll-theme-architect (= 0.1.1) 55 | jekyll-theme-cayman (= 0.1.1) 56 | jekyll-theme-dinky (= 0.1.1) 57 | jekyll-theme-hacker (= 0.1.1) 58 | jekyll-theme-leap-day (= 0.1.1) 59 | jekyll-theme-merlot (= 0.1.1) 60 | jekyll-theme-midnight (= 0.1.1) 61 | jekyll-theme-minimal (= 0.1.1) 62 | jekyll-theme-modernist (= 0.1.1) 63 | jekyll-theme-primer (= 0.5.3) 64 | jekyll-theme-slate (= 0.1.1) 65 | jekyll-theme-tactile (= 0.1.1) 66 | jekyll-theme-time-machine (= 0.1.1) 67 | jekyll-titles-from-headings (= 0.5.1) 68 | jemoji (= 0.9.0) 69 | kramdown (= 1.16.2) 70 | liquid (= 4.0.0) 71 | listen (= 3.1.5) 72 | mercenary (~> 0.3) 73 | minima (= 2.4.1) 74 | nokogiri (>= 1.8.1, < 2.0) 75 | rouge (= 2.2.1) 76 | terminal-table (~> 1.4) 77 | github-pages-health-check (1.8.1) 78 | addressable (~> 2.3) 79 | dnsruby (~> 1.60) 80 | octokit (~> 4.0) 81 | public_suffix (~> 2.0) 82 | typhoeus (~> 1.3) 83 | html-pipeline (2.8.0) 84 | activesupport (>= 2) 85 | nokogiri (>= 1.4) 86 | http_parser.rb (0.6.0) 87 | i18n (0.9.5) 88 | concurrent-ruby (~> 1.0) 89 | jekyll (3.7.3) 90 | addressable (~> 2.4) 91 | colorator (~> 1.0) 92 | em-websocket (~> 0.5) 93 | i18n (~> 0.7) 94 | jekyll-sass-converter (~> 1.0) 95 | jekyll-watch (~> 2.0) 96 | kramdown (~> 1.14) 97 | liquid (~> 4.0) 98 | mercenary (~> 0.3.3) 99 | pathutil (~> 0.9) 100 | rouge (>= 1.7, < 4) 101 | safe_yaml (~> 1.0) 102 | jekyll-avatar (0.5.0) 103 | jekyll (~> 3.0) 104 | jekyll-coffeescript (1.1.1) 105 | coffee-script (~> 2.2) 106 | coffee-script-source (~> 1.11.1) 107 | jekyll-commonmark (1.2.0) 108 | commonmarker (~> 0.14) 109 | jekyll (>= 3.0, < 4.0) 110 | jekyll-commonmark-ghpages (0.1.5) 111 | commonmarker (~> 0.17.6) 112 | jekyll-commonmark (~> 1) 113 | rouge (~> 2) 114 | jekyll-default-layout (0.1.4) 115 | jekyll (~> 3.0) 116 | jekyll-feed (0.9.3) 117 | jekyll (~> 3.3) 118 | jekyll-gist (1.5.0) 119 | octokit (~> 4.2) 120 | jekyll-github-metadata (2.9.4) 121 | jekyll (~> 3.1) 122 | octokit (~> 4.0, != 4.4.0) 123 | jekyll-mentions (1.3.0) 124 | activesupport (~> 4.0) 125 | html-pipeline (~> 2.3) 126 | jekyll (~> 3.0) 127 | jekyll-optional-front-matter (0.3.0) 128 | jekyll (~> 3.0) 129 | jekyll-paginate (1.1.0) 130 | jekyll-readme-index (0.2.0) 131 | jekyll (~> 3.0) 132 | jekyll-redirect-from (0.13.0) 133 | jekyll (~> 3.3) 134 | jekyll-relative-links (0.5.3) 135 | jekyll (~> 3.3) 136 | jekyll-remote-theme (0.3.1) 137 | jekyll (~> 3.5) 138 | rubyzip (>= 1.2.1, < 3.0) 139 | jekyll-sass-converter (1.5.2) 140 | sass (~> 3.4) 141 | jekyll-seo-tag (2.4.0) 142 | jekyll (~> 3.3) 143 | jekyll-sitemap (1.2.0) 144 | jekyll (~> 3.3) 145 | jekyll-swiss (0.4.0) 146 | jekyll-theme-architect (0.1.1) 147 | jekyll (~> 3.5) 148 | jekyll-seo-tag (~> 2.0) 149 | jekyll-theme-cayman (0.1.1) 150 | jekyll (~> 3.5) 151 | jekyll-seo-tag (~> 2.0) 152 | jekyll-theme-dinky (0.1.1) 153 | jekyll (~> 3.5) 154 | jekyll-seo-tag (~> 2.0) 155 | jekyll-theme-hacker (0.1.1) 156 | jekyll (~> 3.5) 157 | jekyll-seo-tag (~> 2.0) 158 | jekyll-theme-leap-day (0.1.1) 159 | jekyll (~> 3.5) 160 | jekyll-seo-tag (~> 2.0) 161 | jekyll-theme-merlot (0.1.1) 162 | jekyll (~> 3.5) 163 | jekyll-seo-tag (~> 2.0) 164 | jekyll-theme-midnight (0.1.1) 165 | jekyll (~> 3.5) 166 | jekyll-seo-tag (~> 2.0) 167 | jekyll-theme-minimal (0.1.1) 168 | jekyll (~> 3.5) 169 | jekyll-seo-tag (~> 2.0) 170 | jekyll-theme-modernist (0.1.1) 171 | jekyll (~> 3.5) 172 | jekyll-seo-tag (~> 2.0) 173 | jekyll-theme-primer (0.5.3) 174 | jekyll (~> 3.5) 175 | jekyll-github-metadata (~> 2.9) 176 | jekyll-seo-tag (~> 2.0) 177 | jekyll-theme-slate (0.1.1) 178 | jekyll (~> 3.5) 179 | jekyll-seo-tag (~> 2.0) 180 | jekyll-theme-tactile (0.1.1) 181 | jekyll (~> 3.5) 182 | jekyll-seo-tag (~> 2.0) 183 | jekyll-theme-time-machine (0.1.1) 184 | jekyll (~> 3.5) 185 | jekyll-seo-tag (~> 2.0) 186 | jekyll-titles-from-headings (0.5.1) 187 | jekyll (~> 3.3) 188 | jekyll-watch (2.0.0) 189 | listen (~> 3.0) 190 | jemoji (0.9.0) 191 | activesupport (~> 4.0, >= 4.2.9) 192 | gemoji (~> 3.0) 193 | html-pipeline (~> 2.2) 194 | jekyll (~> 3.0) 195 | kramdown (1.16.2) 196 | liquid (4.0.0) 197 | listen (3.1.5) 198 | rb-fsevent (~> 0.9, >= 0.9.4) 199 | rb-inotify (~> 0.9, >= 0.9.7) 200 | ruby_dep (~> 1.2) 201 | mercenary (0.3.6) 202 | mini_portile2 (2.3.0) 203 | minima (2.4.1) 204 | jekyll (~> 3.5) 205 | jekyll-feed (~> 0.9) 206 | jekyll-seo-tag (~> 2.1) 207 | minitest (5.11.3) 208 | multipart-post (2.0.0) 209 | nokogiri (1.8.2) 210 | mini_portile2 (~> 2.3.0) 211 | octokit (4.9.0) 212 | sawyer (~> 0.8.0, >= 0.5.3) 213 | pathutil (0.16.1) 214 | forwardable-extended (~> 2.6) 215 | public_suffix (2.0.5) 216 | rb-fsevent (0.10.3) 217 | rb-inotify (0.9.10) 218 | ffi (>= 0.5.0, < 2) 219 | rouge (2.2.1) 220 | ruby-enum (0.7.2) 221 | i18n 222 | ruby_dep (1.5.0) 223 | rubyzip (1.2.1) 224 | safe_yaml (1.0.4) 225 | sass (3.5.6) 226 | sass-listen (~> 4.0.0) 227 | sass-listen (4.0.0) 228 | rb-fsevent (~> 0.9, >= 0.9.4) 229 | rb-inotify (~> 0.9, >= 0.9.7) 230 | sawyer (0.8.1) 231 | addressable (>= 2.3.5, < 2.6) 232 | faraday (~> 0.8, < 1.0) 233 | terminal-table (1.8.0) 234 | unicode-display_width (~> 1.1, >= 1.1.1) 235 | thread_safe (0.3.6) 236 | typhoeus (1.3.0) 237 | ethon (>= 0.9.0) 238 | tzinfo (1.2.5) 239 | thread_safe (~> 0.1) 240 | unicode-display_width (1.3.2) 241 | 242 | PLATFORMS 243 | ruby 244 | 245 | DEPENDENCIES 246 | github-pages 247 | 248 | BUNDLED WITH 249 | 2.1.2 250 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Eric Hynds 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # jQuery UI MultiSelect Widget 2 | 3 | MultiSelect progessively enhances an ordinary multiple select control into elegant drop down list of checkboxes, stylable with ThemeRoller. 4 | 5 | ## Demo Page 6 | 7 | Check out the demo page [here!](https://ehynds.github.io/jquery-ui-multiselect-widget/) 8 | 9 | ## Version 3 10 | 11 | Version 3 of the widget is available. This introduces a lot of new features and substantial performance imrovements. 12 | 13 | Check out the [release notes](https://github.com/ehynds/jquery-ui-multiselect-widget/releases/tag/3.0.0) for an exact list of changes and see the [wiki](https://github.com/ehynds/jquery-ui-multiselect-widget/wiki/Migrating-to-Version-3) for the information you may need to switch over. The main wiki will be getting updates for the new features and changed options. 14 | 15 | ## Requirements 16 | 17 | The [usage](https://github.com/ehynds/jquery-ui-multiselect-widget/wiki/Usage) section of the Wiki specifies the widget's dependencies. 18 | 19 | ## License 20 | 21 | MultiSelect is dual-licensed under the [GPL 2 license](https://github.com/ehynds/jquery-ui-multiselect-widget/blob/master/GPL-LICENSE) and the [MIT license](https://github.com/ehynds/jquery-ui-multiselect-widget/blob/master/MIT-LICENSE). 22 | 23 | ## Contributing 24 | 25 | When submitting a pull request, please describe the change you are making - preferably with a use case. Unit tests are now (14 March 2016) required for the pull to be merged. 26 | 27 | Please do not submit minified code in your pull request, that tends to cause merge conflicts. 28 | 29 | You can test the demo pages locally by navigating to the `docs` directory and running `bundle install && bundle exec jekyll server`. 30 | -------------------------------------------------------------------------------- /css/jquery.multiselect.css: -------------------------------------------------------------------------------- 1 | .ui-multiselect {box-sizing: border-box; padding:2px 0 2px 4px; text-align:left; width: auto;} 2 | .ui-multiselect .ui-multiselect-open { float:right } 3 | 4 | .ui-multiselect-menu { display:none; box-sizing:border-box; position:absolute; text-align:left; z-index: 101; width:auto; height:auto; padding:3px; } 5 | .ui-multiselect-menu.ui-multiselect-listbox {position:relative; z-index: 0;} 6 | 7 | .ui-multiselect-header { display:block; box-sizing:border-box; position:relative; width:auto; padding:3px 0 3px 4px; margin-bottom:2px;} 8 | .ui-multiselect-header > ul { font-size:0.9em } 9 | .ui-multiselect-header li { float:left; margin:0 10px 0 0;} 10 | .ui-multiselect-header a { text-decoration:none; } 11 | .ui-multiselect-header a:hover { text-decoration:underline; cursor: pointer;} 12 | .ui-multiselect-header .ui-icon { float:left; } 13 | .ui-multiselect-header .ui-multiselect-close { float:right; margin-right:0; text-align:right; } 14 | 15 | .ui-multiselect-checkboxes { display:block; box-sizing:border-box; position:relative; overflow:auto; width: auto; border: 0; padding: 4px 0 8px;} 16 | .ui-multiselect-checkboxes li:not(.ui-multiselect-optgroup) { clear:both; font-size:0.9em; list-style: none; padding-right:3px;} 17 | .ui-multiselect-checkboxes label { border:1px solid transparent; cursor:default; display:block; padding:3px 1px 3px 21px; text-indent: -20px;} 18 | .ui-multiselect-checkboxes input { position:relative; top:1px; cursor: pointer;} 19 | .ui-multiselect-checkboxes img { height: 30px; vertical-align: middle; margin-right: 3px;} 20 | .ui-multiselect-grouplabel { border-bottom:1px solid; display:block; font-weight:bold; margin:1px 0; padding:3px; text-align:center; text-decoration:none; } 21 | .ui-multiselect-selectable { cursor: pointer; } 22 | .ui-multiselect-optgroup > ul { padding: 3px; } 23 | .ui-multiselect-columns { display: inline-block; vertical-align: top; } 24 | .ui-multiselect-collapser { float: left; padding: 0 1px; margin: 0; } 25 | .ui-multiselect-collapsed > ul { display:none } 26 | 27 | .ui-multiselect-single .ui-multiselect-checkboxes input { left:-9999px; position:absolute !important; top: auto !important; } 28 | .ui-multiselect-single .ui-multiselect-checkboxes label { padding:5px !important; text-indent: 0 !important; } 29 | 30 | .ui-multiselect.ui-multiselect-nowrap { white-space: nowrap } 31 | .ui-multiselect.ui-multiselect-nowrap > span { display: inline-block } 32 | .ui-multiselect-checkboxes.ui-multiselect-nowrap li, 33 | .ui-multiselect-checkboxes.ui-multiselect-nowrap a { white-space: nowrap } 34 | 35 | .ui-multiselect-measure > .ui-multiselect-header, 36 | .ui-multiselect-measure > .ui-multiselect-checkboxes { float: left; } 37 | .ui-multiselect-measure > .ui-multiselect-checkboxes { margin: 4px; overflow-y: scroll; } 38 | 39 | .ui-multiselect-resize { border: 2px dotted #00F } 40 | 41 | @media print{ 42 | .ui-multiselect-menu {display: none;} 43 | } 44 | -------------------------------------------------------------------------------- /css/jquery.multiselect.filter.css: -------------------------------------------------------------------------------- 1 | .ui-multiselect-hasfilter ul { position:relative; top:2px } 2 | .ui-multiselect-filter { float:left; margin-right:10px; font-size:1em; width:100%; } 3 | .ui-multiselect-header .ui-multiselect-filter input { width:100px; font-size:.9em; margin-left:5px; height:15px; padding:2px; border:1px solid #292929; -webkit-appearance:textfield; -webkit-box-sizing:content-box; } 4 | .ui-multiselect-excluded {display: none} 5 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Jekyll! 2 | # 3 | # This config file is meant for settings that affect your whole blog, values 4 | # which you are expected to set up once and rarely edit after that. If you find 5 | # yourself editing this file very often, consider using Jekyll's data files 6 | # feature for the data you need to update frequently. 7 | # 8 | # For technical reasons, this file is *NOT* reloaded automatically when you use 9 | # 'bundle exec jekyll serve'. If you change this file, please restart the server process. 10 | 11 | # Site settings 12 | # These are used to personalize your new site. If you look in the HTML files, 13 | # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. 14 | # You can create any custom variable you would like, and they will be accessible 15 | # in the templates via {{ site.myvariable }}. 16 | title: jQuery Multiselect Widget 17 | email: osiris2918@gmail.com 18 | description: Documentation site for jquery-ui-multiselect-widget. A widget for jQuery UI. 19 | 20 | baseurl: "" # the subpath of your site, e.g. /blog 21 | url: "https://ehynds.github.io/jquery-ui-multiselect-widget" # the base hostname & protocol for your site, e.g. http://example.com 22 | github_username: mlh758 23 | repository: "https://github.com/ehynds/jquery-ui-multiselect-widget/" 24 | # Build settings 25 | markdown: kramdown 26 | theme: minima 27 | # Exclude from processing. 28 | # The following items will not be processed, by default. Create a custom list 29 | # to override the default setting. 30 | exclude: 31 | - Gemfile 32 | - Gemfile.lock 33 | - node_modules 34 | - vendor/bundle/ 35 | - vendor/cache/ 36 | - vendor/gems/ 37 | - vendor/ruby/ 38 | - src/assets/bundle.js 39 | -------------------------------------------------------------------------------- /docs/_includes/demo-styles.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_includes/jquery-ui.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | {% include jquery-ui.html %} 5 | 6 | 7 | 8 | 9 | {{ content }} 10 | 11 | 23 | 24 | -------------------------------------------------------------------------------- /docs/animations.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 19 | 20 | 21 |Using animations with the openEffect and closeEffect parameters. Either pass an array with the effect name and the speed, an object of animation parameters, or just specify the name of an effect. If you don't specify a speed, the default of 400ms will be used.
23 | 24 |26 | $("#test-1").multiselect({ 27 | openEffect: ["bounce", 200], 28 | closeEffect: ["explode", 1000] 29 | }); 30 |31 | 38 | 39 |
41 | $("#test-2").multiselect({ 42 | openEffect: "bounce", 43 | closeEffect: "explode" 44 | }); 45 |46 | 53 | 54 | -------------------------------------------------------------------------------- /docs/basic.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 9 |
Both multiselects are created with the following one-liner. Optgroup support is built in out of the box:
12 |13 | $(function(){ 14 | $("select").multiselect(); 15 | }); 16 |17 | 18 |
20 | 34 |
35 | 36 |Click on an optgroup's heading to toggle the checked state of the entire group.
38 |39 | 52 |
53 | -------------------------------------------------------------------------------- /docs/callbacks.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 49 |Demonstrating beforeopen, open, beforeclose, close, click, checkall, uncheckall, and optgrouptoggle callbacks/events. Note that you can either
51 | pass in an event handler in the options object upon initialization, or bind to the event (including the multiselect prefix). For example, to add an
52 | "open" handler, you can also use $("select").bind("multiselectopen", fn);
70 | var $callback = $("#callback"); 71 | 72 | $("select").multiselect({ 73 | click: function(event, ui){ 74 | $callback.text(ui.value + ' ' + (ui.checked ? 'checked' : 'unchecked') ); 75 | }, 76 | beforeopen: function(){ 77 | $callback.text("Select about to be opened..."); 78 | }, 79 | open: function(){ 80 | $callback.text("Select opened!"); 81 | }, 82 | beforeclose: function(){ 83 | $callback.text("Select about to be closed..."); 84 | }, 85 | close: function(){ 86 | $callback.text("Select closed!"); 87 | }, 88 | checkAll: function(){ 89 | $callback.text("Check all clicked!"); 90 | }, 91 | uncheckAll: function(){ 92 | $callback.text("Uncheck all clicked!"); 93 | }, 94 | optgrouptoggle: function(event, ui){ 95 | var values = $.map(ui.inputs, function(checkbox){ 96 | return checkbox.value; 97 | }).join(", "); 98 | 99 | $callback.html("Checkboxes " + (ui.checked ? "checked" : "unchecked") + ": " + values); 100 | }, 101 | groupsCollapsable: true, 102 | beforecollapsetoggle: function(){ 103 | $callback.text("Option group about to be collapsed/expanded..."); 104 | }, 105 | collapsetoggle: function(){ 106 | $callback.text("Option group collapsed/expanded"); 107 | } 108 | }); 109 |110 | -------------------------------------------------------------------------------- /docs/css/jquery.multiselect.css: -------------------------------------------------------------------------------- 1 | .ui-multiselect {box-sizing: border-box; padding:2px 0 2px 4px; text-align:left; width: auto;} 2 | .ui-multiselect .ui-multiselect-open { float:right } 3 | 4 | .ui-multiselect-menu { display:none; box-sizing:border-box; position:absolute; text-align:left; z-index: 101; width:auto; height:auto; padding:3px; } 5 | .ui-multiselect-menu.ui-multiselect-listbox {position:relative; z-index: 0;} 6 | 7 | .ui-multiselect-header { display:block; box-sizing:border-box; position:relative; width:auto; padding:3px 0 3px 4px; margin-bottom:2px;} 8 | .ui-multiselect-header > ul { font-size:0.9em } 9 | .ui-multiselect-header li { float:left; margin:0 10px 0 0;} 10 | .ui-multiselect-header a { text-decoration:none; } 11 | .ui-multiselect-header a:hover { text-decoration:underline; cursor: pointer;} 12 | .ui-multiselect-header .ui-icon { float:left; } 13 | .ui-multiselect-header .ui-multiselect-close { float:right; margin-right:0; text-align:right; } 14 | 15 | .ui-multiselect-checkboxes { display:block; box-sizing:border-box; position:relative; overflow:auto; width: auto; border: 0; padding: 4px 0 8px;} 16 | .ui-multiselect-checkboxes li:not(.ui-multiselect-optgroup) { clear:both; font-size:0.9em; list-style: none; padding-right:3px;} 17 | .ui-multiselect-checkboxes label { border:1px solid transparent; cursor:default; display:block; padding:3px 1px 3px 21px; text-indent: -20px;} 18 | .ui-multiselect-checkboxes input { position:relative; top:1px; cursor: pointer;} 19 | .ui-multiselect-checkboxes img { height: 30px; vertical-align: middle; margin-right: 3px;} 20 | .ui-multiselect-grouplabel { border-bottom:1px solid; display:block; font-weight:bold; margin:1px 0; padding:3px; text-align:center; text-decoration:none; } 21 | .ui-multiselect-selectable { cursor: pointer; } 22 | .ui-multiselect-optgroup > ul { padding: 3px; } 23 | .ui-multiselect-columns { display: inline-block; vertical-align: top; } 24 | .ui-multiselect-collapser { float: left; padding: 0 1px; margin: 0; } 25 | .ui-multiselect-collapsed > ul { display:none } 26 | 27 | .ui-multiselect-single .ui-multiselect-checkboxes input { left:-9999px; position:absolute !important; top: auto !important; } 28 | .ui-multiselect-single .ui-multiselect-checkboxes label { padding:5px !important; text-indent: 0 !important; } 29 | 30 | .ui-multiselect.ui-multiselect-nowrap { white-space: nowrap } 31 | .ui-multiselect.ui-multiselect-nowrap > span { display: inline-block } 32 | .ui-multiselect-checkboxes.ui-multiselect-nowrap li, 33 | .ui-multiselect-checkboxes.ui-multiselect-nowrap a { white-space: nowrap } 34 | 35 | .ui-multiselect-measure > .ui-multiselect-header, 36 | .ui-multiselect-measure > .ui-multiselect-checkboxes { float: left; } 37 | .ui-multiselect-measure > .ui-multiselect-checkboxes { margin: 4px; overflow-y: scroll; } 38 | 39 | .ui-multiselect-resize { border: 2px dotted #00F } 40 | 41 | @media print{ 42 | .ui-multiselect-menu {display: none;} 43 | } 44 | -------------------------------------------------------------------------------- /docs/css/jquery.multiselect.filter.css: -------------------------------------------------------------------------------- 1 | .ui-multiselect-hasfilter ul { position:relative; top:2px } 2 | .ui-multiselect-filter { float:left; margin-right:10px; font-size:1em; width:100%; } 3 | .ui-multiselect-header .ui-multiselect-filter input { width:100px; font-size:.9em; margin-left:5px; height:15px; padding:2px; border:1px solid #292929; -webkit-appearance:textfield; -webkit-box-sizing:content-box; } 4 | .ui-multiselect-excluded {display: none} 5 | -------------------------------------------------------------------------------- /docs/css/prettify.css: -------------------------------------------------------------------------------- 1 | /* Pretty printing styles. Used with prettify.js. */ 2 | 3 | .str { color: #080; } 4 | .kwd { color: #008; } 5 | .com { color: #800; } 6 | .typ { color: #606; } 7 | .lit { color: #066; } 8 | .pun { color: #660; } 9 | .pln { color: #000; } 10 | .tag { color: #008; } 11 | .atn { color: #606; } 12 | .atv { color: #080; } 13 | .dec { color: #606; } 14 | pre.prettyprint { padding: 5px; border:1px solid #d2d2d2; background:#f5f5f5 } 15 | 16 | @media print { 17 | .str { color: #060; } 18 | .kwd { color: #006; font-weight: bold; } 19 | .com { color: #600; font-style: italic; } 20 | .typ { color: #404; font-weight: bold; } 21 | .lit { color: #044; } 22 | .pun { color: #440; } 23 | .pln { color: #000; } 24 | .tag { color: #006; font-weight: bold; } 25 | .atn { color: #404; } 26 | .atv { color: #060; } 27 | } -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | body { font:12px Helvetica, arial, sans-serif } 2 | h1, h2, p { margin:10px 0 } 3 | .hidden { visibility:hidden } 4 | 5 | 6 | .message { padding:10px; margin:15px 0; display:block; text-align:left } 7 | .message-title { font-weight:bold; font-size:1.25em } 8 | .message-body { margin-top:4px } 9 | .error, .notice, .success { padding:.8em; margin-bottom:1em; border:2px solid #ddd } 10 | .error { background:#FBE3E4; color:#8a1f11; border-color:#FBC2C4 } 11 | .notice { background:#FFF6BF; color:#514721; border-color:#FFD324 } 12 | .success { background:#E6EFC2; color:#264409; border-color:#C6D880 } 13 | .error a { color:#8a1f11 } 14 | .notice a { color:#514721 } 15 | .success a { color:#264409 } 16 | 17 | -------------------------------------------------------------------------------- /docs/enabledisable.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 15 | 16 |
Programmatically calling the disable and enable methods.
18 |19 | var $widget = $("select").multiselect(), 20 | state = true; 21 | 22 | $("#toggle-disabled").click(function(){ 23 | state = !state; 24 | $widget.multiselect(state ? 'disable' : 'enable'); 25 | }); 26 |27 | 28 | 29 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/filter.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |
Filtering is available by including the jquery.multiselect.filter.js plugin and the jquery.multiselect.filter.css CSS file. Initialize filtering on any of your multiselects by calling multiselectfilter()
on the widget.
9 | $("select").multiselect().multiselectfilter(); 10 |11 | 12 | 42 | 43 |
Pass any of these as a configuration object when you initialize multiselectfilter()
:
label
The text to appear left of the input. Defaults to "Filter:"
width
The width of the input in pixels. Defaults to 100px in the style sheet, but you can override this for each instance.
placeholder
The HTML5 placeholder attribute value of the input. Only supported in webkit as of this writing. Defaults to "Enter keywords"
autoReset
A boolean value denoting whether or not to reset the search box & any filtered options when the widget closes. Defaults to false.
filter
56 | A callback function that fires after filtering is complete. Accepts two arguments: the original event and an array of matches.
57 | 58 |To do something when no matches are found:
59 | 60 |61 | $("select").multiselect().multiselectfilter({ 62 | filter: function(event, matches){ 63 | if( !matches.length ){ 64 | // do something 65 | } 66 | } 67 | }); 68 |69 | 70 |
To do something with a match:
71 | 72 |73 | $("select").multiselect().multiselectfilter({ 74 | filter: function(event, matches){ 75 | 76 | // find the first matching checkbox 77 | var first_match = $( matches[0] ); 78 | } 79 | }); 80 |81 | 82 |
You can also bind to the event after a multiselect has been initialized, like such: 83 | 84 |
85 | $("select").bind("multiselectfilterfilter", function(event, matches){ 86 | // do something 87 | }); 88 |89 | 90 |
Syntax: $("select").multiselectfilter("method_name");
updateCache
Reloads the cache of values to search against. Make sure you call this after dynamically adding or removing any inputs to the multiselect.
destroy
Destroys the widget.
widget
Returns the wrapper div with the input and label text inside. This is a quick and easy way to access the HTML created by the plugin.
The header option can be used in three ways:
27 | 28 | 62 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | {% include demo-styles.html %} 5 |9 | Download 10 | | 11 | npm 12 |
13 | 14 | 52 | 53 |Logic to impose a maximum number of checked inputs (two in this demo).
26 | 27 | 28 | 29 | 40 | 41 |42 | var warning = $(".message"); 43 | 44 | $("select").multiselect({ 45 | header: "Choose only TWO items!", 46 | click: function(e){ 47 | if( $(this).multiselect("widget").find("input:checked").length > 2 ){ 48 | warning.addClass("error").removeClass("success").html("You can only check two checkboxes!"); 49 | return false; 50 | } else { 51 | warning.addClass("success").removeClass("error").html("Check a few boxes."); 52 | } 53 | } 54 | }); 55 |56 | -------------------------------------------------------------------------------- /docs/position.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 25 |
Using the jQuery UI position utility to position the menu above the input. This is especially useful when you want collision detection with the bottom of the window, and automatically open the menu above the button.
28 |Please use jQuery 1.4.3 with the positioning option. Bugs in jQuery 1.4.2 prevent the menu from being positioned correctly the first time multiselect is opened in Webkit/IE.
29 | 30 | 31 | 32 | 72 | -------------------------------------------------------------------------------- /docs/preselected.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |Options one, three, and four have the selected="selected"
attribute and are checked by default. Options five and six have the disabled="disabled"
attribute.
7 | When the widget is initialized on the select, both attribute types are honored.
Calling refresh
allows you to re-build the multiselect menu from your original select. This is useful
33 | when the contents of the select box changes dynamically (through either AJAX, DOM manipulation, etc.) and you want the
34 | multiselect widget to reflect the changes.
37 | // once the "add" button is clicked below: 38 | $("select").multiselect('refresh'); 39 |40 | 41 |
Type in the text of a new option tag to add dynamically.
44 | 45 | 46 |47 | 48 | 49 |
50 |selectedList
ParameterThe selectedList parameter is a boolean/numeric value denoting whether or not to display the checked opens in a list, and how many. A number greater than 0 denotes the maximum number of list items to display before switching over to the selectedText parameter.
18 |19 | $("select").multiselect({ 20 | selectedList: 4 // 0-based index 21 | }); 22 |23 | 36 | 37 |
selectedText
Passing a function to the selectedText
option gives you low-level control of what the widget displays. The function receives three arguments:
39 | the number of checkboxes checked, the total number of checkboxes, and an array of the checkboxes (DOM elements) that were checked. Example usage:
42 | $("select").multiselect({ 43 | selectedText: function(numChecked, numTotal, checkedItems){ 44 | return numChecked + ' of ' + numTotal + ' checked'; 45 | } 46 | }); 47 |48 | 49 |
The selectedList
option is simply a convenience method for the selectedText
option.
Omitting the multiple
attribute from the underlying native select force the widget to use radio buttons instead of checkboxes. It is a good idea to use the header
19 | parameter in order to hide/change the "check all"/"uncheck all" links.
22 | $("select").multiselect({ 23 | header: "=Select an option", 24 | noneSelectedText: "Select an Option", 25 | selectedList: 1 26 | }); 27 |28 | 29 | 42 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.br.js: -------------------------------------------------------------------------------- 1 | /* Brazilian initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Vinícius Fontoura Corrêa (vinusfc@gmail.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselect.prototype.options, { 7 | linkInfo: { 8 | checkAll: {text: 'Marcar todos', title: 'Marcar todos'}, 9 | uncheckAll: {text: 'Desmarcar todos', title: 'Desmarcar todos'} 10 | }, 11 | noneSelectedText: 'Selecione as opções', 12 | selectedText: '# selecionado' 13 | }); 14 | 15 | })( jQuery ); 16 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.cs.js: -------------------------------------------------------------------------------- 1 | /* Czech initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Michi (michi.m@gmail.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselect.prototype.options, { 7 | linkInfo: { 8 | checkAll: {text: 'Vybrat vše', title: 'Vybrat vše'}, 9 | uncheckAll: {text: 'Zrušit výběr', title: 'Zrušit výběr'} 10 | }, 11 | noneSelectedText: 'Nic není vybráno', 12 | selectedText: '# vybráno' 13 | }); 14 | 15 | })( jQuery ); 16 | 17 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.de.js: -------------------------------------------------------------------------------- 1 | /* German initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Sven Tatter (sven.tatter@gmail.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselect.prototype.options, { 7 | linkInfo: { 8 | checkAll: {text: 'Alle auswählen', title: 'Alle auswählen'}, 9 | uncheckAll: {text: 'Alle abwählen', title: 'Alle abwählen'} 10 | }, 11 | noneSelectedText: 'Nichts ausgewählt', 12 | selectedText: '# ausgewählt' 13 | }); 14 | 15 | })( jQuery ); 16 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.es.js: -------------------------------------------------------------------------------- 1 | /* Spanish initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Vinius Fontoura Correa(vinusfc@gmail.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselect.prototype.options, { 7 | linkInfo: { 8 | checkAll: {text: 'Marca todas', title: 'Marca todas'}, 9 | uncheckAll: {text: 'Desmarque todas', title: 'Desmarque todas'} 10 | }, 11 | noneSelectedText: 'Seleccione las opciones', 12 | selectedText: '# seleccionado' 13 | }); 14 | 15 | })( jQuery ); 16 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.filter.br.js: -------------------------------------------------------------------------------- 1 | /* Brazilian initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Vinícius Fontoura Corrêa (vinusfc@gmail.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselectfilter.prototype.options, { 7 | label: "Filtro:", 8 | placeholder: "Entre com a palavra" 9 | }); 10 | 11 | })( jQuery ); 12 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.filter.cs.js: -------------------------------------------------------------------------------- 1 | /* Czech initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Michi (michi.m@gmail.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselectfilter.prototype.options, { 7 | label: "Filtrovat:", 8 | placeholder: "Napište výraz" 9 | }); 10 | 11 | })( jQuery ); 12 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.filter.de.js: -------------------------------------------------------------------------------- 1 | /* German initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Sven Tatter (sven.tatter@gmail.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselectfilter.prototype.options, { 7 | label: "Suchen:", 8 | placeholder: "Stichwort eingeben" 9 | }); 10 | 11 | })( jQuery ); 12 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.filter.es.js: -------------------------------------------------------------------------------- 1 | /* Spanish initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Vinícius Fontoura Corrêa (vinusfc@gmail.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselectfilter.prototype.options, { 7 | label: "Filtro:", 8 | placeholder: "Introduzca una palabra" 9 | }); 10 | 11 | })( jQuery ); 12 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.filter.fr.js: -------------------------------------------------------------------------------- 1 | /* French initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Charles SANQUER (charles.sanquer@spyrit.net). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselectfilter.prototype.options, { 7 | label: "Filtre:", 8 | placeholder: "Entrer un mot clé" 9 | }); 10 | 11 | })( jQuery ); 12 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.filter.hu.js: -------------------------------------------------------------------------------- 1 | /* Hungarian initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Adam Fónagy (adam.fonagy@greenformatics.hu). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselectfilter.prototype.options, { 7 | label: "Keresés:", 8 | placeholder: "kulcsszó megadása" 9 | }); 10 | 11 | })( jQuery ); 12 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.filter.it.js: -------------------------------------------------------------------------------- 1 | /* Italian initialization for the jQuery UI multiselect plugin. */ 2 | /* Written by Vincenzo Farruggia(mastropinguino@networky.net). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselectfilter.prototype.options, { 7 | label: "Filtro:", 8 | placeholder: "Digita una parola chiave" 9 | }); 10 | 11 | })( jQuery ); 12 | 13 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.filter.ja.js: -------------------------------------------------------------------------------- 1 | /* Japanese initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Daisuke (daisuketaniwaki@gmail.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselectfilter.prototype.options, { 7 | label: '絞込み:', 8 | placeholder: 'キーワードを入力してください' 9 | }); 10 | 11 | })( jQuery ); 12 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.filter.pl.js: -------------------------------------------------------------------------------- 1 | /* Polish initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Tomasz Mazur (contact@tomaszmazur.eu). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselectfilter.prototype.options, { 7 | label: "Filtruj:", 8 | placeholder: "Wprowadź słowa kluczowe" 9 | }); 10 | 11 | })( jQuery ); 12 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.filter.ru.js: -------------------------------------------------------------------------------- 1 | /* Russian initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Artem Packhomov (gorblnu4@gmail.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselectfilter.prototype.options, { 7 | label: "Фильтр:", 8 | placeholder: "Введите запрос" 9 | }); 10 | 11 | })( jQuery ); 12 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.filter.tr.js: -------------------------------------------------------------------------------- 1 | /* Brazilian initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Yusuf Özer (realsby@gmail.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselectfilter.prototype.options, { 7 | label: "Filtre:", 8 | placeholder: "Bir kelime yazın" 9 | }); 10 | 11 | })( jQuery ); 12 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.filter.zh-cn.js: -------------------------------------------------------------------------------- 1 | /* Simplified Chinese initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Ben (ben@zfben.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselectfilter.prototype.options, { 7 | label: '过滤:', 8 | placeholder: '输入关键字过滤' 9 | }); 10 | 11 | })( jQuery ); 12 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.filter.zh-tw.js: -------------------------------------------------------------------------------- 1 | /* Simplified Chinese initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Ben (ben@zfben.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselectfilter.prototype.options, { 7 | label: '過濾:', 8 | placeholder: '輸入關鍵字過濾' 9 | }); 10 | 11 | })( jQuery ); 12 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.fr.js: -------------------------------------------------------------------------------- 1 | /* French initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Charles SANQUER (charles.sanquer@spyrit.net). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselect.prototype.options, { 7 | linkInfo: { 8 | checkAll: {text: 'Tout cocher', title: 'Tout cocher'}, 9 | uncheckAll: {text: 'Tout décocher', title: 'Tout décocher'} 10 | }, 11 | noneSelectedText: 'Sélectionner les options', 12 | selectedText: '# sélectionnés' 13 | }); 14 | 15 | })( jQuery ); 16 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.hu.js: -------------------------------------------------------------------------------- 1 | /* Hungarian initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Adam Fónagy (adam.fonagy@greenformatics.hu). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselect.prototype.options, { 7 | linkInfo: { 8 | checkAll: {text: 'Mind kijelöl', title: 'Mind kijelöl'}, 9 | uncheckAll: {text: 'Mind eltávolít', title: 'Mind eltávolít'} 10 | }, 11 | noneSelectedText: 'Nincs kijelölés', 12 | selectedText: '# kijelölve' 13 | }); 14 | 15 | })( jQuery ); 16 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.it.js: -------------------------------------------------------------------------------- 1 | /* Italian initialization for the jQuery UI multiselect plugin. */ 2 | /* Written by Vincenzo Farruggia(mastropinguino@networky.net). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselect.prototype.options, { 7 | linkInfo: { 8 | checkAll: {text: 'Seleziona tutto', title: 'Seleziona tutto'}, 9 | uncheckAll: {text: 'Deseleziona tutto', title: 'Deseleziona tutto'} 10 | }, 11 | noneSelectedText: 'Seleziona le opzioni', 12 | selectedText: '# selezionati' 13 | }); 14 | 15 | })( jQuery ); 16 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.ja.js: -------------------------------------------------------------------------------- 1 | /* Japanese initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Daisuke (daisuketaniwaki@gmail.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselect.prototype.options, { 7 | linkInfo: { 8 | checkAll: {text: 'すべて選択', title: 'すべて選択'}, 9 | uncheckAll: {text: '選択解除', title: '選択解除'} 10 | }, 11 | noneSelectedText: '選択してください', 12 | selectedText: '#つ選択中' 13 | }); 14 | 15 | })( jQuery ); 16 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.pl.js: -------------------------------------------------------------------------------- 1 | /* Polish initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Tomasz Mazur (contact@tomaszmazur.eu). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselect.prototype.options, { 7 | linkInfo: { 8 | checkAll: {text: 'Zaznacz wszystkie', title: 'Zaznacz wszystkie'}, 9 | uncheckAll: {text: 'Odznacz wszystkie', title: 'Odznacz wszystkie'} 10 | }, 11 | noneSelectedText: 'Wybierz opcje', 12 | selectedText: 'Zaznaczono #' 13 | }); 14 | 15 | })( jQuery ); 16 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.ru.js: -------------------------------------------------------------------------------- 1 | /* Russian initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Artem Packhomov (gorblnu4@gmail.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselect.prototype.options, { 7 | linkInfo: { 8 | checkAll: {text: 'Отметить все', title: 'Отметить все'}, 9 | uncheckAll: {text: 'Снять отметку со всех', title: 'Снять отметку со всех'} 10 | }, 11 | noneSelectedText: 'Выберите из списка', 12 | selectedText: 'Выбрано #' 13 | }); 14 | 15 | })( jQuery ); 16 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.tr.js: -------------------------------------------------------------------------------- 1 | /* Turkish initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Yusuf Özer (realsby@gmail.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselect.prototype.options, { 7 | linkInfo: { 8 | checkAll: {text: 'Tümünü seç', title: 'Tümünü seç'}, 9 | uncheckAll: {text: 'Tümünü sil', title: 'Tümünü sil'} 10 | }, 11 | noneSelectedText: 'Seçenekleri belirleyin', 12 | selectedText: '# adet seçildi' 13 | }); 14 | 15 | })( jQuery ); 16 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.zh-cn.js: -------------------------------------------------------------------------------- 1 | /* Simplified Chinese initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Ben (ben@zfben.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselect.prototype.options, { 7 | linkInfo: { 8 | checkAll: {text: '全选', title: '全选'}, 9 | uncheckAll: {text: '清空', title: '清空'} 10 | }, 11 | noneSelectedText: '请选择', 12 | selectedText: '# 已选择' 13 | }); 14 | 15 | })( jQuery ); 16 | -------------------------------------------------------------------------------- /i18n/jquery.multiselect.zh-tw.js: -------------------------------------------------------------------------------- 1 | /* Simplified Chinese initialisation for the jQuery UI multiselect plugin. */ 2 | /* Written by Ben (ben@zfben.com). */ 3 | 4 | (function ( $ ) { 5 | 6 | $.extend($.ech.multiselect.prototype.options, { 7 | linkInfo: { 8 | checkAll: {text: '全選', title: '全選'}, 9 | uncheckAll: {text: '清空', title: '清空'} 10 | }, 11 | noneSelectedText: '請選擇', 12 | selectedText: '# 已選擇' 13 | }); 14 | 15 | })( jQuery ); 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-ui-multiselect-widget", 3 | "description": "MultiSelect progessively enhances an ordinary multiple select control into elegant drop down list of checkboxes, stylable with ThemeRoller.", 4 | "version": "3.0.1", 5 | "license": "MIT or GPL-2.0", 6 | "author": "Eric Hynds", 7 | "contributors": [ 8 | { 9 | "name": "Micheal" 10 | }, 11 | { 12 | "name": "AB Zainuddin", 13 | "email": "burhan@codeyellow.nl" 14 | }, 15 | { 16 | "name": "Steve James" 17 | } 18 | ], 19 | "scripts": { 20 | "test": "node-qunit-phantomjs ./tests/unit/index.htm", 21 | "update-docs": "uglifyjs src/jquery.multiselect.js src/jquery.multiselect.filter.js > docs/js/widget.min.js" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/ehynds/jquery-ui-multiselect-widget" 26 | }, 27 | "main": "src/jquery.multiselect.js", 28 | "dependencies": { 29 | "jquery": ">=1.8.0", 30 | "jquery-ui": "^1.11.0" 31 | }, 32 | "devDependencies": { 33 | "eslint": "^6.0.1", 34 | "eslint-config-google": "^0.9.1", 35 | "node-qunit-phantomjs": "^2.0.0", 36 | "uglifyjs": "^2.4.11" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/jquery.multiselect.filter.js: -------------------------------------------------------------------------------- 1 | /* jshint forin:true, noarg:true, noempty:true, eqeqeq:true, boss:true, undef:true, curly:true, browser:true, jquery:true */ 2 | /* 3 | * jQuery MultiSelect UI Widget Filtering Plugin 3.0.0 4 | * Copyright (c) 2012 Eric Hynds 5 | * 6 | * http://www.erichynds.com/jquery/jquery-ui-multiselect-widget/ 7 | * 8 | * Depends: 9 | * - jQuery UI MultiSelect widget 10 | * 11 | * Dual licensed under the MIT and GPL licenses: 12 | * http://www.opensource.org/licenses/mit-license.php 13 | * http://www.gnu.org/licenses/gpl.html 14 | * 15 | */ 16 | (function($) { 17 | var rEscape = /[\-\[\]{}()*+?.,\\\^$|#\s]/g; 18 | 19 | // "{{term}}" is a placeholder below for where the search term 20 | // would be inserted in the resulting regular expression. 21 | var filterRules = { 22 | contains: '{{term}}', 23 | beginsWith: '^{{term}}', 24 | endsWith: '{{term}}$', 25 | exactMatch: '^{{term}}$', 26 | containsNumber: '\d', 27 | isNumeric: '^\d+$', 28 | isNonNumeric: '^\D+$', 29 | }; 30 | 31 | var headerSelector = '.ui-multiselect-header'; 32 | var hasFilterClass = 'ui-multiselect-hasfilter'; 33 | var filterClass = 'ui-multiselect-filter'; 34 | var optgroupClass = 'ui-multiselect-optgroup'; 35 | var groupLabelClass = 'ui-multiselect-grouplabel'; 36 | var hiddenClass = 'ui-multiselect-excluded'; 37 | 38 | /** 39 | * This comes courtesy of underscore.js 40 | * @param {function} func to debounce 41 | * @param {number} wait period 42 | * @param {bool} immediate perform once immediately 43 | * @return {function} input function with a debounce period 44 | */ 45 | function debounce(func, wait, immediate) { 46 | var timeout; 47 | return function() { 48 | var context = this; var args = arguments; // eslint-disable-line prefer-rest-params 49 | var later = function() { 50 | timeout = null; 51 | if (!immediate) { 52 | func.apply(context, args); 53 | } 54 | }; 55 | var callNow = immediate && !timeout; 56 | clearTimeout(timeout); 57 | timeout = setTimeout(later, wait); 58 | if (callNow) { 59 | func.apply(context, args); 60 | } 61 | }; 62 | } 63 | 64 | $.widget('ech.multiselectfilter', { 65 | 66 | options: { 67 | label: 'Filter:', // (string) The label to show with the input 68 | placeholder: 'Enter keywords', // (string) The placeholder text to show in the input 69 | filterRule: 'contains', // (string) Either a named filter rule from above or a regular expression containing {{term}} as a placeholder 70 | searchGroups: false, // (true | false) If true, search option group labels and show an entire group on a match. 71 | autoReset: false, // (true | false) If true, clear the filter each time the widget menu is closed. 72 | width: null, // (number) Override default width set in css file (px). null will inherit 73 | debounceMS: 250, // (number) Number of milleseconds to wait between running the search handler. 74 | }, 75 | 76 | /** 77 | * Performs widget creation 78 | * Widget API has already set this.element and this.options for us 79 | * - Find the multiselect widget. 80 | * - Create the filter input 81 | * - Set up event handlers 82 | * - Insert in header 83 | * - Create text cache 84 | * - Override toggleState 85 | */ 86 | _create: function() { 87 | var opts = this.options; 88 | var $element = this.element; 89 | 90 | // get the multiselect instance 91 | this.instance = $element.data('ech-multiselect'); 92 | 93 | // store header; add filter class so the close/check all/uncheck all links can be positioned correctly 94 | this.$header = this.instance.$menu.find(headerSelector).addClass(hasFilterClass); 95 | 96 | // wrapper $element 97 | this.$input = $(document.createElement('input')) 98 | .attr({ 99 | placeholder: opts.placeholder, 100 | type: 'search', 101 | }) 102 | .css({width: (typeof opts.width === 'string') 103 | ? this.instance._parse2px(opts.width, this.$header).px + 'px' 104 | : (/\d/.test(opts.width) ? opts.width + 'px' : null), 105 | }); 106 | this._bindInputEvents(); 107 | // automatically reset the widget on close? 108 | if (this.options.autoReset) { 109 | $element.on('multiselectbeforeclose', $.proxy(this._reset, this)); 110 | } 111 | 112 | var $label = $(document.createElement('label')).text(opts.label).append(this.$input).addClass('ui-multiselect-filter-label'); 113 | this.$wrapper = $(document.createElement('div')) 114 | .addClass(filterClass) 115 | .append($label) 116 | .prependTo(this.$header); 117 | 118 | // If menu already opened, have to reset menu height since 119 | // addition of the filter input changes the header height calc. 120 | if (!!this.instance._isOpen) { 121 | this.instance._setMenuHeight(true); 122 | } 123 | 124 | // cache input values for searching 125 | this.updateCache(); 126 | 127 | // Change the normal _toggleChecked fxn behavior so that when checkAll/uncheckAll 128 | // is fired, only the currently displayed filtered inputs are checked if filter entered. 129 | var instance = this.instance; 130 | var filter = this.$input[0]; 131 | instance._oldToggleChecked = instance._toggleChecked; 132 | instance._toggleChecked = function(flag, group) { 133 | instance._oldToggleChecked(flag, group, !!filter.value); 134 | }; 135 | }, 136 | 137 | /** 138 | * Binds keyboard events to the input 139 | * This is where special behavior like ALT-R for reset is bound 140 | */ 141 | _bindInputEvents: function() { 142 | var $element = this.element; 143 | 144 | this.$input.on({ 145 | keydown: function(e) { 146 | // prevent the enter key from submitting the form / closing the widget 147 | if (e.which === 13) { 148 | e.preventDefault(); 149 | } else if (e.which === 27) { 150 | $element.multiselect('close'); 151 | e.preventDefault(); 152 | } else if (e.which === 9 && e.shiftKey) { 153 | $element.multiselect('close'); 154 | e.preventDefault(); 155 | } else if (e.altKey) { 156 | switch (e.which) { 157 | case 82: 158 | e.preventDefault(); 159 | $(this).val('').trigger('input', ''); 160 | break; 161 | case 65: 162 | $element.multiselect('checkAll'); 163 | break; 164 | case 85: 165 | $element.multiselect('uncheckAll'); 166 | break; 167 | case 70: 168 | $element.multiselect('flipAll'); 169 | break; 170 | case 76: 171 | $element.multiselect('instance').$labels.first().trigger('mouseenter'); 172 | break; 173 | } 174 | } 175 | }, 176 | input: $.proxy(debounce(this._handler, this.options.debounceMS), this), 177 | search: $.proxy(this._handler, this), 178 | }); 179 | }, 180 | 181 | /** 182 | * Handles searches as text is entered in the filter box. 183 | * Uses a text cache to speed up searching. 184 | * Debouncing is done to limit how often this is ran. 185 | * Alternate filter rules can be used. 186 | * Option group labels may be searched, also. 187 | * @param {event} e event object from original event. 188 | */ 189 | _handler: function(e) { 190 | var term = this.$input[0].value.toLowerCase().replace(/^\s+|\s+$/g, ''); 191 | var filterRule = this.options.filterRule || 'contains'; 192 | var regex = new RegExp( ( filterRules[filterRule] || filterRule ).replace('{{term}}', term.replace(rEscape, '\\$&')), 'i'); 193 | var searchGroups = !!this.options.searchGroups; 194 | var $checkboxes = this.instance.$checkboxes; 195 | var cache = this.cache; // Cached text() object 196 | 197 | this.$rows.toggleClass(hiddenClass, !!term); 198 | var filteredInputs = $checkboxes.children().map(function(x) { 199 | var elem = this; 200 | var $groupItems = $(elem); 201 | var groupShown = false; 202 | 203 | // Account for optgroups 204 | // If we are searching in option group labels and we match an optgroup label, 205 | // then show all its children and return all its inputs also. 206 | if (elem.classList.contains(optgroupClass)) { 207 | var $groupItems = $groupItems.find('li'); 208 | if (searchGroups && regex.test( cache[x] ) ) { 209 | elem.classList.remove(hiddenClass); 210 | $groupItems.removeClass(hiddenClass); 211 | return $groupItems.find('input').get(); 212 | } 213 | } 214 | 215 | return $groupItems.map(function(y) { 216 | if ( regex.test( cache[x + '.' + y] ) ) { 217 | // Show the opt group heading if needed 218 | if (!groupShown) { 219 | elem.classList.remove(hiddenClass); 220 | groupShown = true; 221 | } 222 | this.classList.remove(hiddenClass); 223 | return this.getElementsByTagName('input')[0]; 224 | } 225 | return null; 226 | }); 227 | }); 228 | if (term) { 229 | this._trigger('filter', e, filteredInputs); 230 | } 231 | if (!this.instance.options.listbox && this.instance._isOpen) { 232 | this.instance._setMenuHeight(true); 233 | this.instance.position(); 234 | } 235 | return; 236 | }, 237 | 238 | _reset: function() { 239 | this.$input.val(''); 240 | var event = document.createEvent('Event'); 241 | event.initEvent('reset', true, true); 242 | this.$input.get(0).dispatchEvent(event); 243 | this._handler(event); 244 | }, 245 | 246 | /** 247 | * Creates a text cache object from the widget options' text. 248 | * @param {Boolean} alsoRefresh causes the displayed search results to refresh. 249 | */ 250 | updateCache: function(alsoRefresh) { 251 | var cache = {}; // keys are like 0, 0.1, 1, 1.0, 1.1 etc. 252 | this.instance.$checkboxes.children().each(function(x) { 253 | var $element = $(this); 254 | // Account for optgroups 255 | if (this.classList.contains(optgroupClass)) { 256 | // Single number keys are the option labels 257 | cache[x] = this.getElementsByClassName(groupLabelClass)[0].textContent; 258 | $element = $element.find('li'); 259 | } 260 | $element.each(function(y) { 261 | cache[x + '.' + y] = this.textContent; 262 | }); 263 | }); 264 | this.cache = cache; 265 | this.$rows = this.instance.$checkboxes.find('li'); 266 | if (!!alsoRefresh) { 267 | this._handler(); 268 | } 269 | }, 270 | 271 | /** 272 | * @return {object} Returns the input wrapper div 273 | */ 274 | widget: function() { 275 | return this.$wrapper; 276 | }, 277 | 278 | /** 279 | * Destroys this widget 280 | */ 281 | destroy: function() { 282 | $.Widget.prototype.destroy.call(this); 283 | this.$input.val('').trigger('keyup').off('keydown input search'); 284 | this.instance.$menu.find(headerSelector).removeClass(hasFilterClass); 285 | this.$wrapper.remove(); 286 | }, 287 | }); 288 | })(jQuery); 289 | -------------------------------------------------------------------------------- /tests/unit/core.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | var form, data; 3 | 4 | QUnit.module("core", { 5 | beforeEach: function () { 6 | form = $('').appendTo(body); 7 | data = null; 8 | }, 9 | afterEach: function () { 10 | form.remove(); 11 | } 12 | }); 13 | 14 | QUnit.test("init", function (assert) { 15 | el = $("select").multiselect(); 16 | var $header = header(); 17 | assert.ok($header.find('a.ui-multiselect-all').css('display') !== 'none', 'select all is visible'); 18 | assert.ok($header.find('a.ui-multiselect-none').css('display') !== 'none', 'select none is visible'); 19 | assert.ok($header.find('a.ui-multiselect-close').css('display') !== 'none', 'close link is visible'); 20 | assert.ok(menu().is(':hidden'), 'menu is hidden'); 21 | assert.ok(el.is(":hidden"), 'the original select is hidden'); 22 | assert.ok(el.attr('tabIndex') == 2, 'button inherited the correct tab index'); 23 | el.multiselect("destroy"); 24 | }); 25 | 26 | QUnit.test("name space separation", function (assert) { 27 | var el1 = $('') 28 | .appendTo(form) 29 | .multiselect(); 30 | 31 | var el2 = $('') 32 | .appendTo(form) 33 | .multiselect(); 34 | 35 | assert.notEqual(el1.multiselect('widget').find('input').eq(0).attr('id'), el2.multiselect('widget').find('input').eq(0).attr('id'), 'name spaces for multiple widgets are different'); 36 | 37 | el1.multiselect('destroy'); 38 | el2.multiselect('destroy'); 39 | }); 40 | 41 | QUnit.test("form submission", function (assert) { 42 | el = $('') 43 | .appendTo(form) 44 | .multiselect() 45 | .multiselect("checkAll"); 46 | 47 | data = form.serialize(); 48 | assert.equal(data, 'test=foo&test=bar', 'after checking all and serializing the form, the correct keys were serialized'); 49 | 50 | el.multiselect("uncheckAll"); 51 | data = form.serialize(); 52 | assert.equal(data.length, 0, 'after unchecking all and serializing the form, nothing was serialized'); 53 | 54 | // re-check all and destroy, exposing original select 55 | el.multiselect("checkAll").multiselect("destroy"); 56 | data = form.serialize(); 57 | assert.equal(data, 'test=foo&test=bar', 'after checking all, destroying the widget, and serializing the form, the correct keys were serialized'); 58 | }); 59 | 60 | QUnit.test("form submission, optgroups", function (assert) { 61 | el = $('') 62 | .appendTo(form) 63 | .multiselect() 64 | .multiselect("checkAll"); 65 | 66 | data = form.serialize(); 67 | assert.equal(data, 'test=foo&test=bar&test=baz&test=bax', 'after checking all and serializing the form, the correct keys were serialized'); 68 | 69 | el.multiselect("uncheckAll"); 70 | data = form.serialize(); 71 | assert.equal(data.length, 0, 'after unchecking all and serializing the form, nothing was serialized'); 72 | 73 | // re-check all and destroy, exposing original select 74 | el.multiselect("checkAll").multiselect("destroy"); 75 | data = form.serialize(); 76 | assert.equal(data, 'test=foo&test=bar&test=baz&test=bax', 'after checking all, destroying the widget, and serializing the form, the correct keys were serialized'); 77 | 78 | // reset option tags 79 | el.find("option").each(function () { 80 | this.selected = false; 81 | }); 82 | 83 | // test checking one option in both optgroups 84 | el.multiselect(); 85 | 86 | // finds the first input in each optgroup (assumes 2 options per optgroup) 87 | el.multiselect("widget").find('.ui-multiselect-checkboxes li:not(.ui-multiselect-optgroup-label) input:even').each(function (i) { 88 | this.click(); 89 | }); 90 | 91 | data = form.serialize(); 92 | assert.equal(data, 'test=foo&test=baz', 'after manually checking one input in each group, the correct two are serialized'); 93 | 94 | el.multiselect('destroy'); 95 | }); 96 | 97 | QUnit.test("form submission, single select", function (assert) { 98 | // Use an underlying single-select here. 99 | el = $('') 100 | .appendTo(form) 101 | .multiselect(); 102 | 103 | // select multiple radios to ensure that, in the underlying select, only one 104 | // will remain selected 105 | var radios = menu().find(":radio"); 106 | radios[0].click(); 107 | radios[2].click(); 108 | radios[1].click(); 109 | 110 | data = form.serialize(); 111 | assert.equal(data, 'test=bar', 'the form serializes correctly after clicking on multiple radio buttons'); 112 | assert.equal(radios.filter(":checked").length, 1, 'Only one radio button is selected'); 113 | 114 | // uncheckAll method 115 | el.multiselect("uncheckAll"); 116 | data = form.serialize(); 117 | assert.equal(data.length, 0, 'After unchecking all, nothing was serialized'); 118 | assert.equal(radios.filter(":checked").length, 0, 'No radio buttons are selected'); 119 | 120 | // checkAll method 121 | el.multiselect("checkAll"); 122 | data = form.serialize(); 123 | assert.equal(el.multiselect("getChecked").length, 1, 'After checkAll, only one radio is selected'); 124 | assert.equal(radios.filter(":checked").length, 1, 'One radio is selected'); 125 | 126 | // expose original 127 | el.multiselect("destroy"); 128 | data = form.serialize(); 129 | assert.equal(data, 'test=baz', 'after destroying the widget and serializing the form, the correct key was serialized: ' + data); 130 | }); 131 | 132 | QUnit.test("form reset, nothing pre-selected", function (assert) { 133 | var noneSelected = 'Please check something'; 134 | 135 | el = $('') 136 | .appendTo(form) 137 | .multiselect({ noneSelectedText: noneSelected, selectedList: 0 }) 138 | .multiselect("checkAll"); 139 | 140 | // trigger reset 141 | var done = assert.async(); 142 | form.trigger("reset"); 143 | 144 | setTimeout(function () { 145 | assert.equal(menu().find(":checked").length, 0, "no checked checkboxes"); 146 | assert.equal(button().text(), noneSelected, "none selected text"); 147 | el.multiselect('destroy'); 148 | done(); 149 | }, 10); 150 | }); 151 | 152 | QUnit.test("form reset, pre-selected options", function (assert) { 153 | el = $('') 154 | .appendTo(form) 155 | .multiselect({ selectedText: '# of # selected', selectedList: 0 }) 156 | .multiselect("uncheckAll"); 157 | 158 | // trigger reset 159 | var done = assert.async(); 160 | form.trigger("reset"); 161 | 162 | setTimeout(function () { 163 | assert.equal(menu().find(":checked").length, 2, "two checked checkboxes"); 164 | assert.equal(button().text(), "2 of 2 selected", "selected text"); 165 | el.multiselect('destroy'); 166 | done(); 167 | }, 10); 168 | }); 169 | 170 | })(jQuery); 171 | -------------------------------------------------------------------------------- /tests/unit/events.js: -------------------------------------------------------------------------------- 1 | (function($){ 2 | 3 | QUnit.module("events"); 4 | 5 | QUnit.test("multiselectopen", function(assert){ 6 | // inject widget 7 | el = $("").appendTo(body); 8 | el.multiselect({ 9 | open: function(e,ui){ 10 | assert.ok( true, 'option: multiselect("open") fires open callback' ); 11 | assert.equal(this, el[0], "option: context of callback"); 12 | assert.equal(e.type, 'multiselectopen', 'option: event type in callback'); 13 | assert.equal(menu().css("display"), 'block', 'menu display css property assert.equal block'); 14 | assert.propEqual(ui, {}, 'option: ui hash in callback'); 15 | } 16 | }) 17 | .on("multiselectopen", function(e,ui){ 18 | assert.ok(true, 'event: multiselect("open") fires multiselectopen event'); 19 | assert.equal(this, el[0], 'event: context of event'); 20 | assert.propEqual(ui, {}, 'event: ui hash'); 21 | }); 22 | 23 | // now try to open it.. 24 | el.multiselect("open"); 25 | 26 | // make sure the width of the menu and button are equivalent 27 | assert.equal( button().outerWidth(), menu().outerWidth(), 'button and menu widths are equivalent'); 28 | 29 | // close 30 | el.multiselect("close"); 31 | 32 | // make sure a click event on the button opens the menu as well. 33 | button().trigger("click"); 34 | el.multiselect("close"); 35 | 36 | // make sure a click event on a span inside the button opens the menu as well. 37 | button().find("span:first").trigger("click"); 38 | 39 | // reset for next test 40 | el.multiselect("destroy").remove(); 41 | 42 | // now try returning false prevent opening 43 | el = $("") 44 | .appendTo(body) 45 | .multiselect() 46 | .on("multiselectbeforeopen", function(){ 47 | assert.ok( true, "event: binding multiselectbeforeopen to return false (prevent from opening)" ); 48 | return false; 49 | }) 50 | .multiselect("open"); 51 | 52 | assert.ok( !el.multiselect("isOpen"), "multiselect is not open after multiselect('open')" ); 53 | el.multiselect("destroy").remove(); 54 | }); 55 | 56 | QUnit.test("multiselectclose", function(assert){ 57 | // inject widget 58 | el = $("").appendTo(body); 59 | el.multiselect({ 60 | close: function(e,ui){ 61 | assert.ok( true, 'option: multiselect("close") fires close callback' ); 62 | assert.equal(this, el[0], "option: context of callback"); 63 | assert.equal(e.type, 'multiselectclose', 'option: event type in callback'); 64 | assert.equal(menu().css("display"), 'none', 'menu display css property assert.equal none'); 65 | assert.propEqual(ui, {}, 'option: ui hash'); 66 | } 67 | }) 68 | .on("multiselectclose", function(e,ui){ 69 | assert.ok(true, 'multiselect("close") fires multiselectclose event'); 70 | assert.equal(this, el[0], 'event: context of event'); 71 | assert.propEqual(ui, {}, 'event: ui hash'); 72 | }) 73 | .multiselect("open") 74 | .multiselect("close") 75 | .multiselect("open"); 76 | 77 | // make sure a click event on the button closes the menu as well. 78 | button().click(); 79 | el.multiselect("open"); 80 | 81 | // make sure a click event on a span inside the button closes the menu as well. 82 | button().find("span:first").click(); 83 | 84 | // make sure that the menu is actually closed. see issue #68 85 | assert.ok( el.multiselect('isOpen') === false, 'menu is indeed closed' ); 86 | 87 | el.multiselect("destroy").remove(); 88 | }); 89 | 90 | QUnit.test("multiselectbeforeclose", function(assert){ 91 | // inject widget 92 | el = $("").appendTo(body); 93 | el.multiselect({ 94 | beforeclose: function(e,ui){ 95 | assert.ok( true, 'option: multiselect("beforeclose") fires close callback' ); 96 | assert.equal(this, el[0], "option: context of callback"); 97 | assert.equal(e.type, 'multiselectbeforeclose', 'option: event type in callback'); 98 | assert.propEqual(ui, {}, 'option: ui hash'); 99 | } 100 | }) 101 | .on("multiselectbeforeclose", function(e,ui){ 102 | assert.ok(true, 'multiselect("beforeclose") fires multiselectclose event'); 103 | assert.equal(this, el[0], 'event: context of event'); 104 | assert.propEqual(ui, {}, 'event: ui hash'); 105 | }) 106 | .multiselect("open") 107 | .multiselect("close"); 108 | 109 | el.multiselect("destroy").remove(); 110 | 111 | // test 'return false' functionality 112 | el = $("").appendTo(body); 113 | el.multiselect({ 114 | beforeclose: function(){ 115 | return false; 116 | } 117 | }); 118 | 119 | el.multiselect('open').multiselect('close'); 120 | assert.ok( menu().is(':visible') && el.multiselect("isOpen"), "returning false inside callback prevents menu from closing" ); 121 | el.multiselect("destroy").remove(); 122 | }); 123 | 124 | QUnit.test("multiselectclick with multiple widgets", function(assert) { 125 | var first = $("").appendTo(body).multiselect(); 126 | var second = $("").appendTo(body).multiselect(); 127 | assert.equal($('.ui-multiselect').length, 2, "two mutliselects are on the page"); 128 | first.multiselect("refresh"); 129 | second.multiselect("refresh"); 130 | var $label = $(second.multiselect("getLabels")[0]); 131 | var $wrongInput = $(first.multiselect("getLabels")[0]).find("input"); 132 | $label.click(); 133 | assert.equal($label.find("input").prop("checked"), true, "the input for that label should be checked"); 134 | assert.equal($wrongInput.prop("checked"), false, "the input for the corresponding label on the first widget should not be checked"); 135 | first.multiselect("destroy").remove(); 136 | second.multiselect("destroy").remove(); 137 | }); 138 | 139 | QUnit.test("multiselectclick", function(assert){ 140 | var times = 0; 141 | 142 | // inject widget 143 | el = $("") 144 | .appendTo(body); 145 | 146 | el.multiselect({ 147 | click: function(e,ui){ 148 | assert.ok(true, 'option: triggering the click event on the second checkbox fires the click callback' ); 149 | assert.equal(this, el[0], "option: context of callback"); 150 | assert.equal(e.type, 'multiselectclick', 'option: event type in callback'); 151 | assert.equal(ui.value, "2", "option: ui.value assert.equal"); 152 | assert.equal(ui.text, "Option 2", "option: ui.text assert.equal"); 153 | 154 | if(times === 0) { 155 | assert.equal(ui.checked, true, "option: ui.checked assert.equal"); 156 | } else if(times === 1) { 157 | assert.equal(ui.checked, false, "option: ui.checked assert.equal"); 158 | } 159 | } 160 | }) 161 | .on("multiselectclick", function(e,ui){ 162 | assert.ok(true, 'event: triggering the click event on the second checkbox triggers multiselectclick'); 163 | assert.equal(this, el[0], 'event: context of event'); 164 | assert.equal(ui.value, "2", "event: ui.value assert.equal"); 165 | assert.equal(ui.text, "Option 2", "event: ui.text assert.equal"); 166 | 167 | if(times === 0) { 168 | assert.equal(ui.checked, true, "option: ui.checked assert.equal"); 169 | } else if(times === 1) { 170 | assert.equal(ui.checked, false, "option: ui.checked assert.equal"); 171 | } 172 | }) 173 | .on("change", function(e){ 174 | if(++times === 1){ 175 | assert.equal(el.val().join(), "2", "event: select element val() within the change event is correct" ); 176 | } else { 177 | assert.equal(el.val(), null, "event: select element val() within the change event is correct" ); 178 | } 179 | 180 | assert.ok(true, "event: the select's change event fires"); 181 | }) 182 | .multiselect("open"); 183 | 184 | // trigger a click event on the input 185 | var lastInput = menu().find("input").last(); 186 | lastInput[0].click(); 187 | 188 | // trigger once more. 189 | lastInput[0].click(); 190 | 191 | // make sure it has focus 192 | assert.equal(true, lastInput[0] == document.activeElement, "The input has focus"); 193 | 194 | // make sure menu isn't closed automatically 195 | assert.equal( true, el.multiselect('isOpen'), 'menu stays open' ); 196 | 197 | el.multiselect("destroy").remove(); 198 | }); 199 | 200 | QUnit.test("multiselectcheckall", function(assert){ 201 | // inject widget 202 | el = $('').appendTo(body); 203 | 204 | el.multiselect({ 205 | checkAll: function(e,ui){ 206 | assert.ok( true, 'option: multiselect("checkAll") fires checkall callback' ); 207 | assert.equal(this, el[0], "option: context of callback"); 208 | assert.equal(e.type, 'multiselectcheckall', 'option: event type in callback'); 209 | assert.propEqual(ui, {}, 'option: ui hash in callback'); 210 | } 211 | }) 212 | .on("multiselectcheckall", function(e,ui){ 213 | assert.ok( true, 'event: multiselect("checkall") fires multiselectcheckall event' ); 214 | assert.equal(this, el[0], 'event: context of event'); 215 | assert.propEqual(ui, {}, 'event: ui hash'); 216 | }) 217 | .on("change", function(){ 218 | assert.ok(true, "event: the select's change event fires"); 219 | assert.equal( el.val().join(), "1,2", "event: select element val() within the change event is correct" ); 220 | }) 221 | .multiselect("open") 222 | .multiselect("checkAll"); 223 | 224 | assert.equal(menu().find("input").first()[0] == document.activeElement, true, "The first input has focus"); 225 | 226 | el.multiselect("destroy").remove(); 227 | }); 228 | 229 | QUnit.test("multiselectuncheckall", function(assert){ 230 | // inject widget 231 | el = $('').appendTo(body); 232 | 233 | el.multiselect({ 234 | uncheckAll: function(e,ui){ 235 | assert.ok( true, 'option: multiselect("uncheckAll") fires uncheckall callback' ); 236 | assert.equal(this, el[0], "option: context of callback"); 237 | assert.equal(e.type, 'multiselectuncheckall', 'option: event type in callback'); 238 | assert.propEqual(ui, {}, 'option: ui hash in callback'); 239 | } 240 | }) 241 | .on("multiselectuncheckall", function(e,ui){ 242 | assert.ok( true, 'event: multiselect("uncheckall") fires multiselectuncheckall event' ); 243 | assert.equal(this, el[0], 'event: context of event'); 244 | assert.propEqual(ui, {}, 'event: ui hash'); 245 | }) 246 | .on("change", function(){ 247 | assert.ok(true, "event: the select's change event fires"); 248 | assert.equal( el.val(), null, "event: select element val() within the change event is correct" ); 249 | }) 250 | .multiselect("open") 251 | .multiselect("uncheckAll"); 252 | 253 | assert.equal(menu().find("input").first()[0] == document.activeElement, true, "The first input has focus"); 254 | 255 | el.multiselect("destroy").remove(); 256 | }); 257 | 258 | 259 | QUnit.test("multiselectbeforeoptgrouptoggle", function(assert){ 260 | // inject widget 261 | el = $('') 262 | .appendTo(body); 263 | 264 | el.on("change", function(){ 265 | assert.ok(true, "the select's change event fires"); 266 | }) 267 | .multiselect({ 268 | groupsSelectable: true, 269 | beforeoptgrouptoggle: function(e,ui){ 270 | assert.equal(this, el[0], "option: context of callback"); 271 | assert.equal(e.type, 'multiselectbeforeoptgrouptoggle', 'option: event type in callback'); 272 | assert.equal(ui.label, "Set One", 'option: ui.label assert.equal'); 273 | assert.equal(ui.inputs.length, 2, 'option: number of inputs in the ui.inputs key'); 274 | } 275 | }) 276 | .on("multiselectbeforeoptgrouptoggle", function(e,ui){ 277 | assert.ok( true, 'option: multiselect("uncheckall") fires multiselectuncheckall event' ); 278 | assert.equal(this, el[0], 'event: context of event'); 279 | assert.equal(ui.label, "Set One", 'event: ui.label assert.equal'); 280 | assert.equal(ui.inputs.length, 2, 'event: number of inputs in the ui.inputs key'); 281 | }) 282 | .multiselect("open"); 283 | 284 | menu().find(".ui-multiselect-optgroup a").click(); 285 | 286 | el.multiselect("destroy").remove(); 287 | el = el.clone(); 288 | 289 | // test return false preventing checkboxes from activating 290 | el.on("change", function(){ 291 | assert.ok( true ); // should not fire 292 | }).multiselect({ 293 | beforeoptgrouptoggle: function(){ 294 | return false; 295 | }, 296 | // if this fires the expected count will be off. just a redundant way of checking that return false worked 297 | optgrouptoggle: function(){ 298 | assert.ok( true ); 299 | } 300 | }).appendTo( body ); 301 | 302 | var label = menu().find("li.ui-multiselect-optgroup-label a").click(); 303 | assert.equal( menu().find(":input:checked").length, 0, "when returning false inside the optgrouptoggle handler, no checkboxes are checked" ); 304 | el.multiselect("destroy").remove(); 305 | }); 306 | 307 | QUnit.test("multiselectoptgrouptoggle", function(assert){ 308 | // inject widget 309 | el = $('').appendTo(body); 310 | 311 | el.multiselect({ 312 | groupsSelectable: true, 313 | optgrouptoggle: function(e,ui){ 314 | assert.equal(this, el[0], "option: context of callback"); 315 | assert.equal(e.type, 'multiselectoptgrouptoggle', 'option: event type in callback'); 316 | assert.equal(ui.label, "Set One", 'option: ui.label assert.equal'); 317 | assert.equal(ui.inputs.length, 2, 'option: number of inputs in the ui.inputs key'); 318 | assert.equal(ui.checked, true, 'option: ui.checked assert.equal true'); 319 | } 320 | }) 321 | .on("multiselectoptgrouptoggle", function(e,ui){ 322 | assert.ok( true, 'option: multiselect("uncheckall") fires multiselectuncheckall event' ); 323 | assert.equal(this, el[0], 'event: context of event'); 324 | assert.equal(ui.label, "Set One", 'event: ui.label assert.equal'); 325 | assert.equal(ui.inputs.length, 2, 'event: number of inputs in the ui.inputs key'); 326 | assert.equal(ui.checked, true, 'event: ui.checked assert.equal true'); 327 | }) 328 | .multiselect("open"); 329 | 330 | // trigger native click event on optgroup 331 | menu().find(".ui-multiselect-optgroup a").click(); 332 | assert.equal(menu().find(":input:checked").length, 2, "both checkboxes are actually checked" ); 333 | 334 | assert.equal(menu().find("input").first()[0] == document.activeElement, true, "The first input has focus"); 335 | 336 | el.multiselect("destroy").remove(); 337 | }); 338 | 339 | })(jQuery); 340 | -------------------------------------------------------------------------------- /tests/unit/filter.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | var el; var widget; var input; 3 | 4 | /** 5 | * @return {Object} 6 | */ 7 | function getVisible() { 8 | return widget.find('.ui-multiselect-checkboxes input:visible'); 9 | } 10 | 11 | /** 12 | * @return {Object} 13 | */ 14 | function getChecked() { 15 | return el.multiselect('getChecked'); 16 | } 17 | 18 | /** 19 | * @return {Object} 20 | */ 21 | function getSelected() { 22 | return el.children(':selected'); 23 | } 24 | 25 | /** 26 | * @param {string} term to search for 27 | */ 28 | function searchFor(term) { 29 | input.val(term).trigger('search'); 30 | } 31 | 32 | /** 33 | * Triggers click event 34 | */ 35 | function triggerClick() { 36 | this.click(); 37 | } 38 | 39 | /** 40 | * @param {string} term to search for 41 | * @param {number} expected numberof results 42 | * @param {string} message to display on failure 43 | */ 44 | function searchTest( term, expected, message ) { 45 | message || (message = 'searching for \'#\''); 46 | message = message.replace('#', term); 47 | searchFor(term); 48 | QUnit.assert.equal( getVisible().length, expected, message ); 49 | } 50 | 51 | QUnit.module('filter widget - multiple select', { 52 | beforeEach: function() { 53 | el = $(''); 65 | 66 | el.appendTo(document.body); 67 | el.multiselect(); 68 | el.multiselectfilter(); 69 | el.multiselect('open'); 70 | 71 | widget = el.multiselect('widget'); 72 | input = widget.find('.ui-multiselect-filter input'); 73 | }, 74 | 75 | afterEach: function() { 76 | el.multiselectfilter('destroy'); 77 | el.multiselect('destroy'); 78 | el.remove(); 79 | }, 80 | }); 81 | 82 | QUnit.test('defaults', function(assert) { 83 | assert.ok( input.is(':visible'), 'Filter input box is visible' ); 84 | }); 85 | 86 | QUnit.test('checked and unchecked', function(assert) { 87 | var labelCount = el.multiselect('getLabels').length; 88 | var uncheckedCount = el.multiselect('getUnchecked').length; 89 | var checkedCount = el.multiselect('getChecked').length; 90 | assert.equal(uncheckedCount, labelCount, 'Only the ten options are returned'); 91 | assert.equal(uncheckedCount + checkedCount, labelCount, 'Unchecked + checked should equal total inputs'); 92 | el.multiselect('refresh'); 93 | assert.equal(el.multiselect('getUnchecked').length, uncheckedCount, 'Search input still excluded after refresh'); 94 | }); 95 | 96 | QUnit.test('filtering by node text', function(assert) { 97 | searchTest( 'bbaa', 2); 98 | searchTest( 'bbaarr', 1); 99 | searchTest( ' bbaa ', 2, 'searching for \'#\' with whitespace'); 100 | searchTest( ' ', el.children().length, 'searching for an empty string'); 101 | searchTest( 'test', 5); 102 | searchTest( 'one hundred', 1); 103 | searchTest( 'with wor', 1); 104 | searchTest( ' with wor ', 1); 105 | 106 | $.each('$ ^ / : // { } | -'.split(' '), function( i, char ) { 107 | searchTest( char, 1 ); 108 | }); 109 | }); 110 | 111 | QUnit.test('filtering & checking', function(assert) { 112 | searchFor('ba'); 113 | 114 | getVisible().each(triggerClick); 115 | assert.equal(getChecked().length, 2, 'Two checkboxes are selected'); 116 | assert.equal(getSelected().length, 2, 'Two option tags are selected'); 117 | 118 | getVisible().each(triggerClick); 119 | assert.equal(getChecked().length, 0, 'After clicking again, no checkboxes are selected'); 120 | assert.equal(getSelected().length, 0, 'After clicking again, no tags are selected'); 121 | }); 122 | 123 | QUnit.test('checkAll / uncheckAll', function(assert) { 124 | searchFor('ba'); 125 | 126 | el.multiselect('checkAll'); 127 | assert.equal(getChecked().length, 2, 'checkAll: two checkboxes are selected'); 128 | assert.equal(getSelected().length, 2, 'checkAll: two option tags are selected'); 129 | 130 | el.multiselect('uncheckAll'); 131 | assert.equal(getChecked().length, 0, 'uncheckAll: no checkboxes are selected'); 132 | assert.equal(getSelected().length, 0, 'uncheckAll: no option tags are selected'); 133 | }); 134 | 135 | QUnit.test('combination of filtering/methods/click events', function(assert) { 136 | searchFor('ba'); 137 | 138 | getVisible().first().each(triggerClick); 139 | assert.equal(getChecked().length, 1, 'selecting 1 of multiple results (checked)'); 140 | assert.equal(getSelected().length, 1, 'selecting 1 of multiple results (selected)'); 141 | 142 | searchFor(' '); 143 | assert.equal(getChecked().length, 1, 'clearing search, only 1 is still selected'); 144 | el.multiselect('uncheckAll'); 145 | assert.equal(getChecked().length, 0, 'uncheckAll, nothing is selected (checked)'); 146 | assert.equal(getSelected().length, 0, 'uncheckedAll, nothing is selected (selected)'); 147 | 148 | searchFor('one hundred'); 149 | el.multiselect('checkAll'); 150 | assert.equal(getChecked().length, 1, 'checkAll on one matching result (checked)'); 151 | assert.equal(getSelected().length, 1, 'checkAll on one matching result (selected)'); 152 | 153 | searchFor('foo'); 154 | el.multiselect('checkAll'); 155 | assert.equal(getChecked().length, 2, 'checkAll on one matching result (checked)'); 156 | assert.equal(getSelected().length, 2, 'checkAll on one matching result (selected)'); 157 | }); 158 | 159 | QUnit.test('setting filter width', function(assert) { 160 | el.multiselectfilter('destroy'); 161 | el.multiselect().multiselectfilter({width: '100px'}); 162 | var input = $('.ui-multiselect-filter-label input'); 163 | assert.equal(input.width(), 100, 'width option sets width style on filter input'); 164 | }); 165 | })(jQuery); 166 | -------------------------------------------------------------------------------- /tests/unit/html.js: -------------------------------------------------------------------------------- 1 | (function($){ 2 | var el, widget, elems, btn, selectClone; 3 | 4 | QUnit.module("html", { 5 | beforeEach: function() { 6 | el = $("select").multiselect(); 7 | widget = el.multiselect("widget"); 8 | btn = el.multiselect("getButton"); 9 | selectClone = $("select").clone(); 10 | } 11 | }); 12 | 13 | QUnit.test("pull in optgroup's class", function(assert){ 14 | elems = widget.find('.ui-multiselect-optgroup'); 15 | assert.equal( elems.length, 3, 'There are three labels' ); 16 | 17 | elems.filter(":not(:last)").each( function() { 18 | assert.equal($(this).hasClass('ui-multiselect-optgroup'),true,'Default class is present when no extra class is defined'); 19 | }); 20 | elems.filter(":last").each( function() { 21 | assert.equal($(this).hasClass('ui-multiselect-optgroup'),true,'Default class is present when extra class is defined'); 22 | assert.equal($(this).hasClass('optgroupClass'),true,'Extra class is present'); 23 | }); 24 | 25 | }); 26 | 27 | QUnit.test("pull in options's class", function(assert){ 28 | assert.equal(widget.find('input[value="9"]').parents('li:first').hasClass('optionClass'),true,'Extra class is present'); 29 | }); 30 | 31 | QUnit.test("pull in select's ID and adds _ms if it exists", function(assert){ 32 | assert.equal(btn.attr("id"), el.attr("id") + "_ms", "Id is taken from select and _ms is appended"); 33 | }); 34 | 35 | QUnit.test("don't attempt to pull in select's ID and adds _ms if none exists", function(assert){ 36 | selectClone.attr("id", ""); 37 | var clonedEl = selectClone.multiselect(); 38 | var clonedBtn = clonedEl.multiselect("getButton"); 39 | assert.equal(clonedBtn.attr("id"), undefined, "No ID is added"); 40 | }); 41 | 42 | })(jQuery); 43 | -------------------------------------------------------------------------------- /tests/unit/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |