├── .gitignore ├── composer.json ├── gulpfile.js ├── scss ├── _mixins.scss ├── _variables.scss └── selectize.bootstrap4.scss ├── package.json ├── README.md ├── docs └── en │ └── index.md ├── LICENSE ├── dist ├── css │ └── selectize.bootstrap4.css ├── examples.html └── js │ └── selectize.js └── template └── examples.pug /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /node_modules/ 3 | /vendor/ 4 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "const-se/selectize-bootstrap4-theme", 3 | "type": "library", 4 | "version": "2.0.2", 5 | "license": "Apache-2.0", 6 | "description": "Selectize Theme for Bootstrap 4", 7 | "keywords": [ 8 | "bootstrap", 9 | "bootstrap4", 10 | "css", 11 | "sass", 12 | "scss", 13 | "select", 14 | "selectize", 15 | "theme", 16 | "web" 17 | ], 18 | "authors": [ 19 | { 20 | "name": "Constantine Seleznyoff", 21 | "email": "const.seoff@gmail.com" 22 | } 23 | ], 24 | "homepage": "https://github.com/const-se/selectize-bootstrap4-theme", 25 | "extra": { 26 | "branch-alias": { 27 | "dev-master": "2.0.x-dev" 28 | } 29 | }, 30 | "support": { 31 | "email": "const.seoff@gmail.com", 32 | "issues": "https://github.com/const-se/selectize-bootstrap4-theme/issues" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var sass = require('gulp-sass'); 3 | var pug = require('gulp-pug'); 4 | 5 | gulp.task('sass', () => { 6 | return gulp 7 | .src('scss/selectize.bootstrap4.scss') 8 | .pipe(sass({outputStyle: 'expanded'}).on('error', sass.logError)) 9 | .pipe(gulp.dest('dist/css')); 10 | }); 11 | 12 | gulp.task('examples-selectize', () => { 13 | return gulp 14 | .src('node_modules/selectize/dist/js/standalone/selectize.js') 15 | .pipe(gulp.dest('dist/js')); 16 | }); 17 | 18 | gulp.task('examples-html', () => { 19 | return gulp 20 | .src('template/*.pug') 21 | .pipe(pug({pretty: true})) 22 | .pipe(gulp.dest('dist')); 23 | }); 24 | 25 | gulp.task('default', ['sass', 'examples-selectize', 'examples-html'], () => { 26 | gulp.watch(['scss/*.scss'], ['sass']); 27 | gulp.watch(['template/*.pug'], ['examples-html']); 28 | }); 29 | -------------------------------------------------------------------------------- /scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin selectize-control-size($height, $border-radius, $font-size, $padding-x, $padding-y) { 2 | height: $height; 3 | padding: 0; 4 | 5 | .selectize-input { 6 | font-size: $font-size; 7 | padding: $padding-y $padding-x; 8 | @include border-radius($border-radius); 9 | } 10 | 11 | &.single { 12 | $arrow-padding: calc(#{$padding-x} + #{$selectize-input-arrow-width} + #{$padding-y}); 13 | 14 | .selectize-input { 15 | padding-right: $arrow-padding; 16 | } 17 | 18 | &.rtl { 19 | .selectize-input { 20 | padding-left: $arrow-padding; 21 | padding-right: $padding-x; 22 | } 23 | } 24 | } 25 | 26 | &.multi { 27 | min-height: $height; 28 | } 29 | } 30 | 31 | @mixin selectize-control-validation-state($state, $color, $spread) { 32 | &.is-#{$state} { 33 | .selectize-input { 34 | border-color: $color; 35 | 36 | &:focus { 37 | border-color: $color; 38 | box-shadow: 0 0 0 $spread rgba($color, .25); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "selectize-bootstrap4-theme", 3 | "version": "2.0.2", 4 | "license": "Apache-2.0", 5 | "main": "index.js", 6 | "description": "Selectize Theme for Bootstrap 4", 7 | "keywords": [ 8 | "bootstrap", 9 | "bootstrap4", 10 | "css", 11 | "sass", 12 | "scss", 13 | "select", 14 | "selectize", 15 | "theme", 16 | "web" 17 | ], 18 | "author": "Constantine Seleznyoff (const.seoff@gmail.com)", 19 | "homepage": "https://github.com/const-se/selectize-bootstrap4-theme#readme", 20 | "dependencies": { 21 | "bootstrap": "^4.1.3" 22 | }, 23 | "peerDependencies": { 24 | "selectize": "^0.12.6" 25 | }, 26 | "devDependencies": { 27 | "gulp": "^3.9.1", 28 | "gulp-pug": "^4.0.1", 29 | "gulp-sass": "^4.0.1", 30 | "lodash": "^4.17.10", 31 | "selectize": "^0.12.6" 32 | }, 33 | "files": [ 34 | "dist/css/*.css", 35 | "scss/*.scss" 36 | ], 37 | "engines": { 38 | "node": ">=6" 39 | }, 40 | "scripts": { 41 | "test": "echo \"Error: no test specified\" && exit 1" 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "git+https://github.com/const-se/selectize-bootstrap4-theme.git" 46 | }, 47 | "bugs": { 48 | "url": "https://github.com/const-se/selectize-bootstrap4-theme/issues" 49 | }, 50 | "sass": "scss/selectize.bootstrap4.scss", 51 | "style": "dist/css/selectize.bootstrap4.css" 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Selectize Theme for Bootstrap 4 2 | 3 | [Selectize](https://selectize.github.io/selectize.js/) Theme (SCSS) based on the [Bootstrap 4](https://getbootstrap.com/) components. 4 | 5 | ## Table of Contents 6 | 7 | - [Status](#status) 8 | - [Quick Start](#quick-start) 9 | - [TODO](#todo) 10 | - [Creators](#creators) 11 | - [Copyrights and License](#copyrights-and-license) 12 | 13 | ## Status 14 | 15 | [![GitHub release](https://img.shields.io/github/release/const-se/selectize-bootstrap4-theme.svg)](https://github.com/const-se/selectize-bootstrap4-theme/releases) 16 | [![npm version](https://img.shields.io/npm/v/selectize-bootstrap4-theme.svg)](https://www.npmjs.com/package/selectize-bootstrap4-theme) 17 | [![Packagist](https://img.shields.io/packagist/v/const-se/selectize-bootstrap4-theme.svg)](https://packagist.org/packages/const-se/selectize-bootstrap4-theme) 18 | [![Github file size](https://img.shields.io/github/size/const-se/selectize-bootstrap4-theme/dist/css/selectize.bootstrap4.css.svg)](https://github.com/const-se/selectize-bootstrap4-theme/blob/master/dist/css/selectize.bootstrap4.css) 19 | [![Github All Releases](https://img.shields.io/github/downloads/const-se/selectize-bootstrap4-theme/total.svg)](https://github.com/const-se/selectize-bootstrap4-theme) 20 | [![npm](https://img.shields.io/npm/dt/selectize-bootstrap4-theme.svg)](https://www.npmjs.com/package/selectize-bootstrap4-theme) 21 | [![Packagist](https://img.shields.io/packagist/dt/const-se/selectize-bootstrap4-theme.svg)](https://packagist.org/packages/const-se/selectize-bootstrap4-theme) 22 | 23 | ## Quick Start 24 | 25 | Several quick start options are available: 26 | 27 | - [Download the latest release](https://github.com/const-se/selectize-bootstrap4-theme/archive/v2.0.2.zip) 28 | - Clone the repo: `git clone git@github.com:const-se/selectize-bootstrap4-theme.git` 29 | - Install with [npm](https://www.npmjs.com/): `npm install selectize-bootstrap4-theme` 30 | - Install with [yarn](https://yarnpkg.com/): `yarn add selectize-bootstrap4-theme` 31 | - Install with [composer](https://getcomposer.org/): `composer require const-se/selectize-bootstrap4-theme` 32 | 33 | Read the [Documentation](docs/en/index.md) for more information. 34 | 35 | ## TODO 36 | 37 | - Attribute `readonly` support 38 | - Pseudo-classes `:valid`, `:invalid` support 39 | - Bootstrap class `custom-select` support 40 | - Selectize plugin styles support 41 | 42 | ## Creators 43 | 44 | **Constantine Seleznyoff** 45 | 46 | - const.seoff@gmail.com 47 | 48 | ## Copyrights and License 49 | 50 | Copyright 2018 [Constantine Seleznyoff](https://github.com/const-se). 51 | Code released under the [Apache 2.0 License](https://github.com/const-se/selectize-bootstrap4-theme/blob/master/LICENSE). 52 | -------------------------------------------------------------------------------- /docs/en/index.md: -------------------------------------------------------------------------------- 1 | # Selectize Theme for Bootstrap 4 2 | 3 | Selectize Theme (SCSS) based on the Bootstrap 4 components. 4 | 5 | ## Table of Contents 6 | 7 | - [Dependencies](#dependencies) 8 | - [Install](#install) 9 | - [Importing](#importing) 10 | - [Usage](#usage) 11 | - [Theming](#theming) 12 | - [Examples](#examples) 13 | 14 | ## Dependencies 15 | 16 | - [Selectize](https://selectize.github.io/selectize.js/) 0.12.6 or later 17 | - [Bootstrap](https://getbootstrap.com/) 4.1.3 or later 18 | 19 | ## Install 20 | 21 | - [Download the latest release](https://github.com/const-se/selectize-bootstrap4-theme/archive/v2.0.2.zip) 22 | - Clone the repo: `git clone git@github.com:const-se/selectize-bootstrap4-theme.git` 23 | - Install with [npm](https://www.npmjs.com/): `npm install selectize-bootstrap4-theme` 24 | - Install with [yarn](https://yarnpkg.com/): `yarn add selectize-bootstrap4-theme` 25 | - Install with [composer](https://getcomposer.org/): `composer require const-se/selectize-bootstrap4-theme` 26 | 27 | ## Importing 28 | 29 | Import the theme into your SCSS: 30 | 31 | ```scss 32 | @import "node_modules/selectize-bootstrap4-theme/scss/selectize.bootstrap4"; 33 | ``` 34 | 35 | ## Usage 36 | 37 | Add class `form-control` to your ` 41 | 42 | 43 | 44 | 45 | 48 | ``` 49 | 50 | Selectize Theme styles will be added to ` 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 47 |
48 |
49 | 50 | 63 |
64 |
65 | 66 | 79 |
80 |
81 | 82 | 98 |
99 |
100 | 101 | 104 |
Looks good!
105 |
106 |
107 | 108 | 111 |
Please provide valid color.
112 |
113 | 114 |
115 | 116 | 129 |
130 |
131 | 132 |
133 |
134 |
Color
135 |
136 | 149 |
150 |
!
151 |
152 |
153 |
154 | 155 |
156 |

Multiple Select

157 |
158 |
159 | 160 | 165 |
166 |
167 | 168 | 173 |
174 |
175 | 176 | 181 |
182 |
183 | 184 | 189 |
190 |
191 | 192 | 193 |
194 |
195 | 196 | 199 |
Looks good!
200 |
201 |
202 | 203 | 206 |
Please provide valid color.
207 |
208 |
209 |
210 | 211 | 216 |
217 |
218 | 219 |
220 |
221 |
Color
222 |
223 | 228 |
229 |
!
230 |
231 |
232 |
233 |
234 | 235 | 236 | 237 | 238 | 302 | 303 | -------------------------------------------------------------------------------- /dist/js/selectize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * sifter.js 3 | * Copyright (c) 2013 Brian Reavis & contributors 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this 6 | * file except in compliance with the License. You may obtain a copy of the License at: 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under 10 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 11 | * ANY KIND, either express or implied. See the License for the specific language 12 | * governing permissions and limitations under the License. 13 | * 14 | * @author Brian Reavis 15 | */ 16 | 17 | (function(root, factory) { 18 | if (typeof define === 'function' && define.amd) { 19 | define('sifter', factory); 20 | } else if (typeof exports === 'object') { 21 | module.exports = factory(); 22 | } else { 23 | root.Sifter = factory(); 24 | } 25 | }(this, function() { 26 | 27 | /** 28 | * Textually searches arrays and hashes of objects 29 | * by property (or multiple properties). Designed 30 | * specifically for autocomplete. 31 | * 32 | * @constructor 33 | * @param {array|object} items 34 | * @param {object} items 35 | */ 36 | var Sifter = function(items, settings) { 37 | this.items = items; 38 | this.settings = settings || {diacritics: true}; 39 | }; 40 | 41 | /** 42 | * Splits a search string into an array of individual 43 | * regexps to be used to match results. 44 | * 45 | * @param {string} query 46 | * @returns {array} 47 | */ 48 | Sifter.prototype.tokenize = function(query) { 49 | query = trim(String(query || '').toLowerCase()); 50 | if (!query || !query.length) return []; 51 | 52 | var i, n, regex, letter; 53 | var tokens = []; 54 | var words = query.split(/ +/); 55 | 56 | for (i = 0, n = words.length; i < n; i++) { 57 | regex = escape_regex(words[i]); 58 | if (this.settings.diacritics) { 59 | for (letter in DIACRITICS) { 60 | if (DIACRITICS.hasOwnProperty(letter)) { 61 | regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]); 62 | } 63 | } 64 | } 65 | tokens.push({ 66 | string : words[i], 67 | regex : new RegExp(regex, 'i') 68 | }); 69 | } 70 | 71 | return tokens; 72 | }; 73 | 74 | /** 75 | * Iterates over arrays and hashes. 76 | * 77 | * ``` 78 | * this.iterator(this.items, function(item, id) { 79 | * // invoked for each item 80 | * }); 81 | * ``` 82 | * 83 | * @param {array|object} object 84 | */ 85 | Sifter.prototype.iterator = function(object, callback) { 86 | var iterator; 87 | if (is_array(object)) { 88 | iterator = Array.prototype.forEach || function(callback) { 89 | for (var i = 0, n = this.length; i < n; i++) { 90 | callback(this[i], i, this); 91 | } 92 | }; 93 | } else { 94 | iterator = function(callback) { 95 | for (var key in this) { 96 | if (this.hasOwnProperty(key)) { 97 | callback(this[key], key, this); 98 | } 99 | } 100 | }; 101 | } 102 | 103 | iterator.apply(object, [callback]); 104 | }; 105 | 106 | /** 107 | * Returns a function to be used to score individual results. 108 | * 109 | * Good matches will have a higher score than poor matches. 110 | * If an item is not a match, 0 will be returned by the function. 111 | * 112 | * @param {object|string} search 113 | * @param {object} options (optional) 114 | * @returns {function} 115 | */ 116 | Sifter.prototype.getScoreFunction = function(search, options) { 117 | var self, fields, tokens, token_count, nesting; 118 | 119 | self = this; 120 | search = self.prepareSearch(search, options); 121 | tokens = search.tokens; 122 | fields = search.options.fields; 123 | token_count = tokens.length; 124 | nesting = search.options.nesting; 125 | 126 | /** 127 | * Calculates how close of a match the 128 | * given value is against a search token. 129 | * 130 | * @param {mixed} value 131 | * @param {object} token 132 | * @return {number} 133 | */ 134 | var scoreValue = function(value, token) { 135 | var score, pos; 136 | 137 | if (!value) return 0; 138 | value = String(value || ''); 139 | pos = value.search(token.regex); 140 | if (pos === -1) return 0; 141 | score = token.string.length / value.length; 142 | if (pos === 0) score += 0.5; 143 | return score; 144 | }; 145 | 146 | /** 147 | * Calculates the score of an object 148 | * against the search query. 149 | * 150 | * @param {object} token 151 | * @param {object} data 152 | * @return {number} 153 | */ 154 | var scoreObject = (function() { 155 | var field_count = fields.length; 156 | if (!field_count) { 157 | return function() { return 0; }; 158 | } 159 | if (field_count === 1) { 160 | return function(token, data) { 161 | return scoreValue(getattr(data, fields[0], nesting), token); 162 | }; 163 | } 164 | return function(token, data) { 165 | for (var i = 0, sum = 0; i < field_count; i++) { 166 | sum += scoreValue(getattr(data, fields[i], nesting), token); 167 | } 168 | return sum / field_count; 169 | }; 170 | })(); 171 | 172 | if (!token_count) { 173 | return function() { return 0; }; 174 | } 175 | if (token_count === 1) { 176 | return function(data) { 177 | return scoreObject(tokens[0], data); 178 | }; 179 | } 180 | 181 | if (search.options.conjunction === 'and') { 182 | return function(data) { 183 | var score; 184 | for (var i = 0, sum = 0; i < token_count; i++) { 185 | score = scoreObject(tokens[i], data); 186 | if (score <= 0) return 0; 187 | sum += score; 188 | } 189 | return sum / token_count; 190 | }; 191 | } else { 192 | return function(data) { 193 | for (var i = 0, sum = 0; i < token_count; i++) { 194 | sum += scoreObject(tokens[i], data); 195 | } 196 | return sum / token_count; 197 | }; 198 | } 199 | }; 200 | 201 | /** 202 | * Returns a function that can be used to compare two 203 | * results, for sorting purposes. If no sorting should 204 | * be performed, `null` will be returned. 205 | * 206 | * @param {string|object} search 207 | * @param {object} options 208 | * @return function(a,b) 209 | */ 210 | Sifter.prototype.getSortFunction = function(search, options) { 211 | var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort; 212 | 213 | self = this; 214 | search = self.prepareSearch(search, options); 215 | sort = (!search.query && options.sort_empty) || options.sort; 216 | 217 | /** 218 | * Fetches the specified sort field value 219 | * from a search result item. 220 | * 221 | * @param {string} name 222 | * @param {object} result 223 | * @return {mixed} 224 | */ 225 | get_field = function(name, result) { 226 | if (name === '$score') return result.score; 227 | return getattr(self.items[result.id], name, options.nesting); 228 | }; 229 | 230 | // parse options 231 | fields = []; 232 | if (sort) { 233 | for (i = 0, n = sort.length; i < n; i++) { 234 | if (search.query || sort[i].field !== '$score') { 235 | fields.push(sort[i]); 236 | } 237 | } 238 | } 239 | 240 | // the "$score" field is implied to be the primary 241 | // sort field, unless it's manually specified 242 | if (search.query) { 243 | implicit_score = true; 244 | for (i = 0, n = fields.length; i < n; i++) { 245 | if (fields[i].field === '$score') { 246 | implicit_score = false; 247 | break; 248 | } 249 | } 250 | if (implicit_score) { 251 | fields.unshift({field: '$score', direction: 'desc'}); 252 | } 253 | } else { 254 | for (i = 0, n = fields.length; i < n; i++) { 255 | if (fields[i].field === '$score') { 256 | fields.splice(i, 1); 257 | break; 258 | } 259 | } 260 | } 261 | 262 | multipliers = []; 263 | for (i = 0, n = fields.length; i < n; i++) { 264 | multipliers.push(fields[i].direction === 'desc' ? -1 : 1); 265 | } 266 | 267 | // build function 268 | fields_count = fields.length; 269 | if (!fields_count) { 270 | return null; 271 | } else if (fields_count === 1) { 272 | field = fields[0].field; 273 | multiplier = multipliers[0]; 274 | return function(a, b) { 275 | return multiplier * cmp( 276 | get_field(field, a), 277 | get_field(field, b) 278 | ); 279 | }; 280 | } else { 281 | return function(a, b) { 282 | var i, result, a_value, b_value, field; 283 | for (i = 0; i < fields_count; i++) { 284 | field = fields[i].field; 285 | result = multipliers[i] * cmp( 286 | get_field(field, a), 287 | get_field(field, b) 288 | ); 289 | if (result) return result; 290 | } 291 | return 0; 292 | }; 293 | } 294 | }; 295 | 296 | /** 297 | * Parses a search query and returns an object 298 | * with tokens and fields ready to be populated 299 | * with results. 300 | * 301 | * @param {string} query 302 | * @param {object} options 303 | * @returns {object} 304 | */ 305 | Sifter.prototype.prepareSearch = function(query, options) { 306 | if (typeof query === 'object') return query; 307 | 308 | options = extend({}, options); 309 | 310 | var option_fields = options.fields; 311 | var option_sort = options.sort; 312 | var option_sort_empty = options.sort_empty; 313 | 314 | if (option_fields && !is_array(option_fields)) options.fields = [option_fields]; 315 | if (option_sort && !is_array(option_sort)) options.sort = [option_sort]; 316 | if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty]; 317 | 318 | return { 319 | options : options, 320 | query : String(query || '').toLowerCase(), 321 | tokens : this.tokenize(query), 322 | total : 0, 323 | items : [] 324 | }; 325 | }; 326 | 327 | /** 328 | * Searches through all items and returns a sorted array of matches. 329 | * 330 | * The `options` parameter can contain: 331 | * 332 | * - fields {string|array} 333 | * - sort {array} 334 | * - score {function} 335 | * - filter {bool} 336 | * - limit {integer} 337 | * 338 | * Returns an object containing: 339 | * 340 | * - options {object} 341 | * - query {string} 342 | * - tokens {array} 343 | * - total {int} 344 | * - items {array} 345 | * 346 | * @param {string} query 347 | * @param {object} options 348 | * @returns {object} 349 | */ 350 | Sifter.prototype.search = function(query, options) { 351 | var self = this, value, score, search, calculateScore; 352 | var fn_sort; 353 | var fn_score; 354 | 355 | search = this.prepareSearch(query, options); 356 | options = search.options; 357 | query = search.query; 358 | 359 | // generate result scoring function 360 | fn_score = options.score || self.getScoreFunction(search); 361 | 362 | // perform search and sort 363 | if (query.length) { 364 | self.iterator(self.items, function(item, id) { 365 | score = fn_score(item); 366 | if (options.filter === false || score > 0) { 367 | search.items.push({'score': score, 'id': id}); 368 | } 369 | }); 370 | } else { 371 | self.iterator(self.items, function(item, id) { 372 | search.items.push({'score': 1, 'id': id}); 373 | }); 374 | } 375 | 376 | fn_sort = self.getSortFunction(search, options); 377 | if (fn_sort) search.items.sort(fn_sort); 378 | 379 | // apply limits 380 | search.total = search.items.length; 381 | if (typeof options.limit === 'number') { 382 | search.items = search.items.slice(0, options.limit); 383 | } 384 | 385 | return search; 386 | }; 387 | 388 | // utilities 389 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 390 | 391 | var cmp = function(a, b) { 392 | if (typeof a === 'number' && typeof b === 'number') { 393 | return a > b ? 1 : (a < b ? -1 : 0); 394 | } 395 | a = asciifold(String(a || '')); 396 | b = asciifold(String(b || '')); 397 | if (a > b) return 1; 398 | if (b > a) return -1; 399 | return 0; 400 | }; 401 | 402 | var extend = function(a, b) { 403 | var i, n, k, object; 404 | for (i = 1, n = arguments.length; i < n; i++) { 405 | object = arguments[i]; 406 | if (!object) continue; 407 | for (k in object) { 408 | if (object.hasOwnProperty(k)) { 409 | a[k] = object[k]; 410 | } 411 | } 412 | } 413 | return a; 414 | }; 415 | 416 | /** 417 | * A property getter resolving dot-notation 418 | * @param {Object} obj The root object to fetch property on 419 | * @param {String} name The optionally dotted property name to fetch 420 | * @param {Boolean} nesting Handle nesting or not 421 | * @return {Object} The resolved property value 422 | */ 423 | var getattr = function(obj, name, nesting) { 424 | if (!obj || !name) return; 425 | if (!nesting) return obj[name]; 426 | var names = name.split("."); 427 | while(names.length && (obj = obj[names.shift()])); 428 | return obj; 429 | }; 430 | 431 | var trim = function(str) { 432 | return (str + '').replace(/^\s+|\s+$|/g, ''); 433 | }; 434 | 435 | var escape_regex = function(str) { 436 | return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); 437 | }; 438 | 439 | var is_array = Array.isArray || (typeof $ !== 'undefined' && $.isArray) || function(object) { 440 | return Object.prototype.toString.call(object) === '[object Array]'; 441 | }; 442 | 443 | var DIACRITICS = { 444 | 'a': '[aḀḁĂăÂâǍǎȺⱥȦȧẠạÄäÀàÁáĀāÃãÅåąĄÃąĄ]', 445 | 'b': '[b␢βΒB฿𐌁ᛒ]', 446 | 'c': '[cĆćĈĉČčĊċC̄c̄ÇçḈḉȻȼƇƈɕᴄCc]', 447 | 'd': '[dĎďḊḋḐḑḌḍḒḓḎḏĐđD̦d̦ƉɖƊɗƋƌᵭᶁᶑȡᴅDdð]', 448 | 'e': '[eÉéÈèÊêḘḙĚěĔĕẼẽḚḛẺẻĖėËëĒēȨȩĘęᶒɆɇȄȅẾếỀềỄễỂểḜḝḖḗḔḕȆȇẸẹỆệⱸᴇEeɘǝƏƐε]', 449 | 'f': '[fƑƒḞḟ]', 450 | 'g': '[gɢ₲ǤǥĜĝĞğĢģƓɠĠġ]', 451 | 'h': '[hĤĥĦħḨḩẖẖḤḥḢḣɦʰǶƕ]', 452 | 'i': '[iÍíÌìĬĭÎîǏǐÏïḮḯĨĩĮįĪīỈỉȈȉȊȋỊịḬḭƗɨɨ̆ᵻᶖİiIıɪIi]', 453 | 'j': '[jȷĴĵɈɉʝɟʲ]', 454 | 'k': '[kƘƙꝀꝁḰḱǨǩḲḳḴḵκϰ₭]', 455 | 'l': '[lŁłĽľĻļĹĺḶḷḸḹḼḽḺḻĿŀȽƚⱠⱡⱢɫɬᶅɭȴʟLl]', 456 | 'n': '[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲȠƞᵰᶇɳȵɴNnŊŋ]', 457 | 'o': '[oØøÖöÓóÒòÔôǑǒŐőŎŏȮȯỌọƟɵƠơỎỏŌōÕõǪǫȌȍՕօ]', 458 | 'p': '[pṔṕṖṗⱣᵽƤƥᵱ]', 459 | 'q': '[qꝖꝗʠɊɋꝘꝙq̃]', 460 | 'r': '[rŔŕɌɍŘřŖŗṘṙȐȑȒȓṚṛⱤɽ]', 461 | 's': '[sŚśṠṡṢṣꞨꞩŜŝŠšŞşȘșS̈s̈]', 462 | 't': '[tŤťṪṫŢţṬṭƮʈȚțṰṱṮṯƬƭ]', 463 | 'u': '[uŬŭɄʉỤụÜüÚúÙùÛûǓǔŰűŬŭƯưỦủŪūŨũŲųȔȕ∪]', 464 | 'v': '[vṼṽṾṿƲʋꝞꝟⱱʋ]', 465 | 'w': '[wẂẃẀẁŴŵẄẅẆẇẈẉ]', 466 | 'x': '[xẌẍẊẋχ]', 467 | 'y': '[yÝýỲỳŶŷŸÿỸỹẎẏỴỵɎɏƳƴ]', 468 | 'z': '[zŹźẐẑŽžŻżẒẓẔẕƵƶ]' 469 | }; 470 | 471 | var asciifold = (function() { 472 | var i, n, k, chunk; 473 | var foreignletters = ''; 474 | var lookup = {}; 475 | for (k in DIACRITICS) { 476 | if (DIACRITICS.hasOwnProperty(k)) { 477 | chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1); 478 | foreignletters += chunk; 479 | for (i = 0, n = chunk.length; i < n; i++) { 480 | lookup[chunk.charAt(i)] = k; 481 | } 482 | } 483 | } 484 | var regexp = new RegExp('[' + foreignletters + ']', 'g'); 485 | return function(str) { 486 | return str.replace(regexp, function(foreignletter) { 487 | return lookup[foreignletter]; 488 | }).toLowerCase(); 489 | }; 490 | })(); 491 | 492 | 493 | // export 494 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 495 | 496 | return Sifter; 497 | })); 498 | 499 | 500 | 501 | /** 502 | * microplugin.js 503 | * Copyright (c) 2013 Brian Reavis & contributors 504 | * 505 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this 506 | * file except in compliance with the License. You may obtain a copy of the License at: 507 | * http://www.apache.org/licenses/LICENSE-2.0 508 | * 509 | * Unless required by applicable law or agreed to in writing, software distributed under 510 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 511 | * ANY KIND, either express or implied. See the License for the specific language 512 | * governing permissions and limitations under the License. 513 | * 514 | * @author Brian Reavis 515 | */ 516 | 517 | (function(root, factory) { 518 | if (typeof define === 'function' && define.amd) { 519 | define('microplugin', factory); 520 | } else if (typeof exports === 'object') { 521 | module.exports = factory(); 522 | } else { 523 | root.MicroPlugin = factory(); 524 | } 525 | }(this, function() { 526 | var MicroPlugin = {}; 527 | 528 | MicroPlugin.mixin = function(Interface) { 529 | Interface.plugins = {}; 530 | 531 | /** 532 | * Initializes the listed plugins (with options). 533 | * Acceptable formats: 534 | * 535 | * List (without options): 536 | * ['a', 'b', 'c'] 537 | * 538 | * List (with options): 539 | * [{'name': 'a', options: {}}, {'name': 'b', options: {}}] 540 | * 541 | * Hash (with options): 542 | * {'a': { ... }, 'b': { ... }, 'c': { ... }} 543 | * 544 | * @param {mixed} plugins 545 | */ 546 | Interface.prototype.initializePlugins = function(plugins) { 547 | var i, n, key; 548 | var self = this; 549 | var queue = []; 550 | 551 | self.plugins = { 552 | names : [], 553 | settings : {}, 554 | requested : {}, 555 | loaded : {} 556 | }; 557 | 558 | if (utils.isArray(plugins)) { 559 | for (i = 0, n = plugins.length; i < n; i++) { 560 | if (typeof plugins[i] === 'string') { 561 | queue.push(plugins[i]); 562 | } else { 563 | self.plugins.settings[plugins[i].name] = plugins[i].options; 564 | queue.push(plugins[i].name); 565 | } 566 | } 567 | } else if (plugins) { 568 | for (key in plugins) { 569 | if (plugins.hasOwnProperty(key)) { 570 | self.plugins.settings[key] = plugins[key]; 571 | queue.push(key); 572 | } 573 | } 574 | } 575 | 576 | while (queue.length) { 577 | self.require(queue.shift()); 578 | } 579 | }; 580 | 581 | Interface.prototype.loadPlugin = function(name) { 582 | var self = this; 583 | var plugins = self.plugins; 584 | var plugin = Interface.plugins[name]; 585 | 586 | if (!Interface.plugins.hasOwnProperty(name)) { 587 | throw new Error('Unable to find "' + name + '" plugin'); 588 | } 589 | 590 | plugins.requested[name] = true; 591 | plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]); 592 | plugins.names.push(name); 593 | }; 594 | 595 | /** 596 | * Initializes a plugin. 597 | * 598 | * @param {string} name 599 | */ 600 | Interface.prototype.require = function(name) { 601 | var self = this; 602 | var plugins = self.plugins; 603 | 604 | if (!self.plugins.loaded.hasOwnProperty(name)) { 605 | if (plugins.requested[name]) { 606 | throw new Error('Plugin has circular dependency ("' + name + '")'); 607 | } 608 | self.loadPlugin(name); 609 | } 610 | 611 | return plugins.loaded[name]; 612 | }; 613 | 614 | /** 615 | * Registers a plugin. 616 | * 617 | * @param {string} name 618 | * @param {function} fn 619 | */ 620 | Interface.define = function(name, fn) { 621 | Interface.plugins[name] = { 622 | 'name' : name, 623 | 'fn' : fn 624 | }; 625 | }; 626 | }; 627 | 628 | var utils = { 629 | isArray: Array.isArray || function(vArg) { 630 | return Object.prototype.toString.call(vArg) === '[object Array]'; 631 | } 632 | }; 633 | 634 | return MicroPlugin; 635 | })); 636 | 637 | /** 638 | * selectize.js (v0.12.6) 639 | * Copyright (c) 2013–2015 Brian Reavis & contributors 640 | * 641 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this 642 | * file except in compliance with the License. You may obtain a copy of the License at: 643 | * http://www.apache.org/licenses/LICENSE-2.0 644 | * 645 | * Unless required by applicable law or agreed to in writing, software distributed under 646 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 647 | * ANY KIND, either express or implied. See the License for the specific language 648 | * governing permissions and limitations under the License. 649 | * 650 | * @author Brian Reavis 651 | */ 652 | 653 | /*jshint curly:false */ 654 | /*jshint browser:true */ 655 | 656 | (function(root, factory) { 657 | if (typeof define === 'function' && define.amd) { 658 | define('selectize', ['jquery','sifter','microplugin'], factory); 659 | } else if (typeof exports === 'object') { 660 | module.exports = factory(require('jquery'), require('sifter'), require('microplugin')); 661 | } else { 662 | root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin); 663 | } 664 | }(this, function($, Sifter, MicroPlugin) { 665 | 'use strict'; 666 | 667 | var highlight = function($element, pattern) { 668 | if (typeof pattern === 'string' && !pattern.length) return; 669 | var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern; 670 | 671 | var highlight = function(node) { 672 | var skip = 0; 673 | // Wrap matching part of text node with highlighting , e.g. 674 | // Soccer -> Soccer for regex = /soc/i 675 | if (node.nodeType === 3) { 676 | var pos = node.data.search(regex); 677 | if (pos >= 0 && node.data.length > 0) { 678 | var match = node.data.match(regex); 679 | var spannode = document.createElement('span'); 680 | spannode.className = 'highlight'; 681 | var middlebit = node.splitText(pos); 682 | var endbit = middlebit.splitText(match[0].length); 683 | var middleclone = middlebit.cloneNode(true); 684 | spannode.appendChild(middleclone); 685 | middlebit.parentNode.replaceChild(spannode, middlebit); 686 | skip = 1; 687 | } 688 | } 689 | // Recurse element node, looking for child text nodes to highlight, unless element 690 | // is childless,