abstracted 0.0.6 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Gemfile.lock +11 -8
  4. data/app/assets/javascripts/abstract_resources.js +29 -25
  5. data/app/assets/javascripts/crud.js.coffee +174 -29
  6. data/app/assets/javascripts/initializers.js.coffee +403 -58
  7. data/app/assets/javascripts/pagescrolling.js.coffee +92 -0
  8. data/app/assets/stylesheets/abstract_resources.css +5 -0
  9. data/app/assets/stylesheets/scaffold.css +4 -2
  10. data/app/assets/stylesheets/selectize.css.scss +0 -0
  11. data/app/controllers/abstract_resources_controller.rb +473 -134
  12. data/app/controllers/application_controller.rb +22 -7
  13. data/app/helpers/abstract_resources_helper.rb +515 -9
  14. data/app/helpers/fab_button_helper.rb +82 -0
  15. data/app/models/abstract_resource.rb +141 -89
  16. data/app/models/ancestry_abstract_resource.rb +27 -0
  17. data/app/models/concerns/roleable.rb +1 -1
  18. data/app/policies/abstract_resource_policy.rb +25 -0
  19. data/app/views/abstract_resources/_title.html.haml +2 -0
  20. data/app/views/abstract_resources/activate.html.haml +5 -0
  21. data/app/views/abstract_resources/attach.html.haml +6 -0
  22. data/app/views/abstract_resources/deactivate.html.haml +5 -0
  23. data/app/views/abstract_resources/defer.html.haml +5 -0
  24. data/app/views/abstract_resources/destroy.js.haml +2 -1
  25. data/app/views/abstract_resources/detach.html.haml +5 -0
  26. data/app/views/abstract_resources/edit.html.haml +4 -9
  27. data/app/views/abstract_resources/error.html.haml +2 -0
  28. data/app/views/abstract_resources/error.js.haml +2 -0
  29. data/app/views/abstract_resources/index.html.haml +27 -0
  30. data/app/views/abstract_resources/index.js.haml +2 -0
  31. data/app/views/abstract_resources/new.html.haml +4 -10
  32. data/app/views/abstract_resources/prefer.html.haml +5 -0
  33. data/app/views/abstract_resources/print.html.haml +2 -0
  34. data/app/views/abstract_resources/print.js.haml +2 -0
  35. data/app/views/abstract_resources/show.html.haml +5 -11
  36. data/app/views/layouts/_messages.html.haml +1 -0
  37. data/app/views/layouts/_navigation.html.haml +13 -0
  38. data/bower.json +34 -0
  39. data/config/initializers/exception.rb +19 -0
  40. data/config/initializers/pundit.rb +22 -0
  41. data/config/routes.rb +24 -0
  42. data/lib/abstracted.rb +3 -0
  43. data/lib/abstracted/version.rb +1 -1
  44. data/lib/abstracted_responder.rb +1 -0
  45. data/lib/exceptions.rb +61 -0
  46. data/vendor/assets/components/jquery/.bower.json +38 -0
  47. data/vendor/assets/components/jquery/MIT-LICENSE.txt +21 -0
  48. data/vendor/assets/components/jquery/bower.json +28 -0
  49. data/vendor/assets/components/jquery/dist/jquery.js +9210 -0
  50. data/vendor/assets/components/jquery/dist/jquery.min.js +5 -0
  51. data/vendor/assets/components/jquery/dist/jquery.min.map +1 -0
  52. data/vendor/assets/components/jquery/src/ajax.js +786 -0
  53. data/vendor/assets/components/jquery/src/ajax/jsonp.js +89 -0
  54. data/vendor/assets/components/jquery/src/ajax/load.js +75 -0
  55. data/vendor/assets/components/jquery/src/ajax/parseJSON.js +13 -0
  56. data/vendor/assets/components/jquery/src/ajax/parseXML.js +28 -0
  57. data/vendor/assets/components/jquery/src/ajax/script.js +64 -0
  58. data/vendor/assets/components/jquery/src/ajax/var/nonce.js +5 -0
  59. data/vendor/assets/components/jquery/src/ajax/var/rquery.js +3 -0
  60. data/vendor/assets/components/jquery/src/ajax/xhr.js +136 -0
  61. data/vendor/assets/components/jquery/src/attributes.js +11 -0
  62. data/vendor/assets/components/jquery/src/attributes/attr.js +141 -0
  63. data/vendor/assets/components/jquery/src/attributes/classes.js +158 -0
  64. data/vendor/assets/components/jquery/src/attributes/prop.js +94 -0
  65. data/vendor/assets/components/jquery/src/attributes/support.js +35 -0
  66. data/vendor/assets/components/jquery/src/attributes/val.js +161 -0
  67. data/vendor/assets/components/jquery/src/callbacks.js +205 -0
  68. data/vendor/assets/components/jquery/src/core.js +502 -0
  69. data/vendor/assets/components/jquery/src/core/access.js +60 -0
  70. data/vendor/assets/components/jquery/src/core/init.js +123 -0
  71. data/vendor/assets/components/jquery/src/core/parseHTML.js +39 -0
  72. data/vendor/assets/components/jquery/src/core/ready.js +97 -0
  73. data/vendor/assets/components/jquery/src/core/var/rsingleTag.js +4 -0
  74. data/vendor/assets/components/jquery/src/css.js +450 -0
  75. data/vendor/assets/components/jquery/src/css/addGetHookIf.js +22 -0
  76. data/vendor/assets/components/jquery/src/css/curCSS.js +57 -0
  77. data/vendor/assets/components/jquery/src/css/defaultDisplay.js +70 -0
  78. data/vendor/assets/components/jquery/src/css/hiddenVisibleSelectors.js +15 -0
  79. data/vendor/assets/components/jquery/src/css/support.js +96 -0
  80. data/vendor/assets/components/jquery/src/css/swap.js +28 -0
  81. data/vendor/assets/components/jquery/src/css/var/cssExpand.js +3 -0
  82. data/vendor/assets/components/jquery/src/css/var/getStyles.js +12 -0
  83. data/vendor/assets/components/jquery/src/css/var/isHidden.js +13 -0
  84. data/vendor/assets/components/jquery/src/css/var/rmargin.js +3 -0
  85. data/vendor/assets/components/jquery/src/css/var/rnumnonpx.js +5 -0
  86. data/vendor/assets/components/jquery/src/data.js +178 -0
  87. data/vendor/assets/components/jquery/src/data/Data.js +181 -0
  88. data/vendor/assets/components/jquery/src/data/accepts.js +20 -0
  89. data/vendor/assets/components/jquery/src/data/var/data_priv.js +5 -0
  90. data/vendor/assets/components/jquery/src/data/var/data_user.js +5 -0
  91. data/vendor/assets/components/jquery/src/deferred.js +149 -0
  92. data/vendor/assets/components/jquery/src/deprecated.js +13 -0
  93. data/vendor/assets/components/jquery/src/dimensions.js +50 -0
  94. data/vendor/assets/components/jquery/src/effects.js +648 -0
  95. data/vendor/assets/components/jquery/src/effects/Tween.js +114 -0
  96. data/vendor/assets/components/jquery/src/effects/animatedSelector.js +13 -0
  97. data/vendor/assets/components/jquery/src/event.js +868 -0
  98. data/vendor/assets/components/jquery/src/event/ajax.js +13 -0
  99. data/vendor/assets/components/jquery/src/event/alias.js +39 -0
  100. data/vendor/assets/components/jquery/src/event/support.js +9 -0
  101. data/vendor/assets/components/jquery/src/exports/amd.js +24 -0
  102. data/vendor/assets/components/jquery/src/exports/global.js +32 -0
  103. data/vendor/assets/components/jquery/src/intro.js +44 -0
  104. data/vendor/assets/components/jquery/src/jquery.js +37 -0
  105. data/vendor/assets/components/jquery/src/manipulation.js +580 -0
  106. data/vendor/assets/components/jquery/src/manipulation/_evalUrl.js +18 -0
  107. data/vendor/assets/components/jquery/src/manipulation/support.js +32 -0
  108. data/vendor/assets/components/jquery/src/manipulation/var/rcheckableType.js +3 -0
  109. data/vendor/assets/components/jquery/src/offset.js +207 -0
  110. data/vendor/assets/components/jquery/src/outro.js +1 -0
  111. data/vendor/assets/components/jquery/src/queue.js +142 -0
  112. data/vendor/assets/components/jquery/src/queue/delay.js +22 -0
  113. data/vendor/assets/components/jquery/src/selector-native.js +172 -0
  114. data/vendor/assets/components/jquery/src/selector-sizzle.js +14 -0
  115. data/vendor/assets/components/jquery/src/selector.js +1 -0
  116. data/vendor/assets/components/jquery/src/serialize.js +111 -0
  117. data/vendor/assets/components/jquery/src/sizzle/dist/sizzle.js +2067 -0
  118. data/vendor/assets/components/jquery/src/sizzle/dist/sizzle.min.js +3 -0
  119. data/vendor/assets/components/jquery/src/sizzle/dist/sizzle.min.map +1 -0
  120. data/vendor/assets/components/jquery/src/traversing.js +199 -0
  121. data/vendor/assets/components/jquery/src/traversing/findFilter.js +100 -0
  122. data/vendor/assets/components/jquery/src/traversing/var/rneedsContext.js +6 -0
  123. data/vendor/assets/components/jquery/src/var/arr.js +3 -0
  124. data/vendor/assets/components/jquery/src/var/class2type.js +4 -0
  125. data/vendor/assets/components/jquery/src/var/concat.js +5 -0
  126. data/vendor/assets/components/jquery/src/var/hasOwn.js +5 -0
  127. data/vendor/assets/components/jquery/src/var/indexOf.js +5 -0
  128. data/vendor/assets/components/jquery/src/var/pnum.js +3 -0
  129. data/vendor/assets/components/jquery/src/var/push.js +5 -0
  130. data/vendor/assets/components/jquery/src/var/rnotwhite.js +3 -0
  131. data/vendor/assets/components/jquery/src/var/slice.js +5 -0
  132. data/vendor/assets/components/jquery/src/var/strundefined.js +3 -0
  133. data/vendor/assets/components/jquery/src/var/support.js +4 -0
  134. data/vendor/assets/components/jquery/src/var/toString.js +5 -0
  135. data/vendor/assets/components/jquery/src/wrap.js +79 -0
  136. data/vendor/assets/components/microplugin/.bower.json +38 -0
  137. data/vendor/assets/components/microplugin/.gitignore +2 -0
  138. data/vendor/assets/components/microplugin/.npmignore +3 -0
  139. data/vendor/assets/components/microplugin/README.md +115 -0
  140. data/vendor/assets/components/microplugin/bower.json +22 -0
  141. data/vendor/assets/components/microplugin/package.json +15 -0
  142. data/vendor/assets/components/microplugin/src/microplugin.js +135 -0
  143. data/vendor/assets/components/selectize/.bower.json +60 -0
  144. data/vendor/assets/components/selectize/.gitignore +5 -0
  145. data/vendor/assets/components/selectize/LICENSE +202 -0
  146. data/vendor/assets/components/selectize/README.md +105 -0
  147. data/vendor/assets/components/selectize/bower.json +40 -0
  148. data/vendor/assets/components/selectize/dist/css/selectize.bootstrap2.css +487 -0
  149. data/vendor/assets/components/selectize/dist/css/selectize.bootstrap3.css +401 -0
  150. data/vendor/assets/components/selectize/dist/css/selectize.css +317 -0
  151. data/vendor/assets/components/selectize/dist/css/selectize.default.css +387 -0
  152. data/vendor/assets/components/selectize/dist/css/selectize.legacy.css +364 -0
  153. data/vendor/assets/components/selectize/dist/js/selectize.js +3058 -0
  154. data/vendor/assets/components/selectize/dist/js/selectize.min.js +3 -0
  155. data/vendor/assets/components/selectize/dist/js/standalone/selectize.js +3667 -0
  156. data/vendor/assets/components/selectize/dist/js/standalone/selectize.min.js +3 -0
  157. data/vendor/assets/components/selectize/dist/less/plugins/drag_drop.less +16 -0
  158. data/vendor/assets/components/selectize/dist/less/plugins/dropdown_header.less +20 -0
  159. data/vendor/assets/components/selectize/dist/less/plugins/optgroup_columns.less +17 -0
  160. data/vendor/assets/components/selectize/dist/less/plugins/remove_button.less +37 -0
  161. data/vendor/assets/components/selectize/dist/less/selectize.bootstrap2.less +161 -0
  162. data/vendor/assets/components/selectize/dist/less/selectize.bootstrap3.less +150 -0
  163. data/vendor/assets/components/selectize/dist/less/selectize.default.less +84 -0
  164. data/vendor/assets/components/selectize/dist/less/selectize.legacy.less +75 -0
  165. data/vendor/assets/components/selectize/dist/less/selectize.less +295 -0
  166. data/vendor/assets/components/selectize/karma.conf.js +107 -0
  167. data/vendor/assets/components/sifter/.bower.json +44 -0
  168. data/vendor/assets/components/sifter/.gitignore +3 -0
  169. data/vendor/assets/components/sifter/.npmignore +4 -0
  170. data/vendor/assets/components/sifter/README.md +149 -0
  171. data/vendor/assets/components/sifter/bower.json +23 -0
  172. data/vendor/assets/components/sifter/package.json +48 -0
  173. data/vendor/assets/components/sifter/sifter.js +471 -0
  174. data/vendor/assets/components/sifter/sifter.min.js +2 -0
  175. data/vendor/assets/components/sweetalert/.bower.json +36 -0
  176. data/vendor/assets/components/sweetalert/.editorconfig +11 -0
  177. data/vendor/assets/components/sweetalert/.gitignore +5 -0
  178. data/vendor/assets/components/sweetalert/.jshintrc +11 -0
  179. data/vendor/assets/components/sweetalert/.travis.yml +6 -0
  180. data/vendor/assets/components/sweetalert/LICENSE +22 -0
  181. data/vendor/assets/components/sweetalert/README.md +157 -0
  182. data/vendor/assets/components/sweetalert/bower.json +25 -0
  183. data/vendor/assets/components/sweetalert/dev/gulpfile-wrap-template.js +18 -0
  184. data/vendor/assets/components/sweetalert/dev/ie9.css +23 -0
  185. data/vendor/assets/components/sweetalert/dev/loader-animation.css +209 -0
  186. data/vendor/assets/components/sweetalert/dev/modules/default-params.js +26 -0
  187. data/vendor/assets/components/sweetalert/dev/modules/handle-click.js +128 -0
  188. data/vendor/assets/components/sweetalert/dev/modules/handle-dom.js +161 -0
  189. data/vendor/assets/components/sweetalert/dev/modules/handle-key.js +73 -0
  190. data/vendor/assets/components/sweetalert/dev/modules/handle-swal-dom.js +148 -0
  191. data/vendor/assets/components/sweetalert/dev/modules/injected-html.js +69 -0
  192. data/vendor/assets/components/sweetalert/dev/modules/set-params.js +221 -0
  193. data/vendor/assets/components/sweetalert/dev/modules/utils.js +71 -0
  194. data/vendor/assets/components/sweetalert/dev/sweetalert.es6.js +311 -0
  195. data/vendor/assets/components/sweetalert/dev/sweetalert.scss +648 -0
  196. data/vendor/assets/components/sweetalert/dist/sweetalert-dev.js +1281 -0
  197. data/vendor/assets/components/sweetalert/dist/sweetalert.css +932 -0
  198. data/vendor/assets/components/sweetalert/dist/sweetalert.min.js +1 -0
  199. data/vendor/assets/components/sweetalert/example/example.css +442 -0
  200. data/vendor/assets/components/sweetalert/example/example.scss +580 -0
  201. data/vendor/assets/components/sweetalert/example/images/logo_big.png +0 -0
  202. data/vendor/assets/components/sweetalert/example/images/logo_big@2x.png +0 -0
  203. data/vendor/assets/components/sweetalert/example/images/logo_small.png +0 -0
  204. data/vendor/assets/components/sweetalert/example/images/logo_small@2x.png +0 -0
  205. data/vendor/assets/components/sweetalert/example/images/te-logo-small.svg +12 -0
  206. data/vendor/assets/components/sweetalert/example/images/thumbs-up.jpg +0 -0
  207. data/vendor/assets/components/sweetalert/example/images/vs_icon.png +0 -0
  208. data/vendor/assets/components/sweetalert/example/images/vs_icon@2x.png +0 -0
  209. data/vendor/assets/components/sweetalert/gulpfile.js +97 -0
  210. data/vendor/assets/components/sweetalert/index.html +584 -0
  211. data/vendor/assets/components/sweetalert/package.json +45 -0
  212. data/vendor/assets/components/sweetalert/sweetalert.gif +0 -0
  213. data/vendor/assets/components/sweetalert/test/index.html +20 -0
  214. data/vendor/assets/components/sweetalert/test/tests.js +143 -0
  215. data/vendor/assets/components/sweetalert/themes/facebook/facebook.css +111 -0
  216. data/vendor/assets/components/sweetalert/themes/facebook/facebook.scss +146 -0
  217. data/vendor/assets/components/sweetalert/themes/google/google.css +115 -0
  218. data/vendor/assets/components/sweetalert/themes/google/google.scss +148 -0
  219. data/vendor/assets/components/sweetalert/themes/twitter/twitter.css +140 -0
  220. data/vendor/assets/components/sweetalert/themes/twitter/twitter.scss +177 -0
  221. metadata +214 -5
  222. data/abstracted-0.0.4.gem +0 -0
  223. data/abstracted.gemspec +0 -51
  224. data/app/views/abstract_resources/index.html.erb +0 -1
