From d91efd00484b7657505dd310114af103b419a67a Mon Sep 17 00:00:00 2001 From: Constantine Seleznyoff Date: Mon, 10 Sep 2018 12:39:19 +0300 Subject: [PATCH] Version 1.0.0 --- .gitignore | 3 + LICENSE | 201 ++ README.md | 48 + composer.json | 34 + dist/css/selectize.bootstrap4.css | 242 ++ dist/index.html | 150 + dist/js/selectize.js | 3891 +++++++++++++++++++++++++ gulpfile.js | 28 + package-lock.json | 4476 +++++++++++++++++++++++++++++ package.json | 48 + scss/_variables.scss | 16 + scss/selectize.bootstrap4.scss | 260 ++ template/index.pug | 132 + 13 files changed, 9529 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 composer.json create mode 100644 dist/css/selectize.bootstrap4.css create mode 100644 dist/index.html create mode 100644 dist/js/selectize.js create mode 100644 gulpfile.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scss/_variables.scss create mode 100644 scss/selectize.bootstrap4.scss create mode 100644 template/index.pug diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..12a85d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.idea/ +/node_modules/ +/vendor/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cc3e53b --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Constantine Seleznyoff + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..774e0a3 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# Selectize Theme for Bootstrap 4 + +## Dependencies + +* [Selectize](https://selectize.github.io/selectize.js/) 0.12.6 or later +* [Bootstrap](https://getbootstrap.com/) 4.1.3 or later + +## Status + +[![GitHub release](https://img.shields.io/github/release/const-se/selectize-bootstrap4-theme.svg)](https://github.com/const-se/selectize-bootstrap4-theme/releases) +[![npm version](https://img.shields.io/npm/v/selectize-bootstrap4-theme.svg)](https://www.npmjs.com/package/selectize-bootstrap4-theme) +[![Packagist](https://img.shields.io/packagist/v/const-se/selectize-bootstrap4-theme.svg)](https://packagist.org/packages/const-se/selectize-bootstrap4-theme) +[![Github file size](https://img.shields.io/github/size/const-se/selectize-bootstrap4-theme/scss/selectize.bootstrap4.scss.svg)](https://github.com/const-se/selectize-bootstrap4-theme/blob/master/scss/selectize.bootstrap4.scss) +[![Github Releases](https://img.shields.io/github/downloads/const-se/selectize-bootstrap4-theme/latest/total.svg)](https://github.com/const-se/selectize-bootstrap4-theme) +[![npm](https://img.shields.io/npm/dt/selectize-bootstrap4-theme.svg)](https://www.npmjs.com/package/selectize-bootstrap4-theme) +[![Packagist](https://img.shields.io/packagist/dt/const-se/selectize-bootstrap4-theme.svg)](https://packagist.org/packages/const-se/selectize-bootstrap4-theme) + +## Install + +* Clone the repo: `git clone git@github.com:const-se/selectize-bootstrap4-theme.git` +* Install with [npm](https://www.npmjs.com/): `npm install selectize-bootstrap4-theme` +* Install with [yarn](https://yarnpkg.com/): `yarn add selectize-bootstrap4-theme` +* Install with [composer](https://getcomposer.org/): `composer require const-se/selectize-bootstrap4-theme` + +## Usage + +Import the theme into your SCSS: + +```scss +@import "node_modules/selectize-bootstrap4-theme/scss/selectise.bootstrap4.scss"; +``` + +Check selectize variables in `scss/_variables.scss`. Override the variables in your SCSS for fine-tuning of selectize inputs. + +## Examples + +Clone the repo and open `dist/index.html`. + +## Creators + +**Constantine Seleznyoff** + +* const.seoff@gmail.com + +## Copyrights and License + +Copyright 2018 [Constantine Seleznyoff](https://github.com/const-se). +Code released under the [Apache 2.0 License](https://github.com/const-se/selectize-bootstrap4-theme/blob/master/LICENSE). diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..a0c1976 --- /dev/null +++ b/composer.json @@ -0,0 +1,34 @@ +{ + "name": "const-se/selectize-bootstrap4-theme", + "type": "library", + "version": "1.0.0", + "license": "Apache-2.0", + "description": "Selectize Theme for Bootstrap 4", + "keywords": [ + "bootstrap", + "bootstrap4", + "css", + "sass", + "scss", + "select", + "selectize", + "theme", + "web" + ], + "authors": [ + { + "name": "Constantine Seleznyoff", + "email": "const.seoff@gmail.com" + } + ], + "homepage": "https://github.com/const-se/selectize-bootstrap4-theme", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "support": { + "email": "const.seoff@gmail.com", + "issues": "https://github.com/const-se/selectize-bootstrap4-theme/issues" + } +} diff --git a/dist/css/selectize.bootstrap4.css b/dist/css/selectize.bootstrap4.css new file mode 100644 index 0000000..c00a371 --- /dev/null +++ b/dist/css/selectize.bootstrap4.css @@ -0,0 +1,242 @@ +/*! + * Selectize Theme for Bootstrap 4 + * Version 1.0.0 + * Copyright 2018 Constantine Seleznyoff + * Licensed under the Apache License, Version 2.0 (https://github.com/const-se/selectize-bootstrap4-theme/blob/master/LICENSE) + */ +.form-control.selectize-control { + background-color: transparent; + border: none; + border-radius: 0; + box-shadow: none; + padding: 0; + position: relative; + transition: none; +} + +.form-control.selectize-control .selectize-input { + background-clip: padding-box; + background-color: #fff; + border: 1px solid #ced4da; + cursor: text; + display: inline-block; + height: 100%; + overflow: hidden; + padding: 0.375rem 0.75rem; + position: relative; + width: 100%; + border-radius: 0.25rem; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} + +.form-control.selectize-control .selectize-input:focus { + color: #495057; + background-color: #fff; + border-color: #80bdff; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +@media screen and (prefers-reduced-motion: reduce) { + .form-control.selectize-control .selectize-input { + transition: none; + } +} + +.form-control.selectize-control .selectize-input * { + display: -moz-inline-stack; + display: inline-block; + *display: inline; + zoom: 1; + vertical-align: baseline; +} + +.form-control.selectize-control .selectize-input input { + background: none !important; + border: none !important; + box-shadow: none !important; + display: inline-block !important; + line-height: inherit !important; + margin: 0 2px 0 0 !important; + max-height: none !important; + max-width: 100% !important; + min-height: 0 !important; + padding: 0 !important; + text-indent: 0 !important; + -webkit-user-select: auto !important; +} + +.form-control.selectize-control .selectize-input input:hover, .form-control.selectize-control .selectize-input input:focus { + outline: none !important; +} + +.form-control.selectize-control .selectize-input input::-ms-clear { + display: none; +} + +.form-control.selectize-control .selectize-input input::placeholder { + color: #6c757d; + opacity: 1; +} + +.form-control.selectize-control .selectize-input.dropdown-active { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.form-control.selectize-control .selectize-dropdown { + background-color: transparent; + border: none; + border-radius: 0; + box-shadow: none; + display: none; + height: auto; + left: 0; + padding: 0; + position: absolute; + top: 100%; + transition: none; + width: 100% !important; + z-index: 1000; +} + +.form-control.selectize-control .selectize-dropdown .selectize-dropdown-content { + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ced4da; + border-radius: 0 0 0.25rem 0.25rem; + border-top-width: 0; + color: #212529; + font-size: 1rem; + max-height: 14.5rem; + overflow-x: hidden; + overflow-y: auto; + padding: 0.5rem 0; + position: relative; + text-align: left; + -webkit-overflow-scrolling: touch; +} + +.form-control.selectize-control .selectize-dropdown .selectize-dropdown-content .option, +.form-control.selectize-control .selectize-dropdown .selectize-dropdown-content .create { + background-color: transparent; + color: #212529; + font-weight: 400; + padding: 0.25rem 1.5rem; + text-align: inherit; + white-space: nowrap; +} + +.form-control.selectize-control .selectize-dropdown .selectize-dropdown-content .create { + cursor: pointer; +} + +.form-control.selectize-control .selectize-dropdown .selectize-dropdown-content .option:hover, .form-control.selectize-control .selectize-dropdown .selectize-dropdown-content .option:focus { + color: #fff; + text-decoration: none; + background-color: #007bff; +} + +.form-control.selectize-control .selectize-dropdown .selectize-dropdown-content .option.active, .form-control.selectize-control .selectize-dropdown .selectize-dropdown-content .option:active { + color: #fff; + text-decoration: none; + background-color: #007bff; +} + +.form-control.selectize-control .selectize-dropdown .selectize-dropdown-content .option.disabled, .form-control.selectize-control .selectize-dropdown .selectize-dropdown-content .option:disabled, .form-control.selectize-control .selectize-dropdown .selectize-dropdown-content .option[data-disabled] { + background-color: transparent; + color: #6c757d; +} + +.form-control.selectize-control .selectize-dropdown .selectize-dropdown-content .option[data-selectable] { + cursor: pointer; + overflow: hidden; +} + +.form-control.selectize-control .selectize-dropdown .selectize-dropdown-content .optgroup { + border-bottom: 1px solid #e9ecef; + margin-bottom: 0.5rem; + padding-bottom: 0.5rem; +} + +.form-control.selectize-control .selectize-dropdown .selectize-dropdown-content .optgroup:last-child { + border-bottom-width: 0; + margin-bottom: 0; + padding-bottom: 0; +} + +.form-control.selectize-control .selectize-dropdown .selectize-dropdown-content .optgroup .optgroup-header { + color: #6c757d; + display: block; + font-size: 0.875rem; + margin-bottom: 0; + padding: 0.25rem 1.5rem; + white-space: nowrap; +} + +.form-control.selectize-control:disabled .selectize-input, .form-control.selectize-control[readonly] .selectize-input { + background-color: #e9ecef; + opacity: 1; +} + +.form-control.selectize-control.single:after { + border-color: #212529 transparent transparent transparent; + border-style: solid; + border-width: 0.54127rem 0.3125rem 0 0.3125rem; + content: ''; + display: block; + height: 0; + margin-top: -0.27063rem; + position: absolute; + top: 50%; + right: 0.75rem; + width: 0; +} + +.form-control.selectize-control.single .selectize-input { + padding-right: calc(0.75rem + 0.625rem); +} + +.form-control.selectize-control.multi { + height: auto; + min-height: calc(2.25rem + 2px); +} + +.form-control.selectize-control.multi .selectize-input { + height: auto; + min-height: 100%; + overflow: auto; +} + +.form-control.selectize-control.multi .selectize-input .item { + background-color: #007bff; + color: #fff; + display: inline-block; + font-size: 75%; + height: 100%; + line-height: 1; + margin: 0 3px 0 0; + padding: 0.25em 0.4em; + text-align: center; + vertical-align: middle; + white-space: nowrap; + border-radius: 0.25rem; +} + +.form-control-lg.selectize-control { + padding: 0; +} + +.form-control-lg.selectize-control .selectize-input { + padding: 0.5rem 1rem; + border-radius: 0.3rem; +} + +.form-control-sm.selectize-control { + padding: 0; +} + +.form-control-sm.selectize-control .selectize-input { + padding: 0.25rem 0.5rem; + border-radius: 0.2rem; +} diff --git a/dist/index.html b/dist/index.html new file mode 100644 index 0000000..c9b87b1 --- /dev/null +++ b/dist/index.html @@ -0,0 +1,150 @@ + + + + + Selectize Examples + + + + +
+