@@ -0,0 +1,3058 @@
1
+ /**
2
+ * selectize.js (v0.12.1)
3
+ * Copyright (c) 2013–2015 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 <brian@thirdroute.com>
15
+ */
16
+
17
+ /*jshint curly:false */
18
+ /*jshint browser:true */
19
+
20
+ (function(root, factory) {
21
+ if (typeof define === 'function' && define.amd) {
22
+ define(['jquery','sifter','microplugin'], factory);
23
+ } else if (typeof exports === 'object') {
24
+ module.exports = factory(require('jquery'), require('sifter'), require('microplugin'));
25
+ } else {
26
+ root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin);
27
+ }
28
+ }(this, function($, Sifter, MicroPlugin) {
29
+ 'use strict';
30
+
31
+ var highlight = function($element, pattern) {
32
+ if (typeof pattern === 'string' && !pattern.length) return;
33
+ var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern;
34
+
35
+ var highlight = function(node) {
36
+ var skip = 0;
37
+ if (node.nodeType === 3) {
38
+ var pos = node.data.search(regex);
39
+ if (pos >= 0 && node.data.length > 0) {
40
+ var match = node.data.match(regex);
41
+ var spannode = document.createElement('span');
42
+ spannode.className = 'highlight';
43
+ var middlebit = node.splitText(pos);
44
+ var endbit = middlebit.splitText(match[0].length);
45
+ var middleclone = middlebit.cloneNode(true);
46
+ spannode.appendChild(middleclone);
47
+ middlebit.parentNode.replaceChild(spannode, middlebit);
48
+ skip = 1;
49
+ }
50
+ } else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {
51
+ for (var i = 0; i < node.childNodes.length; ++i) {
52
+ i += highlight(node.childNodes[i]);
53
+ }
54
+ }
55
+ return skip;
56
+ };
57
+
58
+ return $element.each(function() {
59
+ highlight(this);
60
+ });
61
+ };
62
+
63
+ var MicroEvent = function() {};
64
+ MicroEvent.prototype = {
65
+ on: function(event, fct){
66
+ this._events = this._events || {};
67
+ this._events[event] = this._events[event] || [];
68
+ this._events[event].push(fct);
69
+ },
70
+ off: function(event, fct){
71
+ var n = arguments.length;
72
+ if (n === 0) return delete this._events;
73
+ if (n === 1) return delete this._events[event];
74
+
75
+ this._events = this._events || {};
76
+ if (event in this._events === false) return;
77
+ this._events[event].splice(this._events[event].indexOf(fct), 1);
78
+ },
79
+ trigger: function(event /* , args... */){
80
+ this._events = this._events || {};
81
+ if (event in this._events === false) return;
82
+ for (var i = 0; i < this._events[event].length; i++){
83
+ this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
84
+ }
85
+ }
86
+ };
87
+
88
+ /**
89
+ * Mixin will delegate all MicroEvent.js function in the destination object.
90
+ *
91
+ * - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent
92
+ *
93
+ * @param {object} the object which will support MicroEvent
94
+ */
95
+ MicroEvent.mixin = function(destObject){
96
+ var props = ['on', 'off', 'trigger'];
97
+ for (var i = 0; i < props.length; i++){
98
+ destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];
99
+ }
100
+ };
101
+
102
+ var IS_MAC = /Mac/.test(navigator.userAgent);
103
+
104
+ var KEY_A = 65;
105
+ var KEY_COMMA = 188;
106
+ var KEY_RETURN = 13;
107
+ var KEY_ESC = 27;
108
+ var KEY_LEFT = 37;
109
+ var KEY_UP = 38;
110
+ var KEY_P = 80;
111
+ var KEY_RIGHT = 39;
112
+ var KEY_DOWN = 40;
113
+ var KEY_N = 78;
114
+ var KEY_BACKSPACE = 8;
115
+ var KEY_DELETE = 46;
116
+ var KEY_SHIFT = 16;
117
+ var KEY_CMD = IS_MAC ? 91 : 17;
118
+ var KEY_CTRL = IS_MAC ? 18 : 17;
119
+ var KEY_TAB = 9;
120
+
121
+ var TAG_SELECT = 1;
122
+ var TAG_INPUT = 2;
123
+
124
+ // for now, android support in general is too spotty to support validity
125
+ var SUPPORTS_VALIDITY_API = !/android/i.test(window.navigator.userAgent) && !!document.createElement('form').validity;
126
+
127
+ var isset = function(object) {
128
+ return typeof object !== 'undefined';
129
+ };
130
+
131
+ /**
132
+ * Converts a scalar to its best string representation
133
+ * for hash keys and HTML attribute values.
134
+ *
135
+ * Transformations:
136
+ * 'str' -> 'str'
137
+ * null -> ''
138
+ * undefined -> ''
139
+ * true -> '1'
140
+ * false -> '0'
141
+ * 0 -> '0'
142
+ * 1 -> '1'
143
+ *
144
+ * @param {string} value
145
+ * @returns {string|null}
146
+ */
147
+ var hash_key = function(value) {
148
+ if (typeof value === 'undefined' || value === null) return null;
149
+ if (typeof value === 'boolean') return value ? '1' : '0';
150
+ return value + '';
151
+ };
152
+
153
+ /**
154
+ * Escapes a string for use within HTML.
155
+ *
156
+ * @param {string} str
157
+ * @returns {string}
158
+ */
159
+ var escape_html = function(str) {
160
+ return (str + '')
161
+ .replace(/&/g, '&amp;')
162
+ .replace(/</g, '&lt;')
163
+ .replace(/>/g, '&gt;')
164
+ .replace(/"/g, '&quot;');
165
+ };
166
+
167
+ /**
168
+ * Escapes "$" characters in replacement strings.
169
+ *
170
+ * @param {string} str
171
+ * @returns {string}
172
+ */
173
+ var escape_replace = function(str) {
174
+ return (str + '').replace(/\$/g, '$$$$');
175
+ };
176
+
177
+ var hook = {};
178
+
179
+ /**
180
+ * Wraps `method` on `self` so that `fn`
181
+ * is invoked before the original method.
182
+ *
183
+ * @param {object} self
184
+ * @param {string} method
185
+ * @param {function} fn
186
+ */
187
+ hook.before = function(self, method, fn) {
188
+ var original = self[method];
189
+ self[method] = function() {
190
+ fn.apply(self, arguments);
191
+ return original.apply(self, arguments);
192
+ };
193
+ };
194
+
195
+ /**
196
+ * Wraps `method` on `self` so that `fn`
197
+ * is invoked after the original method.
198
+ *
199
+ * @param {object} self
200
+ * @param {string} method
201
+ * @param {function} fn
202
+ */
203
+ hook.after = function(self, method, fn) {
204
+ var original = self[method];
205
+ self[method] = function() {
206
+ var result = original.apply(self, arguments);
207
+ fn.apply(self, arguments);
208
+ return result;
209
+ };
210
+ };
211
+
212
+ /**
213
+ * Wraps `fn` so that it can only be invoked once.
214
+ *
215
+ * @param {function} fn
216
+ * @returns {function}
217
+ */
218
+ var once = function(fn) {
219
+ var called = false;
220
+ return function() {
221
+ if (called) return;
222
+ called = true;
223
+ fn.apply(this, arguments);
224
+ };
225
+ };
226
+
227
+ /**
228
+ * Wraps `fn` so that it can only be called once
229
+ * every `delay` milliseconds (invoked on the falling edge).
230
+ *
231
+ * @param {function} fn
232
+ * @param {int} delay
233
+ * @returns {function}
234
+ */
235
+ var debounce = function(fn, delay) {
236
+ var timeout;
237
+ return function() {
238
+ var self = this;
239
+ var args = arguments;
240
+ window.clearTimeout(timeout);
241
+ timeout = window.setTimeout(function() {
242
+ fn.apply(self, args);
243
+ }, delay);
244
+ };
245
+ };
246
+
247
+ /**
248
+ * Debounce all fired events types listed in `types`
249
+ * while executing the provided `fn`.
250
+ *
251
+ * @param {object} self
252
+ * @param {array} types
253
+ * @param {function} fn
254
+ */
255
+ var debounce_events = function(self, types, fn) {
256
+ var type;
257
+ var trigger = self.trigger;
258
+ var event_args = {};
259
+
260
+ // override trigger method
261
+ self.trigger = function() {
262
+ var type = arguments[0];
263
+ if (types.indexOf(type) !== -1) {
264
+ event_args[type] = arguments;
265
+ } else {
266
+ return trigger.apply(self, arguments);
267
+ }
268
+ };
269
+
270
+ // invoke provided function
271
+ fn.apply(self, []);
272
+ self.trigger = trigger;
273
+
274
+ // trigger queued events
275
+ for (type in event_args) {
276
+ if (event_args.hasOwnProperty(type)) {
277
+ trigger.apply(self, event_args[type]);
278
+ }
279
+ }
280
+ };
281
+
282
+ /**
283
+ * A workaround for http://bugs.jquery.com/ticket/6696
284
+ *
285
+ * @param {object} $parent - Parent element to listen on.
286
+ * @param {string} event - Event name.
287
+ * @param {string} selector - Descendant selector to filter by.
288
+ * @param {function} fn - Event handler.
289
+ */
290
+ var watchChildEvent = function($parent, event, selector, fn) {
291
+ $parent.on(event, selector, function(e) {
292
+ var child = e.target;
293
+ while (child && child.parentNode !== $parent[0]) {
294
+ child = child.parentNode;
295
+ }
296
+ e.currentTarget = child;
297
+ return fn.apply(this, [e]);
298
+ });
299
+ };
300
+
301
+ /**
302
+ * Determines the current selection within a text input control.
303
+ * Returns an object containing:
304
+ * - start
305
+ * - length
306
+ *
307
+ * @param {object} input
308
+ * @returns {object}
309
+ */
310
+ var getSelection = function(input) {
311
+ var result = {};
312
+ if ('selectionStart' in input) {
313
+ result.start = input.selectionStart;
314
+ result.length = input.selectionEnd - result.start;
315
+ } else if (document.selection) {
316
+ input.focus();
317
+ var sel = document.selection.createRange();
318
+ var selLen = document.selection.createRange().text.length;
319
+ sel.moveStart('character', -input.value.length);
320
+ result.start = sel.text.length - selLen;
321
+ result.length = selLen;
322
+ }
323
+ return result;
324
+ };
325
+
326
+ /**
327
+ * Copies CSS properties from one element to another.
328
+ *
329
+ * @param {object} $from
330
+ * @param {object} $to
331
+ * @param {array} properties
332
+ */
333
+ var transferStyles = function($from, $to, properties) {
334
+ var i, n, styles = {};
335
+ if (properties) {
336
+ for (i = 0, n = properties.length; i < n; i++) {
337
+ styles[properties[i]] = $from.css(properties[i]);
338
+ }
339
+ } else {
340
+ styles = $from.css();
341
+ }
342
+ $to.css(styles);
343
+ };
344
+
345
+ /**
346
+ * Measures the width of a string within a
347
+ * parent element (in pixels).
348
+ *
349
+ * @param {string} str
350
+ * @param {object} $parent
351
+ * @returns {int}
352
+ */
353
+ var measureString = function(str, $parent) {
354
+ if (!str) {
355
+ return 0;
356
+ }
357
+
358
+ var $test = $('<test>').css({
359
+ position: 'absolute',
360
+ top: -99999,
361
+ left: -99999,
362
+ width: 'auto',
363
+ padding: 0,
364
+ whiteSpace: 'pre'
365
+ }).text(str).appendTo('body');
366
+
367
+ transferStyles($parent, $test, [
368
+ 'letterSpacing',
369
+ 'fontSize',
370
+ 'fontFamily',
371
+ 'fontWeight',
372
+ 'textTransform'
373
+ ]);
374
+
375
+ var width = $test.width();
376
+ $test.remove();
377
+
378
+ return width;
379
+ };
380
+
381
+ /**
382
+ * Sets up an input to grow horizontally as the user
383
+ * types. If the value is changed manually, you can
384
+ * trigger the "update" handler to resize:
385
+ *
386
+ * $input.trigger('update');
387
+ *
388
+ * @param {object} $input
389
+ */
390
+ var autoGrow = function($input) {
391
+ var currentWidth = null;
392
+
393
+ var update = function(e, options) {
394
+ var value, keyCode, printable, placeholder, width;
395
+ var shift, character, selection;
396
+ e = e || window.event || {};
397
+ options = options || {};
398
+
399
+ if (e.metaKey || e.altKey) return;
400
+ if (!options.force && $input.data('grow') === false) return;
401
+
402
+ value = $input.val();
403
+ if (e.type && e.type.toLowerCase() === 'keydown') {
404
+ keyCode = e.keyCode;
405
+ printable = (
406
+ (keyCode >= 97 && keyCode <= 122) || // a-z
407
+ (keyCode >= 65 && keyCode <= 90) || // A-Z
408
+ (keyCode >= 48 && keyCode <= 57) || // 0-9
409
+ keyCode === 32 // space
410
+ );
411
+
412
+ if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) {
413
+ selection = getSelection($input[0]);
414
+ if (selection.length) {
415
+ value = value.substring(0, selection.start) + value.substring(selection.start + selection.length);
416
+ } else if (keyCode === KEY_BACKSPACE && selection.start) {
417
+ value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1);
418
+ } else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') {
419
+ value = value.substring(0, selection.start) + value.substring(selection.start + 1);
420
+ }
421
+ } else if (printable) {
422
+ shift = e.shiftKey;
423
+ character = String.fromCharCode(e.keyCode);
424
+ if (shift) character = character.toUpperCase();
425
+ else character = character.toLowerCase();
426
+ value += character;
427
+ }
428
+ }
429
+
430
+ placeholder = $input.attr('placeholder');
431
+ if (!value && placeholder) {
432
+ value = placeholder;
433
+ }
434
+
435
+ width = measureString(value, $input) + 4;
436
+ if (width !== currentWidth) {
437
+ currentWidth = width;
438
+ $input.width(width);
439
+ $input.triggerHandler('resize');
440
+ }
441
+ };
442
+
443
+ $input.on('keydown keyup update blur', update);
444
+ update();
445
+ };
446
+
447
+ var Selectize = function($input, settings) {
448
+ var key, i, n, dir, input, self = this;
449
+ input = $input[0];
450
+ input.selectize = self;
451
+
452
+ // detect rtl environment
453
+ var computedStyle = window.getComputedStyle && window.getComputedStyle(input, null);
454
+ dir = computedStyle ? computedStyle.getPropertyValue('direction') : input.currentStyle && input.currentStyle.direction;
455
+ dir = dir || $input.parents('[dir]:first').attr('dir') || '';
456
+
457
+ // setup default state
458
+ $.extend(self, {
459
+ order : 0,
460
+ settings : settings,
461
+ $input : $input,
462
+ tabIndex : $input.attr('tabindex') || '',
463
+ tagType : input.tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT,
464
+ rtl : /rtl/i.test(dir),
465
+
466
+ eventNS : '.selectize' + (++Selectize.count),
467
+ highlightedValue : null,
468
+ isOpen : false,
469
+ isDisabled : false,
470
+ isRequired : $input.is('[required]'),
471
+ isInvalid : false,
472
+ isLocked : false,
473
+ isFocused : false,
474
+ isInputHidden : false,
475
+ isSetup : false,
476
+ isShiftDown : false,
477
+ isCmdDown : false,
478
+ isCtrlDown : false,
479
+ ignoreFocus : false,
480
+ ignoreBlur : false,
481
+ ignoreHover : false,
482
+ hasOptions : false,
483
+ currentResults : null,
484
+ lastValue : '',
485
+ caretPos : 0,
486
+ loading : 0,
487
+ loadedSearches : {},
488
+
489
+ $activeOption : null,
490
+ $activeItems : [],
491
+
492
+ optgroups : {},
493
+ options : {},
494
+ userOptions : {},
495
+ items : [],
496
+ renderCache : {},
497
+ onSearchChange : settings.loadThrottle === null ? self.onSearchChange : debounce(self.onSearchChange, settings.loadThrottle)
498
+ });
499
+
500
+ // search system
501
+ self.sifter = new Sifter(this.options, {diacritics: settings.diacritics});
502
+
503
+ // build options table
504
+ if (self.settings.options) {
505
+ for (i = 0, n = self.settings.options.length; i < n; i++) {
506
+ self.registerOption(self.settings.options[i]);
507
+ }
508
+ delete self.settings.options;
509
+ }
510
+
511
+ // build optgroup table
512
+ if (self.settings.optgroups) {
513
+ for (i = 0, n = self.settings.optgroups.length; i < n; i++) {
514
+ self.registerOptionGroup(self.settings.optgroups[i]);
515
+ }
516
+ delete self.settings.optgroups;
517
+ }
518
+
519
+ // option-dependent defaults
520
+ self.settings.mode = self.settings.mode || (self.settings.maxItems === 1 ? 'single' : 'multi');
521
+ if (typeof self.settings.hideSelected !== 'boolean') {
522
+ self.settings.hideSelected = self.settings.mode === 'multi';
523
+ }
524
+
525
+ self.initializePlugins(self.settings.plugins);
526
+ self.setupCallbacks();
527
+ self.setupTemplates();
528
+ self.setup();
529
+ };
530
+
531
+ // mixins
532
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
533
+
534
+ MicroEvent.mixin(Selectize);
535
+ MicroPlugin.mixin(Selectize);
536
+
537
+ // methods
538
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
539
+
540
+ $.extend(Selectize.prototype, {
541
+
542
+ /**
543
+ * Creates all elements and sets up event bindings.
544
+ */
545
+ setup: function() {
546
+ var self = this;
547
+ var settings = self.settings;
548
+ var eventNS = self.eventNS;
549
+ var $window = $(window);
550
+ var $document = $(document);
551
+ var $input = self.$input;
552
+
553
+ var $wrapper;
554
+ var $control;
555
+ var $control_input;
556
+ var $dropdown;
557
+ var $dropdown_content;
558
+ var $dropdown_parent;
559
+ var inputMode;
560
+ var timeout_blur;
561
+ var timeout_focus;
562
+ var classes;
563
+ var classes_plugins;
564
+
565
+ inputMode = self.settings.mode;
566
+ classes = $input.attr('class') || '';
567
+
568
+ $wrapper = $('<div>').addClass(settings.wrapperClass).addClass(classes).addClass(inputMode);
569
+ $control = $('<div>').addClass(settings.inputClass).addClass('items').appendTo($wrapper);
570
+ $control_input = $('<input type="text" autocomplete="off" />').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);
571
+ $dropdown_parent = $(settings.dropdownParent || $wrapper);
572
+ $dropdown = $('<div>').addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdown_parent);
573
+ $dropdown_content = $('<div>').addClass(settings.dropdownContentClass).appendTo($dropdown);
574
+
575
+ if(self.settings.copyClassesToDropdown) {
576
+ $dropdown.addClass(classes);
577
+ }
578
+
579
+ $wrapper.css({
580
+ width: $input[0].style.width
581
+ });
582
+
583
+ if (self.plugins.names.length) {
584
+ classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');
585
+ $wrapper.addClass(classes_plugins);
586
+ $dropdown.addClass(classes_plugins);
587
+ }
588
+
589
+ if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) {
590
+ $input.attr('multiple', 'multiple');
591
+ }
592
+
593
+ if (self.settings.placeholder) {
594
+ $control_input.attr('placeholder', settings.placeholder);
595
+ }
596
+
597
+ // if splitOn was not passed in, construct it from the delimiter to allow pasting universally
598
+ if (!self.settings.splitOn && self.settings.delimiter) {
599
+ var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
600
+ self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*');
601
+ }
602
+
603
+ if ($input.attr('autocorrect')) {
604
+ $control_input.attr('autocorrect', $input.attr('autocorrect'));
605
+ }
606
+
607
+ if ($input.attr('autocapitalize')) {
608
+ $control_input.attr('autocapitalize', $input.attr('autocapitalize'));
609
+ }
610
+
611
+ self.$wrapper = $wrapper;
612
+ self.$control = $control;
613
+ self.$control_input = $control_input;
614
+ self.$dropdown = $dropdown;
615
+ self.$dropdown_content = $dropdown_content;
616
+
617
+ $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); });
618
+ $dropdown.on('mousedown click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); });
619
+ watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); });
620
+ autoGrow($control_input);
621
+
622
+ $control.on({
623
+ mousedown : function() { return self.onMouseDown.apply(self, arguments); },
624
+ click : function() { return self.onClick.apply(self, arguments); }
625
+ });
626
+
627
+ $control_input.on({
628
+ mousedown : function(e) { e.stopPropagation(); },
629
+ keydown : function() { return self.onKeyDown.apply(self, arguments); },
630
+ keyup : function() { return self.onKeyUp.apply(self, arguments); },
631
+ keypress : function() { return self.onKeyPress.apply(self, arguments); },
632
+ resize : function() { self.positionDropdown.apply(self, []); },
633
+ blur : function() { return self.onBlur.apply(self, arguments); },
634
+ focus : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); },
635
+ paste : function() { return self.onPaste.apply(self, arguments); }
636
+ });
637
+
638
+ $document.on('keydown' + eventNS, function(e) {
639
+ self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];
640
+ self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];
641
+ self.isShiftDown = e.shiftKey;
642
+ });
643
+
644
+ $document.on('keyup' + eventNS, function(e) {
645
+ if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;
646
+ if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;
647
+ if (e.keyCode === KEY_CMD) self.isCmdDown = false;
648
+ });
649
+
650
+ $document.on('mousedown' + eventNS, function(e) {
651
+ if (self.isFocused) {
652
+ // prevent events on the dropdown scrollbar from causing the control to blur
653
+ if (e.target === self.$dropdown[0] || e.target.parentNode === self.$dropdown[0]) {
654
+ return false;
655
+ }
656
+ // blur on click outside
657
+ if (!self.$control.has(e.target).length && e.target !== self.$control[0]) {
658
+ self.blur(e.target);
659
+ }
660
+ }
661
+ });
662
+
663
+ $window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() {
664
+ if (self.isOpen) {
665
+ self.positionDropdown.apply(self, arguments);
666
+ }
667
+ });
668
+ $window.on('mousemove' + eventNS, function() {
669
+ self.ignoreHover = false;
670
+ });
671
+
672
+ // store original children and tab index so that they can be
673
+ // restored when the destroy() method is called.
674
+ this.revertSettings = {
675
+ $children : $input.children().detach(),
676
+ tabindex : $input.attr('tabindex')
677
+ };
678
+
679
+ $input.attr('tabindex', -1).hide().after(self.$wrapper);
680
+
681
+ if ($.isArray(settings.items)) {
682
+ self.setValue(settings.items);
683
+ delete settings.items;
684
+ }
685
+
686
+ // feature detect for the validation API
687
+ if (SUPPORTS_VALIDITY_API) {
688
+ $input.on('invalid' + eventNS, function(e) {
689
+ e.preventDefault();
690
+ self.isInvalid = true;
691
+ self.refreshState();
692
+ });
693
+ }
694
+
695
+ self.updateOriginalInput();
696
+ self.refreshItems();
697
+ self.refreshState();
698
+ self.updatePlaceholder();
699
+ self.isSetup = true;
700
+
701
+ if ($input.is(':disabled')) {
702
+ self.disable();
703
+ }
704
+
705
+ self.on('change', this.onChange);
706
+
707
+ $input.data('selectize', self);
708
+ $input.addClass('selectized');
709
+ self.trigger('initialize');
710
+
711
+ // preload options
712
+ if (settings.preload === true) {
713
+ self.onSearchChange('');
714
+ }
715
+
716
+ },
717
+
718
+ /**
719
+ * Sets up default rendering functions.
720
+ */
721
+ setupTemplates: function() {
722
+ var self = this;
723
+ var field_label = self.settings.labelField;
724
+ var field_optgroup = self.settings.optgroupLabelField;
725
+
726
+ var templates = {
727
+ 'optgroup': function(data) {
728
+ return '<div class="optgroup">' + data.html + '</div>';
729
+ },
730
+ 'optgroup_header': function(data, escape) {
731
+ return '<div class="optgroup-header">' + escape(data[field_optgroup]) + '</div>';
732
+ },
733
+ 'option': function(data, escape) {
734
+ return '<div class="option">' + escape(data[field_label]) + '</div>';
735
+ },
736
+ 'item': function(data, escape) {
737
+ return '<div class="item">' + escape(data[field_label]) + '</div>';
738
+ },
739
+ 'option_create': function(data, escape) {
740
+ return '<div class="create">Add <strong>' + escape(data.input) + '</strong>&hellip;</div>';
741
+ }
742
+ };
743
+
744
+ self.settings.render = $.extend({}, templates, self.settings.render);
745
+ },
746
+
747
+ /**
748
+ * Maps fired events to callbacks provided
749
+ * in the settings used when creating the control.
750
+ */
751
+ setupCallbacks: function() {
752
+ var key, fn, callbacks = {
753
+ 'initialize' : 'onInitialize',
754
+ 'change' : 'onChange',
755
+ 'item_add' : 'onItemAdd',
756
+ 'item_remove' : 'onItemRemove',
757
+ 'clear' : 'onClear',
758
+ 'option_add' : 'onOptionAdd',
759
+ 'option_remove' : 'onOptionRemove',
760
+ 'option_clear' : 'onOptionClear',
761
+ 'optgroup_add' : 'onOptionGroupAdd',
762
+ 'optgroup_remove' : 'onOptionGroupRemove',
763
+ 'optgroup_clear' : 'onOptionGroupClear',
764
+ 'dropdown_open' : 'onDropdownOpen',
765
+ 'dropdown_close' : 'onDropdownClose',
766
+ 'type' : 'onType',
767
+ 'load' : 'onLoad',
768
+ 'focus' : 'onFocus',
769
+ 'blur' : 'onBlur'
770
+ };
771
+
772
+ for (key in callbacks) {
773
+ if (callbacks.hasOwnProperty(key)) {
774
+ fn = this.settings[callbacks[key]];
775
+ if (fn) this.on(key, fn);
776
+ }
777
+ }
778
+ },
779
+
780
+ /**
781
+ * Triggered when the main control element
782
+ * has a click event.
783
+ *
784
+ * @param {object} e
785
+ * @return {boolean}
786
+ */
787
+ onClick: function(e) {
788
+ var self = this;
789
+
790
+ // necessary for mobile webkit devices (manual focus triggering
791
+ // is ignored unless invoked within a click event)
792
+ if (!self.isFocused) {
793
+ self.focus();
794
+ e.preventDefault();
795
+ }
796
+ },
797
+
798
+ /**
799
+ * Triggered when the main control element
800
+ * has a mouse down event.
801
+ *
802
+ * @param {object} e
803
+ * @return {boolean}
804
+ */
805
+ onMouseDown: function(e) {
806
+ var self = this;
807
+ var defaultPrevented = e.isDefaultPrevented();
808
+ var $target = $(e.target);
809
+
810
+ if (self.isFocused) {
811
+ // retain focus by preventing native handling. if the
812
+ // event target is the input it should not be modified.
813
+ // otherwise, text selection within the input won't work.
814
+ if (e.target !== self.$control_input[0]) {
815
+ if (self.settings.mode === 'single') {
816
+ // toggle dropdown
817
+ self.isOpen ? self.close() : self.open();
818
+ } else if (!defaultPrevented) {
819
+ self.setActiveItem(null);
820
+ }
821
+ return false;
822
+ }
823
+ } else {
824
+ // give control focus
825
+ if (!defaultPrevented) {
826
+ window.setTimeout(function() {
827
+ self.focus();
828
+ }, 0);
829
+ }
830
+ }
831
+ },
832
+
833
+ /**
834
+ * Triggered when the value of the control has been changed.
835
+ * This should propagate the event to the original DOM
836
+ * input / select element.
837
+ */
838
+ onChange: function() {
839
+ this.$input.trigger('change');
840
+ },
841
+
842
+ /**
843
+ * Triggered on <input> paste.
844
+ *
845
+ * @param {object} e
846
+ * @returns {boolean}
847
+ */
848
+ onPaste: function(e) {
849
+ var self = this;
850
+ if (self.isFull() || self.isInputHidden || self.isLocked) {
851
+ e.preventDefault();
852
+ } else {
853
+ // If a regex or string is included, this will split the pasted
854
+ // input and create Items for each separate value
855
+ if (self.settings.splitOn) {
856
+ setTimeout(function() {
857
+ var splitInput = $.trim(self.$control_input.val() || '').split(self.settings.splitOn);
858
+ for (var i = 0, n = splitInput.length; i < n; i++) {
859
+ self.createItem(splitInput[i]);
860
+ }
861
+ }, 0);
862
+ }
863
+ }
864
+ },
865
+
866
+ /**
867
+ * Triggered on <input> keypress.
868
+ *
869
+ * @param {object} e
870
+ * @returns {boolean}
871
+ */
872
+ onKeyPress: function(e) {
873
+ if (this.isLocked) return e && e.preventDefault();
874
+ var character = String.fromCharCode(e.keyCode || e.which);
875
+ if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) {
876
+ this.createItem();
877
+ e.preventDefault();
878
+ return false;
879
+ }
880
+ },
881
+
882
+ /**
883
+ * Triggered on <input> keydown.
884
+ *
885
+ * @param {object} e
886
+ * @returns {boolean}
887
+ */
888
+ onKeyDown: function(e) {
889
+ var isInput = e.target === this.$control_input[0];
890
+ var self = this;
891
+
892
+ if (self.isLocked) {
893
+ if (e.keyCode !== KEY_TAB) {
894
+ e.preventDefault();
895
+ }
896
+ return;
897
+ }
898
+
899
+ switch (e.keyCode) {
900
+ case KEY_A:
901
+ if (self.isCmdDown) {
902
+ self.selectAll();
903
+ return;
904
+ }
905
+ break;
906
+ case KEY_ESC:
907
+ if (self.isOpen) {
908
+ e.preventDefault();
909
+ e.stopPropagation();
910
+ self.close();
911
+ }
912
+ return;
913
+ case KEY_N:
914
+ if (!e.ctrlKey || e.altKey) break;
915
+ case KEY_DOWN:
916
+ if (!self.isOpen && self.hasOptions) {
917
+ self.open();
918
+ } else if (self.$activeOption) {
919
+ self.ignoreHover = true;
920
+ var $next = self.getAdjacentOption(self.$activeOption, 1);
921
+ if ($next.length) self.setActiveOption($next, true, true);
922
+ }
923
+ e.preventDefault();
924
+ return;
925
+ case KEY_P:
926
+ if (!e.ctrlKey || e.altKey) break;
927
+ case KEY_UP:
928
+ if (self.$activeOption) {
929
+ self.ignoreHover = true;
930
+ var $prev = self.getAdjacentOption(self.$activeOption, -1);
931
+ if ($prev.length) self.setActiveOption($prev, true, true);
932
+ }
933
+ e.preventDefault();
934
+ return;
935
+ case KEY_RETURN:
936
+ if (self.isOpen && self.$activeOption) {
937
+ self.onOptionSelect({currentTarget: self.$activeOption});
938
+ e.preventDefault();
939
+ }
940
+ return;
941
+ case KEY_LEFT:
942
+ self.advanceSelection(-1, e);
943
+ return;
944
+ case KEY_RIGHT:
945
+ self.advanceSelection(1, e);
946
+ return;
947
+ case KEY_TAB:
948
+ if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {
949
+ self.onOptionSelect({currentTarget: self.$activeOption});
950
+
951
+ // Default behaviour is to jump to the next field, we only want this
952
+ // if the current field doesn't accept any more entries
953
+ if (!self.isFull()) {
954
+ e.preventDefault();
955
+ }
956
+ }
957
+ if (self.settings.create && self.createItem()) {
958
+ e.preventDefault();
959
+ }
960
+ return;
961
+ case KEY_BACKSPACE:
962
+ case KEY_DELETE:
963
+ self.deleteSelection(e);
964
+ return;
965
+ }
966
+
967
+ if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) {
968
+ e.preventDefault();
969
+ return;
970
+ }
971
+ },
972
+
973
+ /**
974
+ * Triggered on <input> keyup.
975
+ *
976
+ * @param {object} e
977
+ * @returns {boolean}
978
+ */
979
+ onKeyUp: function(e) {
980
+ var self = this;
981
+
982
+ if (self.isLocked) return e && e.preventDefault();
983
+ var value = self.$control_input.val() || '';
984
+ if (self.lastValue !== value) {
985
+ self.lastValue = value;
986
+ self.onSearchChange(value);
987
+ self.refreshOptions();
988
+ self.trigger('type', value);
989
+ }
990
+ },
991
+
992
+ /**
993
+ * Invokes the user-provide option provider / loader.
994
+ *
995
+ * Note: this function is debounced in the Selectize
996
+ * constructor (by `settings.loadDelay` milliseconds)
997
+ *
998
+ * @param {string} value
999
+ */
1000
+ onSearchChange: function(value) {
1001
+ var self = this;
1002
+ var fn = self.settings.load;
1003
+ if (!fn) return;
1004
+ if (self.loadedSearches.hasOwnProperty(value)) return;
1005
+ self.loadedSearches[value] = true;
1006
+ self.load(function(callback) {
1007
+ fn.apply(self, [value, callback]);
1008
+ });
1009
+ },
1010
+
1011
+ /**
1012
+ * Triggered on <input> focus.
1013
+ *
1014
+ * @param {object} e (optional)
1015
+ * @returns {boolean}
1016
+ */
1017
+ onFocus: function(e) {
1018
+ var self = this;
1019
+ var wasFocused = self.isFocused;
1020
+
1021
+ if (self.isDisabled) {
1022
+ self.blur();
1023
+ e && e.preventDefault();
1024
+ return false;
1025
+ }
1026
+
1027
+ if (self.ignoreFocus) return;
1028
+ self.isFocused = true;
1029
+ if (self.settings.preload === 'focus') self.onSearchChange('');
1030
+
1031
+ if (!wasFocused) self.trigger('focus');
1032
+
1033
+ if (!self.$activeItems.length) {
1034
+ self.showInput();
1035
+ self.setActiveItem(null);
1036
+ self.refreshOptions(!!self.settings.openOnFocus);
1037
+ }
1038
+
1039
+ self.refreshState();
1040
+ },
1041
+
1042
+ /**
1043
+ * Triggered on <input> blur.
1044
+ *
1045
+ * @param {object} e
1046
+ * @param {Element} dest
1047
+ */
1048
+ onBlur: function(e, dest) {
1049
+ var self = this;
1050
+ if (!self.isFocused) return;
1051
+ self.isFocused = false;
1052
+
1053
+ if (self.ignoreFocus) {
1054
+ return;
1055
+ } else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) {
1056
+ // necessary to prevent IE closing the dropdown when the scrollbar is clicked
1057
+ self.ignoreBlur = true;
1058
+ self.onFocus(e);
1059
+ return;
1060
+ }
1061
+
1062
+ var deactivate = function() {
1063
+ self.close();
1064
+ self.setTextboxValue('');
1065
+ self.setActiveItem(null);
1066
+ self.setActiveOption(null);
1067
+ self.setCaret(self.items.length);
1068
+ self.refreshState();
1069
+
1070
+ // IE11 bug: element still marked as active
1071
+ (dest || document.body).focus();
1072
+
1073
+ self.ignoreFocus = false;
1074
+ self.trigger('blur');
1075
+ };
1076
+
1077
+ self.ignoreFocus = true;
1078
+ if (self.settings.create && self.settings.createOnBlur) {
1079
+ self.createItem(null, false, deactivate);
1080
+ } else {
1081
+ deactivate();
1082
+ }
1083
+ },
1084
+
1085
+ /**
1086
+ * Triggered when the user rolls over
1087
+ * an option in the autocomplete dropdown menu.
1088
+ *
1089
+ * @param {object} e
1090
+ * @returns {boolean}
1091
+ */
1092
+ onOptionHover: function(e) {
1093
+ if (this.ignoreHover) return;
1094
+ this.setActiveOption(e.currentTarget, false);
1095
+ },
1096
+
1097
+ /**
1098
+ * Triggered when the user clicks on an option
1099
+ * in the autocomplete dropdown menu.
1100
+ *
1101
+ * @param {object} e
1102
+ * @returns {boolean}
1103
+ */
1104
+ onOptionSelect: function(e) {
1105
+ var value, $target, $option, self = this;
1106
+
1107
+ if (e.preventDefault) {
1108
+ e.preventDefault();
1109
+ e.stopPropagation();
1110
+ }
1111
+
1112
+ $target = $(e.currentTarget);
1113
+ if ($target.hasClass('create')) {
1114
+ self.createItem(null, function() {
1115
+ if (self.settings.closeAfterSelect) {
1116
+ self.close();
1117
+ }
1118
+ });
1119
+ } else {
1120
+ value = $target.attr('data-value');
1121
+ if (typeof value !== 'undefined') {
1122
+ self.lastQuery = null;
1123
+ self.setTextboxValue('');
1124
+ self.addItem(value);
1125
+ if (self.settings.closeAfterSelect) {
1126
+ self.close();
1127
+ } else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) {
1128
+ self.setActiveOption(self.getOption(value));
1129
+ }
1130
+ }
1131
+ }
1132
+ },
1133
+
1134
+ /**
1135
+ * Triggered when the user clicks on an item
1136
+ * that has been selected.
1137
+ *
1138
+ * @param {object} e
1139
+ * @returns {boolean}
1140
+ */
1141
+ onItemSelect: function(e) {
1142
+ var self = this;
1143
+
1144
+ if (self.isLocked) return;
1145
+ if (self.settings.mode === 'multi') {
1146
+ e.preventDefault();
1147
+ self.setActiveItem(e.currentTarget, e);
1148
+ }
1149
+ },
1150
+
1151
+ /**
1152
+ * Invokes the provided method that provides
1153
+ * results to a callback---which are then added
1154
+ * as options to the control.
1155
+ *
1156
+ * @param {function} fn
1157
+ */
1158
+ load: function(fn) {
1159
+ var self = this;
1160
+ var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);
1161
+
1162
+ self.loading++;
1163
+ fn.apply(self, [function(results) {
1164
+ self.loading = Math.max(self.loading - 1, 0);
1165
+ if (results && results.length) {
1166
+ self.addOption(results);
1167
+ self.refreshOptions(self.isFocused && !self.isInputHidden);
1168
+ }
1169
+ if (!self.loading) {
1170
+ $wrapper.removeClass(self.settings.loadingClass);
1171
+ }
1172
+ self.trigger('load', results);
1173
+ }]);
1174
+ },
1175
+
1176
+ /**
1177
+ * Sets the input field of the control to the specified value.
1178
+ *
1179
+ * @param {string} value
1180
+ */
1181
+ setTextboxValue: function(value) {
1182
+ var $input = this.$control_input;
1183
+ var changed = $input.val() !== value;
1184
+ if (changed) {
1185
+ $input.val(value).triggerHandler('update');
1186
+ this.lastValue = value;
1187
+ }
1188
+ },
1189
+
1190
+ /**
1191
+ * Returns the value of the control. If multiple items
1192
+ * can be selected (e.g. <select multiple>), this returns
1193
+ * an array. If only one item can be selected, this
1194
+ * returns a string.
1195
+ *
1196
+ * @returns {mixed}
1197
+ */
1198
+ getValue: function() {
1199
+ if (this.tagType === TAG_SELECT && this.$input.attr('multiple')) {
1200
+ return this.items;
1201
+ } else {
1202
+ return this.items.join(this.settings.delimiter);
1203
+ }
1204
+ },
1205
+
1206
+ /**
1207
+ * Resets the selected items to the given value.
1208
+ *
1209
+ * @param {mixed} value
1210
+ */
1211
+ setValue: function(value, silent) {
1212
+ var events = silent ? [] : ['change'];
1213
+
1214
+ debounce_events(this, events, function() {
1215
+ this.clear(silent);
1216
+ this.addItems(value, silent);
1217
+ });
1218
+ },
1219
+
1220
+ /**
1221
+ * Sets the selected item.
1222
+ *
1223
+ * @param {object} $item
1224
+ * @param {object} e (optional)
1225
+ */
1226
+ setActiveItem: function($item, e) {
1227
+ var self = this;
1228
+ var eventName;
1229
+ var i, idx, begin, end, item, swap;
1230
+ var $last;
1231
+
1232
+ if (self.settings.mode === 'single') return;
1233
+ $item = $($item);
1234
+
1235
+ // clear the active selection
1236
+ if (!$item.length) {
1237
+ $(self.$activeItems).removeClass('active');
1238
+ self.$activeItems = [];
1239
+ if (self.isFocused) {
1240
+ self.showInput();
1241
+ }
1242
+ return;
1243
+ }
1244
+
1245
+ // modify selection
1246
+ eventName = e && e.type.toLowerCase();
1247
+
1248
+ if (eventName === 'mousedown' && self.isShiftDown && self.$activeItems.length) {
1249
+ $last = self.$control.children('.active:last');
1250
+ begin = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$last[0]]);
1251
+ end = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$item[0]]);
1252
+ if (begin > end) {
1253
+ swap = begin;
1254
+ begin = end;
1255
+ end = swap;
1256
+ }
1257
+ for (i = begin; i <= end; i++) {
1258
+ item = self.$control[0].childNodes[i];
1259
+ if (self.$activeItems.indexOf(item) === -1) {
1260
+ $(item).addClass('active');
1261
+ self.$activeItems.push(item);
1262
+ }
1263
+ }
1264
+ e.preventDefault();
1265
+ } else if ((eventName === 'mousedown' && self.isCtrlDown) || (eventName === 'keydown' && this.isShiftDown)) {
1266
+ if ($item.hasClass('active')) {
1267
+ idx = self.$activeItems.indexOf($item[0]);
1268
+ self.$activeItems.splice(idx, 1);
1269
+ $item.removeClass('active');
1270
+ } else {
1271
+ self.$activeItems.push($item.addClass('active')[0]);
1272
+ }
1273
+ } else {
1274
+ $(self.$activeItems).removeClass('active');
1275
+ self.$activeItems = [$item.addClass('active')[0]];
1276
+ }
1277
+
1278
+ // ensure control has focus
1279
+ self.hideInput();
1280
+ if (!this.isFocused) {
1281
+ self.focus();
1282
+ }
1283
+ },
1284
+
1285
+ /**
1286
+ * Sets the selected item in the dropdown menu
1287
+ * of available options.
1288
+ *
1289
+ * @param {object} $object
1290
+ * @param {boolean} scroll
1291
+ * @param {boolean} animate
1292
+ */
1293
+ setActiveOption: function($option, scroll, animate) {
1294
+ var height_menu, height_item, y;
1295
+ var scroll_top, scroll_bottom;
1296
+ var self = this;
1297
+
1298
+ if (self.$activeOption) self.$activeOption.removeClass('active');
1299
+ self.$activeOption = null;
1300
+
1301
+ $option = $($option);
1302
+ if (!$option.length) return;
1303
+
1304
+ self.$activeOption = $option.addClass('active');
1305
+
1306
+ if (scroll || !isset(scroll)) {
1307
+
1308
+ height_menu = self.$dropdown_content.height();
1309
+ height_item = self.$activeOption.outerHeight(true);
1310
+ scroll = self.$dropdown_content.scrollTop() || 0;
1311
+ y = self.$activeOption.offset().top - self.$dropdown_content.offset().top + scroll;
1312
+ scroll_top = y;
1313
+ scroll_bottom = y - height_menu + height_item;
1314
+
1315
+ if (y + height_item > height_menu + scroll) {
1316
+ self.$dropdown_content.stop().animate({scrollTop: scroll_bottom}, animate ? self.settings.scrollDuration : 0);
1317
+ } else if (y < scroll) {
1318
+ self.$dropdown_content.stop().animate({scrollTop: scroll_top}, animate ? self.settings.scrollDuration : 0);
1319
+ }
1320
+
1321
+ }
1322
+ },
1323
+
1324
+ /**
1325
+ * Selects all items (CTRL + A).
1326
+ */
1327
+ selectAll: function() {
1328
+ var self = this;
1329
+ if (self.settings.mode === 'single') return;
1330
+
1331
+ self.$activeItems = Array.prototype.slice.apply(self.$control.children(':not(input)').addClass('active'));
1332
+ if (self.$activeItems.length) {
1333
+ self.hideInput();
1334
+ self.close();
1335
+ }
1336
+ self.focus();
1337
+ },
1338
+
1339
+ /**
1340
+ * Hides the input element out of view, while
1341
+ * retaining its focus.
1342
+ */
1343
+ hideInput: function() {
1344
+ var self = this;
1345
+
1346
+ self.setTextboxValue('');
1347
+ self.$control_input.css({opacity: 0, position: 'absolute', left: self.rtl ? 10000 : -10000});
1348
+ self.isInputHidden = true;
1349
+ },
1350
+
1351
+ /**
1352
+ * Restores input visibility.
1353
+ */
1354
+ showInput: function() {
1355
+ this.$control_input.css({opacity: 1, position: 'relative', left: 0});
1356
+ this.isInputHidden = false;
1357
+ },
1358
+
1359
+ /**
1360
+ * Gives the control focus.
1361
+ */
1362
+ focus: function() {
1363
+ var self = this;
1364
+ if (self.isDisabled) return;
1365
+
1366
+ self.ignoreFocus = true;
1367
+ self.$control_input[0].focus();
1368
+ window.setTimeout(function() {
1369
+ self.ignoreFocus = false;
1370
+ self.onFocus();
1371
+ }, 0);
1372
+ },
1373
+
1374
+ /**
1375
+ * Forces the control out of focus.
1376
+ *
1377
+ * @param {Element} dest
1378
+ */
1379
+ blur: function(dest) {
1380
+ this.$control_input[0].blur();
1381
+ this.onBlur(null, dest);
1382
+ },
1383
+
1384
+ /**
1385
+ * Returns a function that scores an object
1386
+ * to show how good of a match it is to the
1387
+ * provided query.
1388
+ *
1389
+ * @param {string} query
1390
+ * @param {object} options
1391
+ * @return {function}
1392
+ */
1393
+ getScoreFunction: function(query) {
1394
+ return this.sifter.getScoreFunction(query, this.getSearchOptions());
1395
+ },
1396
+
1397
+ /**
1398
+ * Returns search options for sifter (the system
1399
+ * for scoring and sorting results).
1400
+ *
1401
+ * @see https://github.com/brianreavis/sifter.js
1402
+ * @return {object}
1403
+ */
1404
+ getSearchOptions: function() {
1405
+ var settings = this.settings;
1406
+ var sort = settings.sortField;
1407
+ if (typeof sort === 'string') {
1408
+ sort = [{field: sort}];
1409
+ }
1410
+
1411
+ return {
1412
+ fields : settings.searchField,
1413
+ conjunction : settings.searchConjunction,
1414
+ sort : sort
1415
+ };
1416
+ },
1417
+
1418
+ /**
1419
+ * Searches through available options and returns
1420
+ * a sorted array of matches.
1421
+ *
1422
+ * Returns an object containing:
1423
+ *
1424
+ * - query {string}
1425
+ * - tokens {array}
1426
+ * - total {int}
1427
+ * - items {array}
1428
+ *
1429
+ * @param {string} query
1430
+ * @returns {object}
1431
+ */
1432
+ search: function(query) {
1433
+ var i, value, score, result, calculateScore;
1434
+ var self = this;
1435
+ var settings = self.settings;
1436
+ var options = this.getSearchOptions();
1437
+
1438
+ // validate user-provided result scoring function
1439
+ if (settings.score) {
1440
+ calculateScore = self.settings.score.apply(this, [query]);
1441
+ if (typeof calculateScore !== 'function') {
1442
+ throw new Error('Selectize "score" setting must be a function that returns a function');
1443
+ }
1444
+ }
1445
+
1446
+ // perform search
1447
+ if (query !== self.lastQuery) {
1448
+ self.lastQuery = query;
1449
+ result = self.sifter.search(query, $.extend(options, {score: calculateScore}));
1450
+ self.currentResults = result;
1451
+ } else {
1452
+ result = $.extend(true, {}, self.currentResults);
1453
+ }
1454
+
1455
+ // filter out selected items
1456
+ if (settings.hideSelected) {
1457
+ for (i = result.items.length - 1; i >= 0; i--) {
1458
+ if (self.items.indexOf(hash_key(result.items[i].id)) !== -1) {
1459
+ result.items.splice(i, 1);
1460
+ }
1461
+ }
1462
+ }
1463
+
1464
+ return result;
1465
+ },
1466
+
1467
+ /**
1468
+ * Refreshes the list of available options shown
1469
+ * in the autocomplete dropdown menu.
1470
+ *
1471
+ * @param {boolean} triggerDropdown
1472
+ */
1473
+ refreshOptions: function(triggerDropdown) {
1474
+ var i, j, k, n, groups, groups_order, option, option_html, optgroup, optgroups, html, html_children, has_create_option;
1475
+ var $active, $active_before, $create;
1476
+
1477
+ if (typeof triggerDropdown === 'undefined') {
1478
+ triggerDropdown = true;
1479
+ }
1480
+
1481
+ var self = this;
1482
+ var query = $.trim(self.$control_input.val());
1483
+ var results = self.search(query);
1484
+ var $dropdown_content = self.$dropdown_content;
1485
+ var active_before = self.$activeOption && hash_key(self.$activeOption.attr('data-value'));
1486
+
1487
+ // build markup
1488
+ n = results.items.length;
1489
+ if (typeof self.settings.maxOptions === 'number') {
1490
+ n = Math.min(n, self.settings.maxOptions);
1491
+ }
1492
+
1493
+ // render and group available options individually
1494
+ groups = {};
1495
+ groups_order = [];
1496
+
1497
+ for (i = 0; i < n; i++) {
1498
+ option = self.options[results.items[i].id];
1499
+ option_html = self.render('option', option);
1500
+ optgroup = option[self.settings.optgroupField] || '';
1501
+ optgroups = $.isArray(optgroup) ? optgroup : [optgroup];
1502
+
1503
+ for (j = 0, k = optgroups && optgroups.length; j < k; j++) {
1504
+ optgroup = optgroups[j];
1505
+ if (!self.optgroups.hasOwnProperty(optgroup)) {
1506
+ optgroup = '';
1507
+ }
1508
+ if (!groups.hasOwnProperty(optgroup)) {
1509
+ groups[optgroup] = [];
1510
+ groups_order.push(optgroup);
1511
+ }
1512
+ groups[optgroup].push(option_html);
1513
+ }
1514
+ }
1515
+
1516
+ // sort optgroups
1517
+ if (this.settings.lockOptgroupOrder) {
1518
+ groups_order.sort(function(a, b) {
1519
+ var a_order = self.optgroups[a].$order || 0;
1520
+ var b_order = self.optgroups[b].$order || 0;
1521
+ return a_order - b_order;
1522
+ });
1523
+ }
1524
+
1525
+ // render optgroup headers & join groups
1526
+ html = [];
1527
+ for (i = 0, n = groups_order.length; i < n; i++) {
1528
+ optgroup = groups_order[i];
1529
+ if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].length) {
1530
+ // render the optgroup header and options within it,
1531
+ // then pass it to the wrapper template
1532
+ html_children = self.render('optgroup_header', self.optgroups[optgroup]) || '';
1533
+ html_children += groups[optgroup].join('');
1534
+ html.push(self.render('optgroup', $.extend({}, self.optgroups[optgroup], {
1535
+ html: html_children
1536
+ })));
1537
+ } else {
1538
+ html.push(groups[optgroup].join(''));
1539
+ }
1540
+ }
1541
+
1542
+ $dropdown_content.html(html.join(''));
1543
+
1544
+ // highlight matching terms inline
1545
+ if (self.settings.highlight && results.query.length && results.tokens.length) {
1546
+ for (i = 0, n = results.tokens.length; i < n; i++) {
1547
+ highlight($dropdown_content, results.tokens[i].regex);
1548
+ }
1549
+ }
1550
+
1551
+ // add "selected" class to selected options
1552
+ if (!self.settings.hideSelected) {
1553
+ for (i = 0, n = self.items.length; i < n; i++) {
1554
+ self.getOption(self.items[i]).addClass('selected');
1555
+ }
1556
+ }
1557
+
1558
+ // add create option
1559
+ has_create_option = self.canCreate(query);
1560
+ if (has_create_option) {
1561
+ $dropdown_content.prepend(self.render('option_create', {input: query}));
1562
+ $create = $($dropdown_content[0].childNodes[0]);
1563
+ }
1564
+
1565
+ // activate
1566
+ self.hasOptions = results.items.length > 0 || has_create_option;
1567
+ if (self.hasOptions) {
1568
+ if (results.items.length > 0) {
1569
+ $active_before = active_before && self.getOption(active_before);
1570
+ if ($active_before && $active_before.length) {
1571
+ $active = $active_before;
1572
+ } else if (self.settings.mode === 'single' && self.items.length) {
1573
+ $active = self.getOption(self.items[0]);
1574
+ }
1575
+ if (!$active || !$active.length) {
1576
+ if ($create && !self.settings.addPrecedence) {
1577
+ $active = self.getAdjacentOption($create, 1);
1578
+ } else {
1579
+ $active = $dropdown_content.find('[data-selectable]:first');
1580
+ }
1581
+ }
1582
+ } else {
1583
+ $active = $create;
1584
+ }
1585
+ self.setActiveOption($active);
1586
+ if (triggerDropdown && !self.isOpen) { self.open(); }
1587
+ } else {
1588
+ self.setActiveOption(null);
1589
+ if (triggerDropdown && self.isOpen) { self.close(); }
1590
+ }
1591
+ },
1592
+
1593
+ /**
1594
+ * Adds an available option. If it already exists,
1595
+ * nothing will happen. Note: this does not refresh
1596
+ * the options list dropdown (use `refreshOptions`
1597
+ * for that).
1598
+ *
1599
+ * Usage:
1600
+ *
1601
+ * this.addOption(data)
1602
+ *
1603
+ * @param {object|array} data
1604
+ */
1605
+ addOption: function(data) {
1606
+ var i, n, value, self = this;
1607
+
1608
+ if ($.isArray(data)) {
1609
+ for (i = 0, n = data.length; i < n; i++) {
1610
+ self.addOption(data[i]);
1611
+ }
1612
+ return;
1613
+ }
1614
+
1615
+ if (value = self.registerOption(data)) {
1616
+ self.userOptions[value] = true;
1617
+ self.lastQuery = null;
1618
+ self.trigger('option_add', value, data);
1619
+ }
1620
+ },
1621
+
1622
+ /**
1623
+ * Registers an option to the pool of options.
1624
+ *
1625
+ * @param {object} data
1626
+ * @return {boolean|string}
1627
+ */
1628
+ registerOption: function(data) {
1629
+ var key = hash_key(data[this.settings.valueField]);
1630
+ if (!key || this.options.hasOwnProperty(key)) return false;
1631
+ data.$order = data.$order || ++this.order;
1632
+ this.options[key] = data;
1633
+ return key;
1634
+ },
1635
+
1636
+ /**
1637
+ * Registers an option group to the pool of option groups.
1638
+ *
1639
+ * @param {object} data
1640
+ * @return {boolean|string}
1641
+ */
1642
+ registerOptionGroup: function(data) {
1643
+ var key = hash_key(data[this.settings.optgroupValueField]);
1644
+ if (!key) return false;
1645
+
1646
+ data.$order = data.$order || ++this.order;
1647
+ this.optgroups[key] = data;
1648
+ return key;
1649
+ },
1650
+
1651
+ /**
1652
+ * Registers a new optgroup for options
1653
+ * to be bucketed into.
1654
+ *
1655
+ * @param {string} id
1656
+ * @param {object} data
1657
+ */
1658
+ addOptionGroup: function(id, data) {
1659
+ data[this.settings.optgroupValueField] = id;
1660
+ if (id = this.registerOptionGroup(data)) {
1661
+ this.trigger('optgroup_add', id, data);
1662
+ }
1663
+ },
1664
+
1665
+ /**
1666
+ * Removes an existing option group.
1667
+ *
1668
+ * @param {string} id
1669
+ */
1670
+ removeOptionGroup: function(id) {
1671
+ if (this.optgroups.hasOwnProperty(id)) {
1672
+ delete this.optgroups[id];
1673
+ this.renderCache = {};
1674
+ this.trigger('optgroup_remove', id);
1675
+ }
1676
+ },
1677
+
1678
+ /**
1679
+ * Clears all existing option groups.
1680
+ */
1681
+ clearOptionGroups: function() {
1682
+ this.optgroups = {};
1683
+ this.renderCache = {};
1684
+ this.trigger('optgroup_clear');
1685
+ },
1686
+
1687
+ /**
1688
+ * Updates an option available for selection. If
1689
+ * it is visible in the selected items or options
1690
+ * dropdown, it will be re-rendered automatically.
1691
+ *
1692
+ * @param {string} value
1693
+ * @param {object} data
1694
+ */
1695
+ updateOption: function(value, data) {
1696
+ var self = this;
1697
+ var $item, $item_new;
1698
+ var value_new, index_item, cache_items, cache_options, order_old;
1699
+
1700
+ value = hash_key(value);
1701
+ value_new = hash_key(data[self.settings.valueField]);
1702
+
1703
+ // sanity checks
1704
+ if (value === null) return;
1705
+ if (!self.options.hasOwnProperty(value)) return;
1706
+ if (typeof value_new !== 'string') throw new Error('Value must be set in option data');
1707
+
1708
+ order_old = self.options[value].$order;
1709
+
1710
+ // update references
1711
+ if (value_new !== value) {
1712
+ delete self.options[value];
1713
+ index_item = self.items.indexOf(value);
1714
+ if (index_item !== -1) {
1715
+ self.items.splice(index_item, 1, value_new);
1716
+ }
1717
+ }
1718
+ data.$order = data.$order || order_old;
1719
+ self.options[value_new] = data;
1720
+
1721
+ // invalidate render cache
1722
+ cache_items = self.renderCache['item'];
1723
+ cache_options = self.renderCache['option'];
1724
+
1725
+ if (cache_items) {
1726
+ delete cache_items[value];
1727
+ delete cache_items[value_new];
1728
+ }
1729
+ if (cache_options) {
1730
+ delete cache_options[value];
1731
+ delete cache_options[value_new];
1732
+ }
1733
+
1734
+ // update the item if it's selected
1735
+ if (self.items.indexOf(value_new) !== -1) {
1736
+ $item = self.getItem(value);
1737
+ $item_new = $(self.render('item', data));
1738
+ if ($item.hasClass('active')) $item_new.addClass('active');
1739
+ $item.replaceWith($item_new);
1740
+ }
1741
+
1742
+ // invalidate last query because we might have updated the sortField
1743
+ self.lastQuery = null;
1744
+
1745
+ // update dropdown contents
1746
+ if (self.isOpen) {
1747
+ self.refreshOptions(false);
1748
+ }
1749
+ },
1750
+
1751
+ /**
1752
+ * Removes a single option.
1753
+ *
1754
+ * @param {string} value
1755
+ * @param {boolean} silent
1756
+ */
1757
+ removeOption: function(value, silent) {
1758
+ var self = this;
1759
+ value = hash_key(value);
1760
+
1761
+ var cache_items = self.renderCache['item'];
1762
+ var cache_options = self.renderCache['option'];
1763
+ if (cache_items) delete cache_items[value];
1764
+ if (cache_options) delete cache_options[value];
1765
+
1766
+ delete self.userOptions[value];
1767
+ delete self.options[value];
1768
+ self.lastQuery = null;
1769
+ self.trigger('option_remove', value);
1770
+ self.removeItem(value, silent);
1771
+ },
1772
+
1773
+ /**
1774
+ * Clears all options.
1775
+ */
1776
+ clearOptions: function() {
1777
+ var self = this;
1778
+
1779
+ self.loadedSearches = {};
1780
+ self.userOptions = {};
1781
+ self.renderCache = {};
1782
+ self.options = self.sifter.items = {};
1783
+ self.lastQuery = null;
1784
+ self.trigger('option_clear');
1785
+ self.clear();
1786
+ },
1787
+
1788
+ /**
1789
+ * Returns the jQuery element of the option
1790
+ * matching the given value.
1791
+ *
1792
+ * @param {string} value
1793
+ * @returns {object}
1794
+ */
1795
+ getOption: function(value) {
1796
+ return this.getElementWithValue(value, this.$dropdown_content.find('[data-selectable]'));
1797
+ },
1798
+
1799
+ /**
1800
+ * Returns the jQuery element of the next or
1801
+ * previous selectable option.
1802
+ *
1803
+ * @param {object} $option
1804
+ * @param {int} direction can be 1 for next or -1 for previous
1805
+ * @return {object}
1806
+ */
1807
+ getAdjacentOption: function($option, direction) {
1808
+ var $options = this.$dropdown.find('[data-selectable]');
1809
+ var index = $options.index($option) + direction;
1810
+
1811
+ return index >= 0 && index < $options.length ? $options.eq(index) : $();
1812
+ },
1813
+
1814
+ /**
1815
+ * Finds the first element with a "data-value" attribute
1816
+ * that matches the given value.
1817
+ *
1818
+ * @param {mixed} value
1819
+ * @param {object} $els
1820
+ * @return {object}
1821
+ */
1822
+ getElementWithValue: function(value, $els) {
1823
+ value = hash_key(value);
1824
+
1825
+ if (typeof value !== 'undefined' && value !== null) {
1826
+ for (var i = 0, n = $els.length; i < n; i++) {
1827
+ if ($els[i].getAttribute('data-value') === value) {
1828
+ return $($els[i]);
1829
+ }
1830
+ }
1831
+ }
1832
+
1833
+ return $();
1834
+ },
1835
+
1836
+ /**
1837
+ * Returns the jQuery element of the item
1838
+ * matching the given value.
1839
+ *
1840
+ * @param {string} value
1841
+ * @returns {object}
1842
+ */
1843
+ getItem: function(value) {
1844
+ return this.getElementWithValue(value, this.$control.children());
1845
+ },
1846
+
1847
+ /**
1848
+ * "Selects" multiple items at once. Adds them to the list
1849
+ * at the current caret position.
1850
+ *
1851
+ * @param {string} value
1852
+ * @param {boolean} silent
1853
+ */
1854
+ addItems: function(values, silent) {
1855
+ var items = $.isArray(values) ? values : [values];
1856
+ for (var i = 0, n = items.length; i < n; i++) {
1857
+ this.isPending = (i < n - 1);
1858
+ this.addItem(items[i], silent);
1859
+ }
1860
+ },
1861
+
1862
+ /**
1863
+ * "Selects" an item. Adds it to the list
1864
+ * at the current caret position.
1865
+ *
1866
+ * @param {string} value
1867
+ * @param {boolean} silent
1868
+ */
1869
+ addItem: function(value, silent) {
1870
+ var events = silent ? [] : ['change'];
1871
+
1872
+ debounce_events(this, events, function() {
1873
+ var $item, $option, $options;
1874
+ var self = this;
1875
+ var inputMode = self.settings.mode;
1876
+ var i, active, value_next, wasFull;
1877
+ value = hash_key(value);
1878
+
1879
+ if (self.items.indexOf(value) !== -1) {
1880
+ if (inputMode === 'single') self.close();
1881
+ return;
1882
+ }
1883
+
1884
+ if (!self.options.hasOwnProperty(value)) return;
1885
+ if (inputMode === 'single') self.clear(silent);
1886
+ if (inputMode === 'multi' && self.isFull()) return;
1887
+
1888
+ $item = $(self.render('item', self.options[value]));
1889
+ wasFull = self.isFull();
1890
+ self.items.splice(self.caretPos, 0, value);
1891
+ self.insertAtCaret($item);
1892
+ if (!self.isPending || (!wasFull && self.isFull())) {
1893
+ self.refreshState();
1894
+ }
1895
+
1896
+ if (self.isSetup) {
1897
+ $options = self.$dropdown_content.find('[data-selectable]');
1898
+
1899
+ // update menu / remove the option (if this is not one item being added as part of series)
1900
+ if (!self.isPending) {
1901
+ $option = self.getOption(value);
1902
+ value_next = self.getAdjacentOption($option, 1).attr('data-value');
1903
+ self.refreshOptions(self.isFocused && inputMode !== 'single');
1904
+ if (value_next) {
1905
+ self.setActiveOption(self.getOption(value_next));
1906
+ }
1907
+ }
1908
+
1909
+ // hide the menu if the maximum number of items have been selected or no options are left
1910
+ if (!$options.length || self.isFull()) {
1911
+ self.close();
1912
+ } else {
1913
+ self.positionDropdown();
1914
+ }
1915
+
1916
+ self.updatePlaceholder();
1917
+ self.trigger('item_add', value, $item);
1918
+ self.updateOriginalInput({silent: silent});
1919
+ }
1920
+ });
1921
+ },
1922
+
1923
+ /**
1924
+ * Removes the selected item matching
1925
+ * the provided value.
1926
+ *
1927
+ * @param {string} value
1928
+ */
1929
+ removeItem: function(value, silent) {
1930
+ var self = this;
1931
+ var $item, i, idx;
1932
+
1933
+ $item = (typeof value === 'object') ? value : self.getItem(value);
1934
+ value = hash_key($item.attr('data-value'));
1935
+ i = self.items.indexOf(value);
1936
+
1937
+ if (i !== -1) {
1938
+ $item.remove();
1939
+ if ($item.hasClass('active')) {
1940
+ idx = self.$activeItems.indexOf($item[0]);
1941
+ self.$activeItems.splice(idx, 1);
1942
+ }
1943
+
1944
+ self.items.splice(i, 1);
1945
+ self.lastQuery = null;
1946
+ if (!self.settings.persist && self.userOptions.hasOwnProperty(value)) {
1947
+ self.removeOption(value, silent);
1948
+ }
1949
+
1950
+ if (i < self.caretPos) {
1951
+ self.setCaret(self.caretPos - 1);
1952
+ }
1953
+
1954
+ self.refreshState();
1955
+ self.updatePlaceholder();
1956
+ self.updateOriginalInput({silent: silent});
1957
+ self.positionDropdown();
1958
+ self.trigger('item_remove', value, $item);
1959
+ }
1960
+ },
1961
+
1962
+ /**
1963
+ * Invokes the `create` method provided in the
1964
+ * selectize options that should provide the data
1965
+ * for the new item, given the user input.
1966
+ *
1967
+ * Once this completes, it will be added
1968
+ * to the item list.
1969
+ *
1970
+ * @param {string} value
1971
+ * @param {boolean} [triggerDropdown]
1972
+ * @param {function} [callback]
1973
+ * @return {boolean}
1974
+ */
1975
+ createItem: function(input, triggerDropdown) {
1976
+ var self = this;
1977
+ var caret = self.caretPos;
1978
+ input = input || $.trim(self.$control_input.val() || '');
1979
+
1980
+ var callback = arguments[arguments.length - 1];
1981
+ if (typeof callback !== 'function') callback = function() {};
1982
+
1983
+ if (typeof triggerDropdown !== 'boolean') {
1984
+ triggerDropdown = true;
1985
+ }
1986
+
1987
+ if (!self.canCreate(input)) {
1988
+ callback();
1989
+ return false;
1990
+ }
1991
+
1992
+ self.lock();
1993
+
1994
+ var setup = (typeof self.settings.create === 'function') ? this.settings.create : function(input) {
1995
+ var data = {};
1996
+ data[self.settings.labelField] = input;
1997
+ data[self.settings.valueField] = input;
1998
+ return data;
1999
+ };
2000
+
2001
+ var create = once(function(data) {
2002
+ self.unlock();
2003
+
2004
+ if (!data || typeof data !== 'object') return callback();
2005
+ var value = hash_key(data[self.settings.valueField]);
2006
+ if (typeof value !== 'string') return callback();
2007
+
2008
+ self.setTextboxValue('');
2009
+ self.addOption(data);
2010
+ self.setCaret(caret);
2011
+ self.addItem(value);
2012
+ self.refreshOptions(triggerDropdown && self.settings.mode !== 'single');
2013
+ callback(data);
2014
+ });
2015
+
2016
+ var output = setup.apply(this, [input, create]);
2017
+ if (typeof output !== 'undefined') {
2018
+ create(output);
2019
+ }
2020
+
2021
+ return true;
2022
+ },
2023
+
2024
+ /**
2025
+ * Re-renders the selected item lists.
2026
+ */
2027
+ refreshItems: function() {
2028
+ this.lastQuery = null;
2029
+
2030
+ if (this.isSetup) {
2031
+ this.addItem(this.items);
2032
+ }
2033
+
2034
+ this.refreshState();
2035
+ this.updateOriginalInput();
2036
+ },
2037
+
2038
+ /**
2039
+ * Updates all state-dependent attributes
2040
+ * and CSS classes.
2041
+ */
2042
+ refreshState: function() {
2043
+ var invalid, self = this;
2044
+ if (self.isRequired) {
2045
+ if (self.items.length) self.isInvalid = false;
2046
+ self.$control_input.prop('required', invalid);
2047
+ }
2048
+ self.refreshClasses();
2049
+ },
2050
+
2051
+ /**
2052
+ * Updates all state-dependent CSS classes.
2053
+ */
2054
+ refreshClasses: function() {
2055
+ var self = this;
2056
+ var isFull = self.isFull();
2057
+ var isLocked = self.isLocked;
2058
+
2059
+ self.$wrapper
2060
+ .toggleClass('rtl', self.rtl);
2061
+
2062
+ self.$control
2063
+ .toggleClass('focus', self.isFocused)
2064
+ .toggleClass('disabled', self.isDisabled)
2065
+ .toggleClass('required', self.isRequired)
2066
+ .toggleClass('invalid', self.isInvalid)
2067
+ .toggleClass('locked', isLocked)
2068
+ .toggleClass('full', isFull).toggleClass('not-full', !isFull)
2069
+ .toggleClass('input-active', self.isFocused && !self.isInputHidden)
2070
+ .toggleClass('dropdown-active', self.isOpen)
2071
+ .toggleClass('has-options', !$.isEmptyObject(self.options))
2072
+ .toggleClass('has-items', self.items.length > 0);
2073
+
2074
+ self.$control_input.data('grow', !isFull && !isLocked);
2075
+ },
2076
+
2077
+ /**
2078
+ * Determines whether or not more items can be added
2079
+ * to the control without exceeding the user-defined maximum.
2080
+ *
2081
+ * @returns {boolean}
2082
+ */
2083
+ isFull: function() {
2084
+ return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems;
2085
+ },
2086
+
2087
+ /**
2088
+ * Refreshes the original <select> or <input>
2089
+ * element to reflect the current state.
2090
+ */
2091
+ updateOriginalInput: function(opts) {
2092
+ var i, n, options, label, self = this;
2093
+ opts = opts || {};
2094
+
2095
+ if (self.tagType === TAG_SELECT) {
2096
+ options = [];
2097
+ for (i = 0, n = self.items.length; i < n; i++) {
2098
+ label = self.options[self.items[i]][self.settings.labelField] || '';
2099
+ options.push('<option value="' + escape_html(self.items[i]) + '" selected="selected">' + escape_html(label) + '</option>');
2100
+ }
2101
+ if (!options.length && !this.$input.attr('multiple')) {
2102
+ options.push('<option value="" selected="selected"></option>');
2103
+ }
2104
+ self.$input.html(options.join(''));
2105
+ } else {
2106
+ self.$input.val(self.getValue());
2107
+ self.$input.attr('value',self.$input.val());
2108
+ }
2109
+
2110
+ if (self.isSetup) {
2111
+ if (!opts.silent) {
2112
+ self.trigger('change', self.$input.val());
2113
+ }
2114
+ }
2115
+ },
2116
+
2117
+ /**
2118
+ * Shows/hide the input placeholder depending
2119
+ * on if there items in the list already.
2120
+ */
2121
+ updatePlaceholder: function() {
2122
+ if (!this.settings.placeholder) return;
2123
+ var $input = this.$control_input;
2124
+
2125
+ if (this.items.length) {
2126
+ $input.removeAttr('placeholder');
2127
+ } else {
2128
+ $input.attr('placeholder', this.settings.placeholder);
2129
+ }
2130
+ $input.triggerHandler('update', {force: true});
2131
+ },
2132
+
2133
+ /**
2134
+ * Shows the autocomplete dropdown containing
2135
+ * the available options.
2136
+ */
2137
+ open: function() {
2138
+ var self = this;
2139
+
2140
+ if (self.isLocked || self.isOpen || (self.settings.mode === 'multi' && self.isFull())) return;
2141
+ self.focus();
2142
+ self.isOpen = true;
2143
+ self.refreshState();
2144
+ self.$dropdown.css({visibility: 'hidden', display: 'block'});
2145
+ self.positionDropdown();
2146
+ self.$dropdown.css({visibility: 'visible'});
2147
+ self.trigger('dropdown_open', self.$dropdown);
2148
+ },
2149
+
2150
+ /**
2151
+ * Closes the autocomplete dropdown menu.
2152
+ */
2153
+ close: function() {
2154
+ var self = this;
2155
+ var trigger = self.isOpen;
2156
+
2157
+ if (self.settings.mode === 'single' && self.items.length) {
2158
+ self.hideInput();
2159
+ }
2160
+
2161
+ self.isOpen = false;
2162
+ self.$dropdown.hide();
2163
+ self.setActiveOption(null);
2164
+ self.refreshState();
2165
+
2166
+ if (trigger) self.trigger('dropdown_close', self.$dropdown);
2167
+ },
2168
+
2169
+ /**
2170
+ * Calculates and applies the appropriate
2171
+ * position of the dropdown.
2172
+ */
2173
+ positionDropdown: function() {
2174
+ var $control = this.$control;
2175
+ var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position();
2176
+ offset.top += $control.outerHeight(true);
2177
+
2178
+ this.$dropdown.css({
2179
+ width : $control.outerWidth(),
2180
+ top : offset.top,
2181
+ left : offset.left
2182
+ });
2183
+ },
2184
+
2185
+ /**
2186
+ * Resets / clears all selected items
2187
+ * from the control.
2188
+ *
2189
+ * @param {boolean} silent
2190
+ */
2191
+ clear: function(silent) {
2192
+ var self = this;
2193
+
2194
+ if (!self.items.length) return;
2195
+ self.$control.children(':not(input)').remove();
2196
+ self.items = [];
2197
+ self.lastQuery = null;
2198
+ self.setCaret(0);
2199
+ self.setActiveItem(null);
2200
+ self.updatePlaceholder();
2201
+ self.updateOriginalInput({silent: silent});
2202
+ self.refreshState();
2203
+ self.showInput();
2204
+ self.trigger('clear');
2205
+ },
2206
+
2207
+ /**
2208
+ * A helper method for inserting an element
2209
+ * at the current caret position.
2210
+ *
2211
+ * @param {object} $el
2212
+ */
2213
+ insertAtCaret: function($el) {
2214
+ var caret = Math.min(this.caretPos, this.items.length);
2215
+ if (caret === 0) {
2216
+ this.$control.prepend($el);
2217
+ } else {
2218
+ $(this.$control[0].childNodes[caret]).before($el);
2219
+ }
2220
+ this.setCaret(caret + 1);
2221
+ },
2222
+
2223
+ /**
2224
+ * Removes the current selected item(s).
2225
+ *
2226
+ * @param {object} e (optional)
2227
+ * @returns {boolean}
2228
+ */
2229
+ deleteSelection: function(e) {
2230
+ var i, n, direction, selection, values, caret, option_select, $option_select, $tail;
2231
+ var self = this;
2232
+
2233
+ direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1;
2234
+ selection = getSelection(self.$control_input[0]);
2235
+
2236
+ if (self.$activeOption && !self.settings.hideSelected) {
2237
+ option_select = self.getAdjacentOption(self.$activeOption, -1).attr('data-value');
2238
+ }
2239
+
2240
+ // determine items that will be removed
2241
+ values = [];
2242
+
2243
+ if (self.$activeItems.length) {
2244
+ $tail = self.$control.children('.active:' + (direction > 0 ? 'last' : 'first'));
2245
+ caret = self.$control.children(':not(input)').index($tail);
2246
+ if (direction > 0) { caret++; }
2247
+
2248
+ for (i = 0, n = self.$activeItems.length; i < n; i++) {
2249
+ values.push($(self.$activeItems[i]).attr('data-value'));
2250
+ }
2251
+ if (e) {
2252
+ e.preventDefault();
2253
+ e.stopPropagation();
2254
+ }
2255
+ } else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) {
2256
+ if (direction < 0 && selection.start === 0 && selection.length === 0) {
2257
+ values.push(self.items[self.caretPos - 1]);
2258
+ } else if (direction > 0 && selection.start === self.$control_input.val().length) {
2259
+ values.push(self.items[self.caretPos]);
2260
+ }
2261
+ }
2262
+
2263
+ // allow the callback to abort
2264
+ if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete.apply(self, [values]) === false)) {
2265
+ return false;
2266
+ }
2267
+
2268
+ // perform removal
2269
+ if (typeof caret !== 'undefined') {
2270
+ self.setCaret(caret);
2271
+ }
2272
+ while (values.length) {
2273
+ self.removeItem(values.pop());
2274
+ }
2275
+
2276
+ self.showInput();
2277
+ self.positionDropdown();
2278
+ self.refreshOptions(true);
2279
+
2280
+ // select previous option
2281
+ if (option_select) {
2282
+ $option_select = self.getOption(option_select);
2283
+ if ($option_select.length) {
2284
+ self.setActiveOption($option_select);
2285
+ }
2286
+ }
2287
+
2288
+ return true;
2289
+ },
2290
+
2291
+ /**
2292
+ * Selects the previous / next item (depending
2293
+ * on the `direction` argument).
2294
+ *
2295
+ * > 0 - right
2296
+ * < 0 - left
2297
+ *
2298
+ * @param {int} direction
2299
+ * @param {object} e (optional)
2300
+ */
2301
+ advanceSelection: function(direction, e) {
2302
+ var tail, selection, idx, valueLength, cursorAtEdge, $tail;
2303
+ var self = this;
2304
+
2305
+ if (direction === 0) return;
2306
+ if (self.rtl) direction *= -1;
2307
+
2308
+ tail = direction > 0 ? 'last' : 'first';
2309
+ selection = getSelection(self.$control_input[0]);
2310
+
2311
+ if (self.isFocused && !self.isInputHidden) {
2312
+ valueLength = self.$control_input.val().length;
2313
+ cursorAtEdge = direction < 0
2314
+ ? selection.start === 0 && selection.length === 0
2315
+ : selection.start === valueLength;
2316
+
2317
+ if (cursorAtEdge && !valueLength) {
2318
+ self.advanceCaret(direction, e);
2319
+ }
2320
+ } else {
2321
+ $tail = self.$control.children('.active:' + tail);
2322
+ if ($tail.length) {
2323
+ idx = self.$control.children(':not(input)').index($tail);
2324
+ self.setActiveItem(null);
2325
+ self.setCaret(direction > 0 ? idx + 1 : idx);
2326
+ }
2327
+ }
2328
+ },
2329
+
2330
+ /**
2331
+ * Moves the caret left / right.
2332
+ *
2333
+ * @param {int} direction
2334
+ * @param {object} e (optional)
2335
+ */
2336
+ advanceCaret: function(direction, e) {
2337
+ var self = this, fn, $adj;
2338
+
2339
+ if (direction === 0) return;
2340
+
2341
+ fn = direction > 0 ? 'next' : 'prev';
2342
+ if (self.isShiftDown) {
2343
+ $adj = self.$control_input[fn]();
2344
+ if ($adj.length) {
2345
+ self.hideInput();
2346
+ self.setActiveItem($adj);
2347
+ e && e.preventDefault();
2348
+ }
2349
+ } else {
2350
+ self.setCaret(self.caretPos + direction);
2351
+ }
2352
+ },
2353
+
2354
+ /**
2355
+ * Moves the caret to the specified index.
2356
+ *
2357
+ * @param {int} i
2358
+ */
2359
+ setCaret: function(i) {
2360
+ var self = this;
2361
+
2362
+ if (self.settings.mode === 'single') {
2363
+ i = self.items.length;
2364
+ } else {
2365
+ i = Math.max(0, Math.min(self.items.length, i));
2366
+ }
2367
+
2368
+ if(!self.isPending) {
2369
+ // the input must be moved by leaving it in place and moving the
2370
+ // siblings, due to the fact that focus cannot be restored once lost
2371
+ // on mobile webkit devices
2372
+ var j, n, fn, $children, $child;
2373
+ $children = self.$control.children(':not(input)');
2374
+ for (j = 0, n = $children.length; j < n; j++) {
2375
+ $child = $($children[j]).detach();
2376
+ if (j < i) {
2377
+ self.$control_input.before($child);
2378
+ } else {
2379
+ self.$control.append($child);
2380
+ }
2381
+ }
2382
+ }
2383
+
2384
+ self.caretPos = i;
2385
+ },
2386
+
2387
+ /**
2388
+ * Disables user input on the control. Used while
2389
+ * items are being asynchronously created.
2390
+ */
2391
+ lock: function() {
2392
+ this.close();
2393
+ this.isLocked = true;
2394
+ this.refreshState();
2395
+ },
2396
+
2397
+ /**
2398
+ * Re-enables user input on the control.
2399
+ */
2400
+ unlock: function() {
2401
+ this.isLocked = false;
2402
+ this.refreshState();
2403
+ },
2404
+
2405
+ /**
2406
+ * Disables user input on the control completely.
2407
+ * While disabled, it cannot receive focus.
2408
+ */
2409
+ disable: function() {
2410
+ var self = this;
2411
+ self.$input.prop('disabled', true);
2412
+ self.$control_input.prop('disabled', true).prop('tabindex', -1);
2413
+ self.isDisabled = true;
2414
+ self.lock();
2415
+ },
2416
+
2417
+ /**
2418
+ * Enables the control so that it can respond
2419
+ * to focus and user input.
2420
+ */
2421
+ enable: function() {
2422
+ var self = this;
2423
+ self.$input.prop('disabled', false);
2424
+ self.$control_input.prop('disabled', false).prop('tabindex', self.tabIndex);
2425
+ self.isDisabled = false;
2426
+ self.unlock();
2427
+ },
2428
+
2429
+ /**
2430
+ * Completely destroys the control and
2431
+ * unbinds all event listeners so that it can
2432
+ * be garbage collected.
2433
+ */
2434
+ destroy: function() {
2435
+ var self = this;
2436
+ var eventNS = self.eventNS;
2437
+ var revertSettings = self.revertSettings;
2438
+
2439
+ self.trigger('destroy');
2440
+ self.off();
2441
+ self.$wrapper.remove();
2442
+ self.$dropdown.remove();
2443
+
2444
+ self.$input
2445
+ .html('')
2446
+ .append(revertSettings.$children)
2447
+ .removeAttr('tabindex')
2448
+ .removeClass('selectized')
2449
+ .attr({tabindex: revertSettings.tabindex})
2450
+ .show();
2451
+
2452
+ self.$control_input.removeData('grow');
2453
+ self.$input.removeData('selectize');
2454
+
2455
+ $(window).off(eventNS);
2456
+ $(document).off(eventNS);
2457
+ $(document.body).off(eventNS);
2458
+
2459
+ delete self.$input[0].selectize;
2460
+ },
2461
+
2462
+ /**
2463
+ * A helper method for rendering "item" and
2464
+ * "option" templates, given the data.
2465
+ *
2466
+ * @param {string} templateName
2467
+ * @param {object} data
2468
+ * @returns {string}
2469
+ */
2470
+ render: function(templateName, data) {
2471
+ var value, id, label;
2472
+ var html = '';
2473
+ var cache = false;
2474
+ var self = this;
2475
+ var regex_tag = /^[\t \r\n]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i;
2476
+
2477
+ if (templateName === 'option' || templateName === 'item') {
2478
+ value = hash_key(data[self.settings.valueField]);
2479
+ cache = !!value;
2480
+ }
2481
+
2482
+ // pull markup from cache if it exists
2483
+ if (cache) {
2484
+ if (!isset(self.renderCache[templateName])) {
2485
+ self.renderCache[templateName] = {};
2486
+ }
2487
+ if (self.renderCache[templateName].hasOwnProperty(value)) {
2488
+ return self.renderCache[templateName][value];
2489
+ }
2490
+ }
2491
+
2492
+ // render markup
2493
+ html = self.settings.render[templateName].apply(this, [data, escape_html]);
2494
+
2495
+ // add mandatory attributes
2496
+ if (templateName === 'option' || templateName === 'option_create') {
2497
+ html = html.replace(regex_tag, '<$1 data-selectable');
2498
+ }
2499
+ if (templateName === 'optgroup') {
2500
+ id = data[self.settings.optgroupValueField] || '';
2501
+ html = html.replace(regex_tag, '<$1 data-group="' + escape_replace(escape_html(id)) + '"');
2502
+ }
2503
+ if (templateName === 'option' || templateName === 'item') {
2504
+ html = html.replace(regex_tag, '<$1 data-value="' + escape_replace(escape_html(value || '')) + '"');
2505
+ }
2506
+
2507
+ // update cache
2508
+ if (cache) {
2509
+ self.renderCache[templateName][value] = html;
2510
+ }
2511
+
2512
+ return html;
2513
+ },
2514
+
2515
+ /**
2516
+ * Clears the render cache for a template. If
2517
+ * no template is given, clears all render
2518
+ * caches.
2519
+ *
2520
+ * @param {string} templateName
2521
+ */
2522
+ clearCache: function(templateName) {
2523
+ var self = this;
2524
+ if (typeof templateName === 'undefined') {
2525
+ self.renderCache = {};
2526
+ } else {
2527
+ delete self.renderCache[templateName];
2528
+ }
2529
+ },
2530
+
2531
+ /**
2532
+ * Determines whether or not to display the
2533
+ * create item prompt, given a user input.
2534
+ *
2535
+ * @param {string} input
2536
+ * @return {boolean}
2537
+ */
2538
+ canCreate: function(input) {
2539
+ var self = this;
2540
+ if (!self.settings.create) return false;
2541
+ var filter = self.settings.createFilter;
2542
+ return input.length
2543
+ && (typeof filter !== 'function' || filter.apply(self, [input]))
2544
+ && (typeof filter !== 'string' || new RegExp(filter).test(input))
2545
+ && (!(filter instanceof RegExp) || filter.test(input));
2546
+ }
2547
+
2548
+ });
2549
+
2550
+
2551
+ Selectize.count = 0;
2552
+ Selectize.defaults = {
2553
+ options: [],
2554
+ optgroups: [],
2555
+
2556
+ plugins: [],
2557
+ delimiter: ',',
2558
+ splitOn: null, // regexp or string for splitting up values from a paste command
2559
+ persist: true,
2560
+ diacritics: true,
2561
+ create: false,
2562
+ createOnBlur: false,
2563
+ createFilter: null,
2564
+ highlight: true,
2565
+ openOnFocus: true,
2566
+ maxOptions: 1000,
2567
+ maxItems: null,
2568
+ hideSelected: null,
2569
+ addPrecedence: false,
2570
+ selectOnTab: false,
2571
+ preload: false,
2572
+ allowEmptyOption: false,
2573
+ closeAfterSelect: false,
2574
+
2575
+ scrollDuration: 60,
2576
+ loadThrottle: 300,
2577
+ loadingClass: 'loading',
2578
+
2579
+ dataAttr: 'data-data',
2580
+ optgroupField: 'optgroup',
2581
+ valueField: 'value',
2582
+ labelField: 'text',
2583
+ optgroupLabelField: 'label',
2584
+ optgroupValueField: 'value',
2585
+ lockOptgroupOrder: false,
2586
+
2587
+ sortField: '$order',
2588
+ searchField: ['text'],
2589
+ searchConjunction: 'and',
2590
+
2591
+ mode: null,
2592
+ wrapperClass: 'selectize-control',
2593
+ inputClass: 'selectize-input',
2594
+ dropdownClass: 'selectize-dropdown',
2595
+ dropdownContentClass: 'selectize-dropdown-content',
2596
+
2597
+ dropdownParent: null,
2598
+
2599
+ copyClassesToDropdown: true,
2600
+
2601
+ /*
2602
+ load : null, // function(query, callback) { ... }
2603
+ score : null, // function(search) { ... }
2604
+ onInitialize : null, // function() { ... }
2605
+ onChange : null, // function(value) { ... }
2606
+ onItemAdd : null, // function(value, $item) { ... }
2607
+ onItemRemove : null, // function(value) { ... }
2608
+ onClear : null, // function() { ... }
2609
+ onOptionAdd : null, // function(value, data) { ... }
2610
+ onOptionRemove : null, // function(value) { ... }
2611
+ onOptionClear : null, // function() { ... }
2612
+ onOptionGroupAdd : null, // function(id, data) { ... }
2613
+ onOptionGroupRemove : null, // function(id) { ... }
2614
+ onOptionGroupClear : null, // function() { ... }
2615
+ onDropdownOpen : null, // function($dropdown) { ... }
2616
+ onDropdownClose : null, // function($dropdown) { ... }
2617
+ onType : null, // function(str) { ... }
2618
+ onDelete : null, // function(values) { ... }
2619
+ */
2620
+
2621
+ render: {
2622
+ /*
2623
+ item: null,
2624
+ optgroup: null,
2625
+ optgroup_header: null,
2626
+ option: null,
2627
+ option_create: null
2628
+ */
2629
+ }
2630
+ };
2631
+
2632
+
2633
+ $.fn.selectize = function(settings_user) {
2634
+ var defaults = $.fn.selectize.defaults;
2635
+ var settings = $.extend({}, defaults, settings_user);
2636
+ var attr_data = settings.dataAttr;
2637
+ var field_label = settings.labelField;
2638
+ var field_value = settings.valueField;
2639
+ var field_optgroup = settings.optgroupField;
2640
+ var field_optgroup_label = settings.optgroupLabelField;
2641
+ var field_optgroup_value = settings.optgroupValueField;
2642
+
2643
+ /**
2644
+ * Initializes selectize from a <input type="text"> element.
2645
+ *
2646
+ * @param {object} $input
2647
+ * @param {object} settings_element
2648
+ */
2649
+ var init_textbox = function($input, settings_element) {
2650
+ var i, n, values, option;
2651
+
2652
+ var data_raw = $input.attr(attr_data);
2653
+
2654
+ if (!data_raw) {
2655
+ var value = $.trim($input.val() || '');
2656
+ if (!settings.allowEmptyOption && !value.length) return;
2657
+ values = value.split(settings.delimiter);
2658
+ for (i = 0, n = values.length; i < n; i++) {
2659
+ option = {};
2660
+ option[field_label] = values[i];
2661
+ option[field_value] = values[i];
2662
+ settings_element.options.push(option);
2663
+ }
2664
+ settings_element.items = values;
2665
+ } else {
2666
+ settings_element.options = JSON.parse(data_raw);
2667
+ for (i = 0, n = settings_element.options.length; i < n; i++) {
2668
+ settings_element.items.push(settings_element.options[i][field_value]);
2669
+ }
2670
+ }
2671
+ };
2672
+
2673
+ /**
2674
+ * Initializes selectize from a <select> element.
2675
+ *
2676
+ * @param {object} $input
2677
+ * @param {object} settings_element
2678
+ */
2679
+ var init_select = function($input, settings_element) {
2680
+ var i, n, tagName, $children, order = 0;
2681
+ var options = settings_element.options;
2682
+ var optionsMap = {};
2683
+
2684
+ var readData = function($el) {
2685
+ var data = attr_data && $el.attr(attr_data);
2686
+ if (typeof data === 'string' && data.length) {
2687
+ return JSON.parse(data);
2688
+ }
2689
+ return null;
2690
+ };
2691
+
2692
+ var addOption = function($option, group) {
2693
+ $option = $($option);
2694
+
2695
+ var value = hash_key($option.attr('value'));
2696
+ if (!value && !settings.allowEmptyOption) return;
2697
+
2698
+ // if the option already exists, it's probably been
2699
+ // duplicated in another optgroup. in this case, push
2700
+ // the current group to the "optgroup" property on the
2701
+ // existing option so that it's rendered in both places.
2702
+ if (optionsMap.hasOwnProperty(value)) {
2703
+ if (group) {
2704
+ var arr = optionsMap[value][field_optgroup];
2705
+ if (!arr) {
2706
+ optionsMap[value][field_optgroup] = group;
2707
+ } else if (!$.isArray(arr)) {
2708
+ optionsMap[value][field_optgroup] = [arr, group];
2709
+ } else {
2710
+ arr.push(group);
2711
+ }
2712
+ }
2713
+ return;
2714
+ }
2715
+
2716
+ var option = readData($option) || {};
2717
+ option[field_label] = option[field_label] || $option.text();
2718
+ option[field_value] = option[field_value] || value;
2719
+ option[field_optgroup] = option[field_optgroup] || group;
2720
+
2721
+ optionsMap[value] = option;
2722
+ options.push(option);
2723
+
2724
+ if ($option.is(':selected')) {
2725
+ settings_element.items.push(value);
2726
+ }
2727
+ };
2728
+
2729
+ var addGroup = function($optgroup) {
2730
+ var i, n, id, optgroup, $options;
2731
+
2732
+ $optgroup = $($optgroup);
2733
+ id = $optgroup.attr('label');
2734
+
2735
+ if (id) {
2736
+ optgroup = readData($optgroup) || {};
2737
+ optgroup[field_optgroup_label] = id;
2738
+ optgroup[field_optgroup_value] = id;
2739
+ settings_element.optgroups.push(optgroup);
2740
+ }
2741
+
2742
+ $options = $('option', $optgroup);
2743
+ for (i = 0, n = $options.length; i < n; i++) {
2744
+ addOption($options[i], id);
2745
+ }
2746
+ };
2747
+
2748
+ settings_element.maxItems = $input.attr('multiple') ? null : 1;
2749
+
2750
+ $children = $input.children();
2751
+ for (i = 0, n = $children.length; i < n; i++) {
2752
+ tagName = $children[i].tagName.toLowerCase();
2753
+ if (tagName === 'optgroup') {
2754
+ addGroup($children[i]);
2755
+ } else if (tagName === 'option') {
2756
+ addOption($children[i]);
2757
+ }
2758
+ }
2759
+ };
2760
+
2761
+ return this.each(function() {
2762
+ if (this.selectize) return;
2763
+
2764
+ var instance;
2765
+ var $input = $(this);
2766
+ var tag_name = this.tagName.toLowerCase();
2767
+ var placeholder = $input.attr('placeholder') || $input.attr('data-placeholder');
2768
+ if (!placeholder && !settings.allowEmptyOption) {
2769
+ placeholder = $input.children('option[value=""]').text();
2770
+ }
2771
+
2772
+ var settings_element = {
2773
+ 'placeholder' : placeholder,
2774
+ 'options' : [],
2775
+ 'optgroups' : [],
2776
+ 'items' : []
2777
+ };
2778
+
2779
+ if (tag_name === 'select') {
2780
+ init_select($input, settings_element);
2781
+ } else {
2782
+ init_textbox($input, settings_element);
2783
+ }
2784
+
2785
+ instance = new Selectize($input, $.extend(true, {}, defaults, settings_element, settings_user));
2786
+ });
2787
+ };
2788
+
2789
+ $.fn.selectize.defaults = Selectize.defaults;
2790
+ $.fn.selectize.support = {
2791
+ validity: SUPPORTS_VALIDITY_API
2792
+ };
2793
+
2794
+
2795
+ Selectize.define('drag_drop', function(options) {
2796
+ if (!$.fn.sortable) throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');
2797
+ if (this.settings.mode !== 'multi') return;
2798
+ var self = this;
2799
+
2800
+ self.lock = (function() {
2801
+ var original = self.lock;
2802
+ return function() {
2803
+ var sortable = self.$control.data('sortable');
2804
+ if (sortable) sortable.disable();
2805
+ return original.apply(self, arguments);
2806
+ };
2807
+ })();
2808
+
2809
+ self.unlock = (function() {
2810
+ var original = self.unlock;
2811
+ return function() {
2812
+ var sortable = self.$control.data('sortable');
2813
+ if (sortable) sortable.enable();
2814
+ return original.apply(self, arguments);
2815
+ };
2816
+ })();
2817
+
2818
+ self.setup = (function() {
2819
+ var original = self.setup;
2820
+ return function() {
2821
+ original.apply(this, arguments);
2822
+
2823
+ var $control = self.$control.sortable({
2824
+ items: '[data-value]',
2825
+ forcePlaceholderSize: true,
2826
+ disabled: self.isLocked,
2827
+ start: function(e, ui) {
2828
+ ui.placeholder.css('width', ui.helper.css('width'));
2829
+ $control.css({overflow: 'visible'});
2830
+ },
2831
+ stop: function() {
2832
+ $control.css({overflow: 'hidden'});
2833
+ var active = self.$activeItems ? self.$activeItems.slice() : null;
2834
+ var values = [];
2835
+ $control.children('[data-value]').each(function() {
2836
+ values.push($(this).attr('data-value'));
2837
+ });
2838
+ self.setValue(values);
2839
+ self.setActiveItem(active);
2840
+ }
2841
+ });
2842
+ };
2843
+ })();
2844
+
2845
+ });
2846
+
2847
+ Selectize.define('dropdown_header', function(options) {
2848
+ var self = this;
2849
+
2850
+ options = $.extend({
2851
+ title : 'Untitled',
2852
+ headerClass : 'selectize-dropdown-header',
2853
+ titleRowClass : 'selectize-dropdown-header-title',
2854
+ labelClass : 'selectize-dropdown-header-label',
2855
+ closeClass : 'selectize-dropdown-header-close',
2856
+
2857
+ html: function(data) {
2858
+ return (
2859
+ '<div class="' + data.headerClass + '">' +
2860
+ '<div class="' + data.titleRowClass + '">' +
2861
+ '<span class="' + data.labelClass + '">' + data.title + '</span>' +
2862
+ '<a href="javascript:void(0)" class="' + data.closeClass + '">&times;</a>' +
2863
+ '</div>' +
2864
+ '</div>'
2865
+ );
2866
+ }
2867
+ }, options);
2868
+
2869
+ self.setup = (function() {
2870
+ var original = self.setup;
2871
+ return function() {
2872
+ original.apply(self, arguments);
2873
+ self.$dropdown_header = $(options.html(options));
2874
+ self.$dropdown.prepend(self.$dropdown_header);
2875
+ };
2876
+ })();
2877
+
2878
+ });
2879
+
2880
+ Selectize.define('optgroup_columns', function(options) {
2881
+ var self = this;
2882
+
2883
+ options = $.extend({
2884
+ equalizeWidth : true,
2885
+ equalizeHeight : true
2886
+ }, options);
2887
+
2888
+ this.getAdjacentOption = function($option, direction) {
2889
+ var $options = $option.closest('[data-group]').find('[data-selectable]');
2890
+ var index = $options.index($option) + direction;
2891
+
2892
+ return index >= 0 && index < $options.length ? $options.eq(index) : $();
2893
+ };
2894
+
2895
+ this.onKeyDown = (function() {
2896
+ var original = self.onKeyDown;
2897
+ return function(e) {
2898
+ var index, $option, $options, $optgroup;
2899
+
2900
+ if (this.isOpen && (e.keyCode === KEY_LEFT || e.keyCode === KEY_RIGHT)) {
2901
+ self.ignoreHover = true;
2902
+ $optgroup = this.$activeOption.closest('[data-group]');
2903
+ index = $optgroup.find('[data-selectable]').index(this.$activeOption);
2904
+
2905
+ if(e.keyCode === KEY_LEFT) {
2906
+ $optgroup = $optgroup.prev('[data-group]');
2907
+ } else {
2908
+ $optgroup = $optgroup.next('[data-group]');
2909
+ }
2910
+
2911
+ $options = $optgroup.find('[data-selectable]');
2912
+ $option = $options.eq(Math.min($options.length - 1, index));
2913
+ if ($option.length) {
2914
+ this.setActiveOption($option);
2915
+ }
2916
+ return;
2917
+ }
2918
+
2919
+ return original.apply(this, arguments);
2920
+ };
2921
+ })();
2922
+
2923
+ var getScrollbarWidth = function() {
2924
+ var div;
2925
+ var width = getScrollbarWidth.width;
2926
+ var doc = document;
2927
+
2928
+ if (typeof width === 'undefined') {
2929
+ div = doc.createElement('div');
2930
+ div.innerHTML = '<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>';
2931
+ div = div.firstChild;
2932
+ doc.body.appendChild(div);
2933
+ width = getScrollbarWidth.width = div.offsetWidth - div.clientWidth;
2934
+ doc.body.removeChild(div);
2935
+ }
2936
+ return width;
2937
+ };
2938
+
2939
+ var equalizeSizes = function() {
2940
+ var i, n, height_max, width, width_last, width_parent, $optgroups;
2941
+
2942
+ $optgroups = $('[data-group]', self.$dropdown_content);
2943
+ n = $optgroups.length;
2944
+ if (!n || !self.$dropdown_content.width()) return;
2945
+
2946
+ if (options.equalizeHeight) {
2947
+ height_max = 0;
2948
+ for (i = 0; i < n; i++) {
2949
+ height_max = Math.max(height_max, $optgroups.eq(i).height());
2950
+ }
2951
+ $optgroups.css({height: height_max});
2952
+ }
2953
+
2954
+ if (options.equalizeWidth) {
2955
+ width_parent = self.$dropdown_content.innerWidth() - getScrollbarWidth();
2956
+ width = Math.round(width_parent / n);
2957
+ $optgroups.css({width: width});
2958
+ if (n > 1) {
2959
+ width_last = width_parent - width * (n - 1);
2960
+ $optgroups.eq(n - 1).css({width: width_last});
2961
+ }
2962
+ }
2963
+ };
2964
+
2965
+ if (options.equalizeHeight || options.equalizeWidth) {
2966
+ hook.after(this, 'positionDropdown', equalizeSizes);
2967
+ hook.after(this, 'refreshOptions', equalizeSizes);
2968
+ }
2969
+
2970
+
2971
+ });
2972
+
2973
+ Selectize.define('remove_button', function(options) {
2974
+ if (this.settings.mode === 'single') return;
2975
+
2976
+ options = $.extend({
2977
+ label : '&times;',
2978
+ title : 'Remove',
2979
+ className : 'remove',
2980
+ append : true
2981
+ }, options);
2982
+
2983
+ var self = this;
2984
+ var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>';
2985
+
2986
+ /**
2987
+ * Appends an element as a child (with raw HTML).
2988
+ *
2989
+ * @param {string} html_container
2990
+ * @param {string} html_element
2991
+ * @return {string}
2992
+ */
2993
+ var append = function(html_container, html_element) {
2994
+ var pos = html_container.search(/(<\/[^>]+>\s*)$/);
2995
+ return html_container.substring(0, pos) + html_element + html_container.substring(pos);
2996
+ };
2997
+
2998
+ this.setup = (function() {
2999
+ var original = self.setup;
3000
+ return function() {
3001
+ // override the item rendering method to add the button to each
3002
+ if (options.append) {
3003
+ var render_item = self.settings.render.item;
3004
+ self.settings.render.item = function(data) {
3005
+ return append(render_item.apply(this, arguments), html);
3006
+ };
3007
+ }
3008
+
3009
+ original.apply(this, arguments);
3010
+
3011
+ // add event listener
3012
+ this.$control.on('click', '.' + options.className, function(e) {
3013
+ e.preventDefault();
3014
+ if (self.isLocked) return;
3015
+
3016
+ var $item = $(e.currentTarget).parent();
3017
+ self.setActiveItem($item);
3018
+ if (self.deleteSelection()) {
3019
+ self.setCaret(self.items.length);
3020
+ }
3021
+ });
3022
+
3023
+ };
3024
+ })();
3025
+
3026
+ });
3027
+
3028
+ Selectize.define('restore_on_backspace', function(options) {
3029
+ var self = this;
3030
+
3031
+ options.text = options.text || function(option) {
3032
+ return option[this.settings.labelField];
3033
+ };
3034
+
3035
+ this.onKeyDown = (function() {
3036
+ var original = self.onKeyDown;
3037
+ return function(e) {
3038
+ var index, option;
3039
+ if (e.keyCode === KEY_BACKSPACE && this.$control_input.val() === '' && !this.$activeItems.length) {
3040
+ index = this.caretPos - 1;
3041
+ if (index >= 0 && index < this.items.length) {
3042
+ option = this.options[this.items[index]];
3043
+ if (this.deleteSelection(e)) {
3044
+ this.setTextboxValue(options.text.apply(this, [option]));
3045
+ this.refreshOptions(true);
3046
+ }
3047
+ e.preventDefault();
3048
+ return;
3049
+ }
3050
+ }
3051
+ return original.apply(this, arguments);
3052
+ };
3053
+ })();
3054
+ });
3055
+
3056
+
3057
+ return Selectize;
3058
+ }));