Examples


+
+

Tagging

+ +
+

Email Contacts

+ +
+

Single Item Select

+ +
+

Option Groups

+ +
+

Large Input

+ + +

+

Inline Form

+
+ + +
+ + +
+
+
+ + + + + \ No newline at end of file diff --git a/dist/js/selectize.js b/dist/js/selectize.js new file mode 100644 index 0000000..e064981 --- /dev/null +++ b/dist/js/selectize.js @@ -0,0 +1,3891 @@ +/** + * sifter.js + * Copyright (c) 2013 Brian Reavis & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Brian Reavis + */ + +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define('sifter', factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root.Sifter = factory(); + } +}(this, function() { + + /** + * Textually searches arrays and hashes of objects + * by property (or multiple properties). Designed + * specifically for autocomplete. + * + * @constructor + * @param {array|object} items + * @param {object} items + */ + var Sifter = function(items, settings) { + this.items = items; + this.settings = settings || {diacritics: true}; + }; + + /** + * Splits a search string into an array of individual + * regexps to be used to match results. + * + * @param {string} query + * @returns {array} + */ + Sifter.prototype.tokenize = function(query) { + query = trim(String(query || '').toLowerCase()); + if (!query || !query.length) return []; + + var i, n, regex, letter; + var tokens = []; + var words = query.split(/ +/); + + for (i = 0, n = words.length; i < n; i++) { + regex = escape_regex(words[i]); + if (this.settings.diacritics) { + for (letter in DIACRITICS) { + if (DIACRITICS.hasOwnProperty(letter)) { + regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]); + } + } + } + tokens.push({ + string : words[i], + regex : new RegExp(regex, 'i') + }); + } + + return tokens; + }; + + /** + * Iterates over arrays and hashes. + * + * ``` + * this.iterator(this.items, function(item, id) { + * // invoked for each item + * }); + * ``` + * + * @param {array|object} object + */ + Sifter.prototype.iterator = function(object, callback) { + var iterator; + if (is_array(object)) { + iterator = Array.prototype.forEach || function(callback) { + for (var i = 0, n = this.length; i < n; i++) { + callback(this[i], i, this); + } + }; + } else { + iterator = function(callback) { + for (var key in this) { + if (this.hasOwnProperty(key)) { + callback(this[key], key, this); + } + } + }; + } + + iterator.apply(object, [callback]); + }; + + /** + * Returns a function to be used to score individual results. + * + * Good matches will have a higher score than poor matches. + * If an item is not a match, 0 will be returned by the function. + * + * @param {object|string} search + * @param {object} options (optional) + * @returns {function} + */ + Sifter.prototype.getScoreFunction = function(search, options) { + var self, fields, tokens, token_count, nesting; + + self = this; + search = self.prepareSearch(search, options); + tokens = search.tokens; + fields = search.options.fields; + token_count = tokens.length; + nesting = search.options.nesting; + + /** + * Calculates how close of a match the + * given value is against a search token. + * + * @param {mixed} value + * @param {object} token + * @return {number} + */ + var scoreValue = function(value, token) { + var score, pos; + + if (!value) return 0; + value = String(value || ''); + pos = value.search(token.regex); + if (pos === -1) return 0; + score = token.string.length / value.length; + if (pos === 0) score += 0.5; + return score; + }; + + /** + * Calculates the score of an object + * against the search query. + * + * @param {object} token + * @param {object} data + * @return {number} + */ + var scoreObject = (function() { + var field_count = fields.length; + if (!field_count) { + return function() { return 0; }; + } + if (field_count === 1) { + return function(token, data) { + return scoreValue(getattr(data, fields[0], nesting), token); + }; + } + return function(token, data) { + for (var i = 0, sum = 0; i < field_count; i++) { + sum += scoreValue(getattr(data, fields[i], nesting), token); + } + return sum / field_count; + }; + })(); + + if (!token_count) { + return function() { return 0; }; + } + if (token_count === 1) { + return function(data) { + return scoreObject(tokens[0], data); + }; + } + + if (search.options.conjunction === 'and') { + return function(data) { + var score; + for (var i = 0, sum = 0; i < token_count; i++) { + score = scoreObject(tokens[i], data); + if (score <= 0) return 0; + sum += score; + } + return sum / token_count; + }; + } else { + return function(data) { + for (var i = 0, sum = 0; i < token_count; i++) { + sum += scoreObject(tokens[i], data); + } + return sum / token_count; + }; + } + }; + + /** + * Returns a function that can be used to compare two + * results, for sorting purposes. If no sorting should + * be performed, `null` will be returned. + * + * @param {string|object} search + * @param {object} options + * @return function(a,b) + */ + Sifter.prototype.getSortFunction = function(search, options) { + var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort; + + self = this; + search = self.prepareSearch(search, options); + sort = (!search.query && options.sort_empty) || options.sort; + + /** + * Fetches the specified sort field value + * from a search result item. + * + * @param {string} name + * @param {object} result + * @return {mixed} + */ + get_field = function(name, result) { + if (name === '$score') return result.score; + return getattr(self.items[result.id], name, options.nesting); + }; + + // parse options + fields = []; + if (sort) { + for (i = 0, n = sort.length; i < n; i++) { + if (search.query || sort[i].field !== '$score') { + fields.push(sort[i]); + } + } + } + + // the "$score" field is implied to be the primary + // sort field, unless it's manually specified + if (search.query) { + implicit_score = true; + for (i = 0, n = fields.length; i < n; i++) { + if (fields[i].field === '$score') { + implicit_score = false; + break; + } + } + if (implicit_score) { + fields.unshift({field: '$score', direction: 'desc'}); + } + } else { + for (i = 0, n = fields.length; i < n; i++) { + if (fields[i].field === '$score') { + fields.splice(i, 1); + break; + } + } + } + + multipliers = []; + for (i = 0, n = fields.length; i < n; i++) { + multipliers.push(fields[i].direction === 'desc' ? -1 : 1); + } + + // build function + fields_count = fields.length; + if (!fields_count) { + return null; + } else if (fields_count === 1) { + field = fields[0].field; + multiplier = multipliers[0]; + return function(a, b) { + return multiplier * cmp( + get_field(field, a), + get_field(field, b) + ); + }; + } else { + return function(a, b) { + var i, result, a_value, b_value, field; + for (i = 0; i < fields_count; i++) { + field = fields[i].field; + result = multipliers[i] * cmp( + get_field(field, a), + get_field(field, b) + ); + if (result) return result; + } + return 0; + }; + } + }; + + /** + * Parses a search query and returns an object + * with tokens and fields ready to be populated + * with results. + * + * @param {string} query + * @param {object} options + * @returns {object} + */ + Sifter.prototype.prepareSearch = function(query, options) { + if (typeof query === 'object') return query; + + options = extend({}, options); + + var option_fields = options.fields; + var option_sort = options.sort; + var option_sort_empty = options.sort_empty; + + if (option_fields && !is_array(option_fields)) options.fields = [option_fields]; + if (option_sort && !is_array(option_sort)) options.sort = [option_sort]; + if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty]; + + return { + options : options, + query : String(query || '').toLowerCase(), + tokens : this.tokenize(query), + total : 0, + items : [] + }; + }; + + /** + * Searches through all items and returns a sorted array of matches. + * + * The `options` parameter can contain: + * + * - fields {string|array} + * - sort {array} + * - score {function} + * - filter {bool} + * - limit {integer} + * + * Returns an object containing: + * + * - options {object} + * - query {string} + * - tokens {array} + * - total {int} + * - items {array} + * + * @param {string} query + * @param {object} options + * @returns {object} + */ + Sifter.prototype.search = function(query, options) { + var self = this, value, score, search, calculateScore; + var fn_sort; + var fn_score; + + search = this.prepareSearch(query, options); + options = search.options; + query = search.query; + + // generate result scoring function + fn_score = options.score || self.getScoreFunction(search); + + // perform search and sort + if (query.length) { + self.iterator(self.items, function(item, id) { + score = fn_score(item); + if (options.filter === false || score > 0) { + search.items.push({'score': score, 'id': id}); + } + }); + } else { + self.iterator(self.items, function(item, id) { + search.items.push({'score': 1, 'id': id}); + }); + } + + fn_sort = self.getSortFunction(search, options); + if (fn_sort) search.items.sort(fn_sort); + + // apply limits + search.total = search.items.length; + if (typeof options.limit === 'number') { + search.items = search.items.slice(0, options.limit); + } + + return search; + }; + + // utilities + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + var cmp = function(a, b) { + if (typeof a === 'number' && typeof b === 'number') { + return a > b ? 1 : (a < b ? -1 : 0); + } + a = asciifold(String(a || '')); + b = asciifold(String(b || '')); + if (a > b) return 1; + if (b > a) return -1; + return 0; + }; + + var extend = function(a, b) { + var i, n, k, object; + for (i = 1, n = arguments.length; i < n; i++) { + object = arguments[i]; + if (!object) continue; + for (k in object) { + if (object.hasOwnProperty(k)) { + a[k] = object[k]; + } + } + } + return a; + }; + + /** + * A property getter resolving dot-notation + * @param {Object} obj The root object to fetch property on + * @param {String} name The optionally dotted property name to fetch + * @param {Boolean} nesting Handle nesting or not + * @return {Object} The resolved property value + */ + var getattr = function(obj, name, nesting) { + if (!obj || !name) return; + if (!nesting) return obj[name]; + var names = name.split("."); + while(names.length && (obj = obj[names.shift()])); + return obj; + }; + + var trim = function(str) { + return (str + '').replace(/^\s+|\s+$|/g, ''); + }; + + var escape_regex = function(str) { + return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); + }; + + var is_array = Array.isArray || (typeof $ !== 'undefined' && $.isArray) || function(object) { + return Object.prototype.toString.call(object) === '[object Array]'; + }; + + var DIACRITICS = { + 'a': '[aḀḁĂăÂâǍǎȺⱥȦȧẠạÄäÀàÁáĀāÃãÅåąĄÃąĄ]', + 'b': '[b␢βΒB฿𐌁ᛒ]', + 'c': '[cĆćĈĉČčĊċC̄c̄ÇçḈḉȻȼƇƈɕᴄCc]', + 'd': '[dĎďḊḋḐḑḌḍḒḓḎḏĐđD̦d̦ƉɖƊɗƋƌᵭᶁᶑȡᴅDdð]', + 'e': '[eÉéÈèÊêḘḙĚěĔĕẼẽḚḛẺẻĖėËëĒēȨȩĘęᶒɆɇȄȅẾếỀềỄễỂểḜḝḖḗḔḕȆȇẸẹỆệⱸᴇEeɘǝƏƐε]', + 'f': '[fƑƒḞḟ]', + 'g': '[gɢ₲ǤǥĜĝĞğĢģƓɠĠġ]', + 'h': '[hĤĥĦħḨḩẖẖḤḥḢḣɦʰǶƕ]', + 'i': '[iÍíÌìĬĭÎîǏǐÏïḮḯĨĩĮįĪīỈỉȈȉȊȋỊịḬḭƗɨɨ̆ᵻᶖİiIıɪIi]', + 'j': '[jȷĴĵɈɉʝɟʲ]', + 'k': '[kƘƙꝀꝁḰḱǨǩḲḳḴḵκϰ₭]', + 'l': '[lŁłĽľĻļĹĺḶḷḸḹḼḽḺḻĿŀȽƚⱠⱡⱢɫɬᶅɭȴʟLl]', + 'n': '[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲȠƞᵰᶇɳȵɴNnŊŋ]', + 'o': '[oØøÖöÓóÒòÔôǑǒŐőŎŏȮȯỌọƟɵƠơỎỏŌōÕõǪǫȌȍՕօ]', + 'p': '[pṔṕṖṗⱣᵽƤƥᵱ]', + 'q': '[qꝖꝗʠɊɋꝘꝙq̃]', + 'r': '[rŔŕɌɍŘřŖŗṘṙȐȑȒȓṚṛⱤɽ]', + 's': '[sŚśṠṡṢṣꞨꞩŜŝŠšŞşȘșS̈s̈]', + 't': '[tŤťṪṫŢţṬṭƮʈȚțṰṱṮṯƬƭ]', + 'u': '[uŬŭɄʉỤụÜüÚúÙùÛûǓǔŰűŬŭƯưỦủŪūŨũŲųȔȕ∪]', + 'v': '[vṼṽṾṿƲʋꝞꝟⱱʋ]', + 'w': '[wẂẃẀẁŴŵẄẅẆẇẈẉ]', + 'x': '[xẌẍẊẋχ]', + 'y': '[yÝýỲỳŶŷŸÿỸỹẎẏỴỵɎɏƳƴ]', + 'z': '[zŹźẐẑŽžŻżẒẓẔẕƵƶ]' + }; + + var asciifold = (function() { + var i, n, k, chunk; + var foreignletters = ''; + var lookup = {}; + for (k in DIACRITICS) { + if (DIACRITICS.hasOwnProperty(k)) { + chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1); + foreignletters += chunk; + for (i = 0, n = chunk.length; i < n; i++) { + lookup[chunk.charAt(i)] = k; + } + } + } + var regexp = new RegExp('[' + foreignletters + ']', 'g'); + return function(str) { + return str.replace(regexp, function(foreignletter) { + return lookup[foreignletter]; + }).toLowerCase(); + }; + })(); + + + // export + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + return Sifter; +})); + + + +/** + * microplugin.js + * Copyright (c) 2013 Brian Reavis & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Brian Reavis + */ + +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define('microplugin', factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root.MicroPlugin = factory(); + } +}(this, function() { + var MicroPlugin = {}; + + MicroPlugin.mixin = function(Interface) { + Interface.plugins = {}; + + /** + * Initializes the listed plugins (with options). + * Acceptable formats: + * + * List (without options): + * ['a', 'b', 'c'] + * + * List (with options): + * [{'name': 'a', options: {}}, {'name': 'b', options: {}}] + * + * Hash (with options): + * {'a': { ... }, 'b': { ... }, 'c': { ... }} + * + * @param {mixed} plugins + */ + Interface.prototype.initializePlugins = function(plugins) { + var i, n, key; + var self = this; + var queue = []; + + self.plugins = { + names : [], + settings : {}, + requested : {}, + loaded : {} + }; + + if (utils.isArray(plugins)) { + for (i = 0, n = plugins.length; i < n; i++) { + if (typeof plugins[i] === 'string') { + queue.push(plugins[i]); + } else { + self.plugins.settings[plugins[i].name] = plugins[i].options; + queue.push(plugins[i].name); + } + } + } else if (plugins) { + for (key in plugins) { + if (plugins.hasOwnProperty(key)) { + self.plugins.settings[key] = plugins[key]; + queue.push(key); + } + } + } + + while (queue.length) { + self.require(queue.shift()); + } + }; + + Interface.prototype.loadPlugin = function(name) { + var self = this; + var plugins = self.plugins; + var plugin = Interface.plugins[name]; + + if (!Interface.plugins.hasOwnProperty(name)) { + throw new Error('Unable to find "' + name + '" plugin'); + } + + plugins.requested[name] = true; + plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]); + plugins.names.push(name); + }; + + /** + * Initializes a plugin. + * + * @param {string} name + */ + Interface.prototype.require = function(name) { + var self = this; + var plugins = self.plugins; + + if (!self.plugins.loaded.hasOwnProperty(name)) { + if (plugins.requested[name]) { + throw new Error('Plugin has circular dependency ("' + name + '")'); + } + self.loadPlugin(name); + } + + return plugins.loaded[name]; + }; + + /** + * Registers a plugin. + * + * @param {string} name + * @param {function} fn + */ + Interface.define = function(name, fn) { + Interface.plugins[name] = { + 'name' : name, + 'fn' : fn + }; + }; + }; + + var utils = { + isArray: Array.isArray || function(vArg) { + return Object.prototype.toString.call(vArg) === '[object Array]'; + } + }; + + return MicroPlugin; +})); + +/** + * selectize.js (v0.12.6) + * Copyright (c) 2013–2015 Brian Reavis & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Brian Reavis + */ + +/*jshint curly:false */ +/*jshint browser:true */ + +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define('selectize', ['jquery','sifter','microplugin'], factory); + } else if (typeof exports === 'object') { + module.exports = factory(require('jquery'), require('sifter'), require('microplugin')); + } else { + root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin); + } +}(this, function($, Sifter, MicroPlugin) { + 'use strict'; + + var highlight = function($element, pattern) { + if (typeof pattern === 'string' && !pattern.length) return; + var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern; + + var highlight = function(node) { + var skip = 0; + // Wrap matching part of text node with highlighting , e.g. + // Soccer -> Soccer for regex = /soc/i + if (node.nodeType === 3) { + var pos = node.data.search(regex); + if (pos >= 0 && node.data.length > 0) { + var match = node.data.match(regex); + var spannode = document.createElement('span'); + spannode.className = 'highlight'; + var middlebit = node.splitText(pos); + var endbit = middlebit.splitText(match[0].length); + var middleclone = middlebit.cloneNode(true); + spannode.appendChild(middleclone); + middlebit.parentNode.replaceChild(spannode, middlebit); + skip = 1; + } + } + // Recurse element node, looking for child text nodes to highlight, unless element + // is childless,