vue_crud 0.1.9.5 → 0.1.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/vue_crud/example_generator.rb +12 -0
  3. data/lib/generators/vue_crud/install_generator.rb +1 -1
  4. data/lib/generators/vue_crud/pace_generator.rb +11 -0
  5. data/lib/generators/vue_crud/semantic_generator.rb +12 -0
  6. data/lib/generators/vue_crud/templates/assets/images/semantic-ui/flags.png +0 -0
  7. data/lib/generators/vue_crud/templates/assets/javascripts/pace.min.js +1 -0
  8. data/lib/generators/vue_crud/templates/assets/javascripts/semantic-ui/checkbox.js +831 -0
  9. data/lib/generators/vue_crud/templates/assets/javascripts/semantic-ui/dimmer.js +708 -0
  10. data/lib/generators/vue_crud/templates/assets/javascripts/semantic-ui/dropdown.js +3741 -0
  11. data/lib/generators/vue_crud/templates/assets/javascripts/semantic-ui/modal.js +913 -0
  12. data/lib/generators/vue_crud/templates/assets/javascripts/semantic-ui/site.js +487 -0
  13. data/lib/generators/vue_crud/templates/assets/javascripts/semantic-ui/transition.js +1089 -0
  14. data/lib/generators/vue_crud/templates/assets/javascripts/vue.js +7402 -0
  15. data/lib/generators/vue_crud/templates/assets/stylesheets/pace.min.css +1 -0
  16. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/.DS_Store +0 -0
  17. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/collections/.DS_Store +0 -0
  18. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/collections/_all.scss +7 -0
  19. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/collections/_breadcrumb.scss +124 -0
  20. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/collections/_form.scss +1706 -0
  21. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/collections/_grid.scss +84 -0
  22. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/collections/_grid_bk.scss +2032 -0
  23. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/collections/_menu.scss +2021 -0
  24. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/collections/_message.scss +482 -0
  25. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/collections/_mixins.scss +3 -0
  26. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/collections/_table.scss +1108 -0
  27. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/collections/mixins/.DS_Store +0 -0
  28. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/collections/mixins/_clearfix.scss +1 -0
  29. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/collections/mixins/_grid-framework.scss +1 -0
  30. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/collections/mixins/_grid.scss +1 -0
  31. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_all.scss +15 -0
  32. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_button.scss +3470 -0
  33. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_container.scss +148 -0
  34. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_divider.scss +262 -0
  35. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_flag.scss +1031 -0
  36. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_header.scss +721 -0
  37. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_icon.scss +3148 -0
  38. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_image.scss +306 -0
  39. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_input.scss +517 -0
  40. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_label.scss +1314 -0
  41. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_list.scss +951 -0
  42. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_loader.scss +349 -0
  43. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_rail.scss +152 -0
  44. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_reveal.scss +307 -0
  45. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_segment.scss +799 -0
  46. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/elements/_step.scss +646 -0
  47. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/globals/_all.scss +3 -0
  48. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/globals/_reset.scss +424 -0
  49. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/globals/_site.scss +163 -0
  50. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/globals/_variables.scss +32 -0
  51. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_accordion.scss +256 -0
  52. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_all.scss +17 -0
  53. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_checkbox.scss +627 -0
  54. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_dimmer.scss +199 -0
  55. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_dropdown.scss +1425 -0
  56. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_embed.scss +167 -0
  57. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_modal.scss +509 -0
  58. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_nag.scss +147 -0
  59. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_popup.scss +764 -0
  60. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_progress.scss +516 -0
  61. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_rating.scss +265 -0
  62. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_search.scss +409 -0
  63. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_shape.scss +157 -0
  64. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_sidebar.scss +644 -0
  65. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_sticky.scss +78 -0
  66. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_tab.scss +91 -0
  67. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_transition.scss +1981 -0
  68. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/modules/_video.scss +125 -0
  69. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/views/_ad.scss +276 -0
  70. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/views/_all.scss +6 -0
  71. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/views/_card.scss +685 -0
  72. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/views/_comment.scss +270 -0
  73. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/views/_feed.scss +300 -0
  74. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/views/_item.scss +481 -0
  75. data/lib/generators/vue_crud/templates/assets/stylesheets/semantic-ui/views/_statistic.scss +583 -0
  76. data/lib/generators/vue_crud/templates/example/user.rb +10 -0
  77. data/lib/generators/vue_crud/templates/example/users/index.html.erb +154 -0
  78. data/lib/generators/vue_crud/templates/example/users/index.json.jbuilder +15 -0
  79. data/lib/generators/vue_crud/templates/example/users/show.json.jbuilder +11 -0
  80. data/lib/generators/vue_crud/templates/example/users_controller.rb +85 -0
  81. data/lib/generators/vue_crud/vuejs_generator.rb +10 -0
  82. data/lib/vue_crud/version.rb +1 -1
  83. data/vendor/assets/javascripts/vue_crud.js +24 -3
  84. data/vendor/assets/stylesheets/vue_crud.css +0 -2
  85. metadata +80 -6
  86. data/vue_crud-0.1.5.gem +0 -0
  87. data/vue_crud-0.1.6.gem +0 -0
  88. data/vue_crud-0.1.7.gem +0 -0
  89. data/vue_crud-0.1.8.gem +0 -0
  90. data/vue_crud-0.1.9.gem +0 -0
@@ -0,0 +1,3741 @@
1
+ /*!
2
+ * # Semantic UI - Dropdown
3
+ * http://github.com/semantic-org/semantic-ui/
4
+ *
5
+ *
6
+ * Released under the MIT license
7
+ * http://opensource.org/licenses/MIT
8
+ *
9
+ */
10
+
11
+ ;(function ($, window, document, undefined) {
12
+
13
+ "use strict";
14
+
15
+ window = (typeof window != 'undefined' && window.Math == Math)
16
+ ? window
17
+ : (typeof self != 'undefined' && self.Math == Math)
18
+ ? self
19
+ : Function('return this')()
20
+ ;
21
+
22
+ $.fn.dropdown = function(parameters) {
23
+ var
24
+ $allModules = $(this),
25
+ $document = $(document),
26
+
27
+ moduleSelector = $allModules.selector || '',
28
+
29
+ hasTouch = ('ontouchstart' in document.documentElement),
30
+ time = new Date().getTime(),
31
+ performance = [],
32
+
33
+ query = arguments[0],
34
+ methodInvoked = (typeof query == 'string'),
35
+ queryArguments = [].slice.call(arguments, 1),
36
+ returnedValue
37
+ ;
38
+
39
+ $allModules
40
+ .each(function(elementIndex) {
41
+ var
42
+ settings = ( $.isPlainObject(parameters) )
43
+ ? $.extend(true, {}, $.fn.dropdown.settings, parameters)
44
+ : $.extend({}, $.fn.dropdown.settings),
45
+
46
+ className = settings.className,
47
+ message = settings.message,
48
+ fields = settings.fields,
49
+ keys = settings.keys,
50
+ metadata = settings.metadata,
51
+ namespace = settings.namespace,
52
+ regExp = settings.regExp,
53
+ selector = settings.selector,
54
+ error = settings.error,
55
+ templates = settings.templates,
56
+
57
+ eventNamespace = '.' + namespace,
58
+ moduleNamespace = 'module-' + namespace,
59
+
60
+ $module = $(this),
61
+ $context = $(settings.context),
62
+ $text = $module.find(selector.text),
63
+ $search = $module.find(selector.search),
64
+ $sizer = $module.find(selector.sizer),
65
+ $input = $module.find(selector.input),
66
+ $icon = $module.find(selector.icon),
67
+
68
+ $combo = ($module.prev().find(selector.text).length > 0)
69
+ ? $module.prev().find(selector.text)
70
+ : $module.prev(),
71
+
72
+ $menu = $module.children(selector.menu),
73
+ $item = $menu.find(selector.item),
74
+
75
+ activated = false,
76
+ itemActivated = false,
77
+ internalChange = false,
78
+ element = this,
79
+ instance = $module.data(moduleNamespace),
80
+
81
+ initialLoad,
82
+ pageLostFocus,
83
+ willRefocus,
84
+ elementNamespace,
85
+ id,
86
+ selectObserver,
87
+ menuObserver,
88
+ module
89
+ ;
90
+
91
+ module = {
92
+
93
+ initialize: function() {
94
+ module.debug('Initializing dropdown', settings);
95
+
96
+ if( module.is.alreadySetup() ) {
97
+ module.setup.reference();
98
+ }
99
+ else {
100
+ module.setup.layout();
101
+ module.refreshData();
102
+
103
+ module.save.defaults();
104
+ module.restore.selected();
105
+
106
+ module.create.id();
107
+ module.bind.events();
108
+
109
+ module.observeChanges();
110
+ module.instantiate();
111
+ }
112
+
113
+ },
114
+
115
+ instantiate: function() {
116
+ module.verbose('Storing instance of dropdown', module);
117
+ instance = module;
118
+ $module
119
+ .data(moduleNamespace, module)
120
+ ;
121
+ },
122
+
123
+ destroy: function() {
124
+ module.verbose('Destroying previous dropdown', $module);
125
+ module.remove.tabbable();
126
+ $module
127
+ .off(eventNamespace)
128
+ .removeData(moduleNamespace)
129
+ ;
130
+ $menu
131
+ .off(eventNamespace)
132
+ ;
133
+ $document
134
+ .off(elementNamespace)
135
+ ;
136
+ module.disconnect.menuObserver();
137
+ module.disconnect.selectObserver();
138
+ },
139
+
140
+ observeChanges: function() {
141
+ if('MutationObserver' in window) {
142
+ selectObserver = new MutationObserver(module.event.select.mutation);
143
+ menuObserver = new MutationObserver(module.event.menu.mutation);
144
+ module.debug('Setting up mutation observer', selectObserver, menuObserver);
145
+ module.observe.select();
146
+ module.observe.menu();
147
+ }
148
+ },
149
+
150
+ disconnect: {
151
+ menuObserver: function() {
152
+ if(menuObserver) {
153
+ menuObserver.disconnect();
154
+ }
155
+ },
156
+ selectObserver: function() {
157
+ if(selectObserver) {
158
+ selectObserver.disconnect();
159
+ }
160
+ }
161
+ },
162
+ observe: {
163
+ select: function() {
164
+ if(module.has.input()) {
165
+ selectObserver.observe($input[0], {
166
+ childList : true,
167
+ subtree : true
168
+ });
169
+ }
170
+ },
171
+ menu: function() {
172
+ if(module.has.menu()) {
173
+ menuObserver.observe($menu[0], {
174
+ childList : true,
175
+ subtree : true
176
+ });
177
+ }
178
+ }
179
+ },
180
+
181
+ create: {
182
+ id: function() {
183
+ id = (Math.random().toString(16) + '000000000').substr(2, 8);
184
+ elementNamespace = '.' + id;
185
+ module.verbose('Creating unique id for element', id);
186
+ },
187
+ userChoice: function(values) {
188
+ var
189
+ $userChoices,
190
+ $userChoice,
191
+ isUserValue,
192
+ html
193
+ ;
194
+ values = values || module.get.userValues();
195
+ if(!values) {
196
+ return false;
197
+ }
198
+ values = $.isArray(values)
199
+ ? values
200
+ : [values]
201
+ ;
202
+ $.each(values, function(index, value) {
203
+ if(module.get.item(value) === false) {
204
+ html = settings.templates.addition( module.add.variables(message.addResult, value) );
205
+ $userChoice = $('<div />')
206
+ .html(html)
207
+ .attr('data-' + metadata.value, value)
208
+ .attr('data-' + metadata.text, value)
209
+ .addClass(className.addition)
210
+ .addClass(className.item)
211
+ ;
212
+ if(settings.hideAdditions) {
213
+ $userChoice.addClass(className.hidden);
214
+ }
215
+ $userChoices = ($userChoices === undefined)
216
+ ? $userChoice
217
+ : $userChoices.add($userChoice)
218
+ ;
219
+ module.verbose('Creating user choices for value', value, $userChoice);
220
+ }
221
+ });
222
+ return $userChoices;
223
+ },
224
+ userLabels: function(value) {
225
+ var
226
+ userValues = module.get.userValues()
227
+ ;
228
+ if(userValues) {
229
+ module.debug('Adding user labels', userValues);
230
+ $.each(userValues, function(index, value) {
231
+ module.verbose('Adding custom user value');
232
+ module.add.label(value, value);
233
+ });
234
+ }
235
+ },
236
+ menu: function() {
237
+ $menu = $('<div />')
238
+ .addClass(className.menu)
239
+ .appendTo($module)
240
+ ;
241
+ },
242
+ sizer: function() {
243
+ $sizer = $('<span />')
244
+ .addClass(className.sizer)
245
+ .insertAfter($search)
246
+ ;
247
+ }
248
+ },
249
+
250
+ search: function(query) {
251
+ query = (query !== undefined)
252
+ ? query
253
+ : module.get.query()
254
+ ;
255
+ module.verbose('Searching for query', query);
256
+ if(module.has.minCharacters(query)) {
257
+ module.filter(query);
258
+ }
259
+ else {
260
+ module.hide();
261
+ }
262
+ },
263
+
264
+ select: {
265
+ firstUnfiltered: function() {
266
+ module.verbose('Selecting first non-filtered element');
267
+ module.remove.selectedItem();
268
+ $item
269
+ .not(selector.unselectable)
270
+ .not(selector.addition + selector.hidden)
271
+ .eq(0)
272
+ .addClass(className.selected)
273
+ ;
274
+ },
275
+ nextAvailable: function($selected) {
276
+ $selected = $selected.eq(0);
277
+ var
278
+ $nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0),
279
+ $prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0),
280
+ hasNext = ($nextAvailable.length > 0)
281
+ ;
282
+ if(hasNext) {
283
+ module.verbose('Moving selection to', $nextAvailable);
284
+ $nextAvailable.addClass(className.selected);
285
+ }
286
+ else {
287
+ module.verbose('Moving selection to', $prevAvailable);
288
+ $prevAvailable.addClass(className.selected);
289
+ }
290
+ }
291
+ },
292
+
293
+ setup: {
294
+ api: function() {
295
+ var
296
+ apiSettings = {
297
+ debug : settings.debug,
298
+ urlData : {
299
+ value : module.get.value(),
300
+ query : module.get.query()
301
+ },
302
+ on : false
303
+ }
304
+ ;
305
+ module.verbose('First request, initializing API');
306
+ $module
307
+ .api(apiSettings)
308
+ ;
309
+ },
310
+ layout: function() {
311
+ if( $module.is('select') ) {
312
+ module.setup.select();
313
+ module.setup.returnedObject();
314
+ }
315
+ if( !module.has.menu() ) {
316
+ module.create.menu();
317
+ }
318
+ if( module.is.search() && !module.has.search() ) {
319
+ module.verbose('Adding search input');
320
+ $search = $('<input />')
321
+ .addClass(className.search)
322
+ .prop('autocomplete', 'off')
323
+ .insertBefore($text)
324
+ ;
325
+ }
326
+ if( module.is.multiple() && module.is.searchSelection() && !module.has.sizer()) {
327
+ module.create.sizer();
328
+ }
329
+ if(settings.allowTab) {
330
+ module.set.tabbable();
331
+ }
332
+ },
333
+ select: function() {
334
+ var
335
+ selectValues = module.get.selectValues()
336
+ ;
337
+ module.debug('Dropdown initialized on a select', selectValues);
338
+ if( $module.is('select') ) {
339
+ $input = $module;
340
+ }
341
+ // see if select is placed correctly already
342
+ if($input.parent(selector.dropdown).length > 0) {
343
+ module.debug('UI dropdown already exists. Creating dropdown menu only');
344
+ $module = $input.closest(selector.dropdown);
345
+ if( !module.has.menu() ) {
346
+ module.create.menu();
347
+ }
348
+ $menu = $module.children(selector.menu);
349
+ module.setup.menu(selectValues);
350
+ }
351
+ else {
352
+ module.debug('Creating entire dropdown from select');
353
+ $module = $('<div />')
354
+ .attr('class', $input.attr('class') )
355
+ .addClass(className.selection)
356
+ .addClass(className.dropdown)
357
+ .html( templates.dropdown(selectValues) )
358
+ .insertBefore($input)
359
+ ;
360
+ if($input.hasClass(className.multiple) && $input.prop('multiple') === false) {
361
+ module.error(error.missingMultiple);
362
+ $input.prop('multiple', true);
363
+ }
364
+ if($input.is('[multiple]')) {
365
+ module.set.multiple();
366
+ }
367
+ if ($input.prop('disabled')) {
368
+ module.debug('Disabling dropdown');
369
+ $module.addClass(className.disabled);
370
+ }
371
+ $input
372
+ .removeAttr('class')
373
+ .detach()
374
+ .prependTo($module)
375
+ ;
376
+ }
377
+ module.refresh();
378
+ },
379
+ menu: function(values) {
380
+ $menu.html( templates.menu(values, fields));
381
+ $item = $menu.find(selector.item);
382
+ },
383
+ reference: function() {
384
+ module.debug('Dropdown behavior was called on select, replacing with closest dropdown');
385
+ // replace module reference
386
+ $module = $module.parent(selector.dropdown);
387
+ module.refresh();
388
+ module.setup.returnedObject();
389
+ // invoke method in context of current instance
390
+ if(methodInvoked) {
391
+ instance = module;
392
+ module.invoke(query);
393
+ }
394
+ },
395
+ returnedObject: function() {
396
+ var
397
+ $firstModules = $allModules.slice(0, elementIndex),
398
+ $lastModules = $allModules.slice(elementIndex + 1)
399
+ ;
400
+ // adjust all modules to use correct reference
401
+ $allModules = $firstModules.add($module).add($lastModules);
402
+ }
403
+ },
404
+
405
+ refresh: function() {
406
+ module.refreshSelectors();
407
+ module.refreshData();
408
+ },
409
+
410
+ refreshItems: function() {
411
+ $item = $menu.find(selector.item);
412
+ },
413
+
414
+ refreshSelectors: function() {
415
+ module.verbose('Refreshing selector cache');
416
+ $text = $module.find(selector.text);
417
+ $search = $module.find(selector.search);
418
+ $input = $module.find(selector.input);
419
+ $icon = $module.find(selector.icon);
420
+ $combo = ($module.prev().find(selector.text).length > 0)
421
+ ? $module.prev().find(selector.text)
422
+ : $module.prev()
423
+ ;
424
+ $menu = $module.children(selector.menu);
425
+ $item = $menu.find(selector.item);
426
+ },
427
+
428
+ refreshData: function() {
429
+ module.verbose('Refreshing cached metadata');
430
+ $item
431
+ .removeData(metadata.text)
432
+ .removeData(metadata.value)
433
+ ;
434
+ },
435
+
436
+ clearData: function() {
437
+ module.verbose('Clearing metadata');
438
+ $item
439
+ .removeData(metadata.text)
440
+ .removeData(metadata.value)
441
+ ;
442
+ $module
443
+ .removeData(metadata.defaultText)
444
+ .removeData(metadata.defaultValue)
445
+ .removeData(metadata.placeholderText)
446
+ ;
447
+ },
448
+
449
+ toggle: function() {
450
+ module.verbose('Toggling menu visibility');
451
+ if( !module.is.active() ) {
452
+ module.show();
453
+ }
454
+ else {
455
+ module.hide();
456
+ }
457
+ },
458
+
459
+ show: function(callback) {
460
+ callback = $.isFunction(callback)
461
+ ? callback
462
+ : function(){}
463
+ ;
464
+ if( module.can.show() && !module.is.active() ) {
465
+ module.debug('Showing dropdown');
466
+ if(module.has.message() && !(module.has.maxSelections() || module.has.allResultsFiltered()) ) {
467
+ module.remove.message();
468
+ }
469
+ if(module.is.allFiltered()) {
470
+ return true;
471
+ }
472
+ if(settings.onShow.call(element) !== false) {
473
+ module.animate.show(function() {
474
+ if( module.can.click() ) {
475
+ module.bind.intent();
476
+ }
477
+ if(module.has.menuSearch()) {
478
+ module.focusSearch();
479
+ }
480
+ module.set.visible();
481
+ callback.call(element);
482
+ });
483
+ }
484
+ }
485
+ },
486
+
487
+ hide: function(callback) {
488
+ callback = $.isFunction(callback)
489
+ ? callback
490
+ : function(){}
491
+ ;
492
+ if( module.is.active() ) {
493
+ module.debug('Hiding dropdown');
494
+ if(settings.onHide.call(element) !== false) {
495
+ module.animate.hide(function() {
496
+ module.remove.visible();
497
+ callback.call(element);
498
+ });
499
+ }
500
+ }
501
+ },
502
+
503
+ hideOthers: function() {
504
+ module.verbose('Finding other dropdowns to hide');
505
+ $allModules
506
+ .not($module)
507
+ .has(selector.menu + '.' + className.visible)
508
+ .dropdown('hide')
509
+ ;
510
+ },
511
+
512
+ hideMenu: function() {
513
+ module.verbose('Hiding menu instantaneously');
514
+ module.remove.active();
515
+ module.remove.visible();
516
+ $menu.transition('hide');
517
+ },
518
+
519
+ hideSubMenus: function() {
520
+ var
521
+ $subMenus = $menu.children(selector.item).find(selector.menu)
522
+ ;
523
+ module.verbose('Hiding sub menus', $subMenus);
524
+ $subMenus.transition('hide');
525
+ },
526
+
527
+ bind: {
528
+ events: function() {
529
+ if(hasTouch) {
530
+ module.bind.touchEvents();
531
+ }
532
+ module.bind.keyboardEvents();
533
+ module.bind.inputEvents();
534
+ module.bind.mouseEvents();
535
+ },
536
+ touchEvents: function() {
537
+ module.debug('Touch device detected binding additional touch events');
538
+ if( module.is.searchSelection() ) {
539
+ // do nothing special yet
540
+ }
541
+ else if( module.is.single() ) {
542
+ $module
543
+ .on('touchstart' + eventNamespace, module.event.test.toggle)
544
+ ;
545
+ }
546
+ $menu
547
+ .on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter)
548
+ ;
549
+ },
550
+ keyboardEvents: function() {
551
+ module.verbose('Binding keyboard events');
552
+ $module
553
+ .on('keydown' + eventNamespace, module.event.keydown)
554
+ ;
555
+ if( module.has.search() ) {
556
+ $module
557
+ .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input)
558
+ ;
559
+ }
560
+ if( module.is.multiple() ) {
561
+ $document
562
+ .on('keydown' + elementNamespace, module.event.document.keydown)
563
+ ;
564
+ }
565
+ },
566
+ inputEvents: function() {
567
+ module.verbose('Binding input change events');
568
+ $module
569
+ .on('change' + eventNamespace, selector.input, module.event.change)
570
+ ;
571
+ },
572
+ mouseEvents: function() {
573
+ module.verbose('Binding mouse events');
574
+ if(module.is.multiple()) {
575
+ $module
576
+ .on('click' + eventNamespace, selector.label, module.event.label.click)
577
+ .on('click' + eventNamespace, selector.remove, module.event.remove.click)
578
+ ;
579
+ }
580
+ if( module.is.searchSelection() ) {
581
+ $module
582
+ .on('mousedown' + eventNamespace, module.event.mousedown)
583
+ .on('mouseup' + eventNamespace, module.event.mouseup)
584
+ .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown)
585
+ .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup)
586
+ .on('click' + eventNamespace, selector.icon, module.event.icon.click)
587
+ .on('focus' + eventNamespace, selector.search, module.event.search.focus)
588
+ .on('click' + eventNamespace, selector.search, module.event.search.focus)
589
+ .on('blur' + eventNamespace, selector.search, module.event.search.blur)
590
+ .on('click' + eventNamespace, selector.text, module.event.text.focus)
591
+ ;
592
+ if(module.is.multiple()) {
593
+ $module
594
+ .on('click' + eventNamespace, module.event.click)
595
+ ;
596
+ }
597
+ }
598
+ else {
599
+ if(settings.on == 'click') {
600
+ $module
601
+ .on('click' + eventNamespace, selector.icon, module.event.icon.click)
602
+ .on('click' + eventNamespace, module.event.test.toggle)
603
+ ;
604
+ }
605
+ else if(settings.on == 'hover') {
606
+ $module
607
+ .on('mouseenter' + eventNamespace, module.delay.show)
608
+ .on('mouseleave' + eventNamespace, module.delay.hide)
609
+ ;
610
+ }
611
+ else {
612
+ $module
613
+ .on(settings.on + eventNamespace, module.toggle)
614
+ ;
615
+ }
616
+ $module
617
+ .on('mousedown' + eventNamespace, module.event.mousedown)
618
+ .on('mouseup' + eventNamespace, module.event.mouseup)
619
+ .on('focus' + eventNamespace, module.event.focus)
620
+ .on('blur' + eventNamespace, module.event.blur)
621
+ ;
622
+ }
623
+ $menu
624
+ .on('mouseenter' + eventNamespace, selector.item, module.event.item.mouseenter)
625
+ .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave)
626
+ .on('click' + eventNamespace, selector.item, module.event.item.click)
627
+ ;
628
+ },
629
+ intent: function() {
630
+ module.verbose('Binding hide intent event to document');
631
+ if(hasTouch) {
632
+ $document
633
+ .on('touchstart' + elementNamespace, module.event.test.touch)
634
+ .on('touchmove' + elementNamespace, module.event.test.touch)
635
+ ;
636
+ }
637
+ $document
638
+ .on('click' + elementNamespace, module.event.test.hide)
639
+ ;
640
+ }
641
+ },
642
+
643
+ unbind: {
644
+ intent: function() {
645
+ module.verbose('Removing hide intent event from document');
646
+ if(hasTouch) {
647
+ $document
648
+ .off('touchstart' + elementNamespace)
649
+ .off('touchmove' + elementNamespace)
650
+ ;
651
+ }
652
+ $document
653
+ .off('click' + elementNamespace)
654
+ ;
655
+ }
656
+ },
657
+
658
+ filter: function(query) {
659
+ var
660
+ searchTerm = (query !== undefined)
661
+ ? query
662
+ : module.get.query(),
663
+ afterFiltered = function() {
664
+ if(module.is.multiple()) {
665
+ module.filterActive();
666
+ }
667
+ module.select.firstUnfiltered();
668
+ if( module.has.allResultsFiltered() ) {
669
+ if( settings.onNoResults.call(element, searchTerm) ) {
670
+ if(settings.allowAdditions) {
671
+ if(settings.hideAdditions) {
672
+ module.verbose('User addition with no menu, setting empty style');
673
+ module.set.empty();
674
+ module.hideMenu();
675
+ }
676
+ }
677
+ else {
678
+ module.verbose('All items filtered, showing message', searchTerm);
679
+ module.add.message(message.noResults);
680
+ }
681
+ }
682
+ else {
683
+ module.verbose('All items filtered, hiding dropdown', searchTerm);
684
+ module.hideMenu();
685
+ }
686
+ }
687
+ else {
688
+ module.remove.empty();
689
+ module.remove.message();
690
+ }
691
+ if(settings.allowAdditions) {
692
+ module.add.userSuggestion(query);
693
+ }
694
+ if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) {
695
+ module.show();
696
+ }
697
+ }
698
+ ;
699
+ if(settings.useLabels && module.has.maxSelections()) {
700
+ return;
701
+ }
702
+ if(settings.apiSettings) {
703
+ if( module.can.useAPI() ) {
704
+ module.queryRemote(searchTerm, function() {
705
+ afterFiltered();
706
+ });
707
+ }
708
+ else {
709
+ module.error(error.noAPI);
710
+ }
711
+ }
712
+ else {
713
+ module.filterItems(searchTerm);
714
+ afterFiltered();
715
+ }
716
+ },
717
+
718
+ queryRemote: function(query, callback) {
719
+ var
720
+ apiSettings = {
721
+ errorDuration : false,
722
+ cache : 'local',
723
+ throttle : settings.throttle,
724
+ urlData : {
725
+ query: query
726
+ },
727
+ onError: function() {
728
+ module.add.message(message.serverError);
729
+ callback();
730
+ },
731
+ onFailure: function() {
732
+ module.add.message(message.serverError);
733
+ callback();
734
+ },
735
+ onSuccess : function(response) {
736
+ module.remove.message();
737
+ module.setup.menu({
738
+ values: response[fields.remoteValues]
739
+ });
740
+ callback();
741
+ }
742
+ }
743
+ ;
744
+ if( !$module.api('get request') ) {
745
+ module.setup.api();
746
+ }
747
+ apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings);
748
+ $module
749
+ .api('setting', apiSettings)
750
+ .api('query')
751
+ ;
752
+ },
753
+
754
+ filterItems: function(query) {
755
+ var
756
+ searchTerm = (query !== undefined)
757
+ ? query
758
+ : module.get.query(),
759
+ results = null,
760
+ escapedTerm = module.escape.regExp(searchTerm),
761
+ beginsWithRegExp = new RegExp('^' + escapedTerm, 'igm')
762
+ ;
763
+ // avoid loop if we're matching nothing
764
+ if( module.has.query() ) {
765
+ results = [];
766
+
767
+ module.verbose('Searching for matching values', searchTerm);
768
+ $item
769
+ .each(function(){
770
+ var
771
+ $choice = $(this),
772
+ text,
773
+ value
774
+ ;
775
+ if(settings.match == 'both' || settings.match == 'text') {
776
+ text = String(module.get.choiceText($choice, false));
777
+ if(text.search(beginsWithRegExp) !== -1) {
778
+ results.push(this);
779
+ return true;
780
+ }
781
+ else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text)) {
782
+ results.push(this);
783
+ return true;
784
+ }
785
+ else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, text)) {
786
+ results.push(this);
787
+ return true;
788
+ }
789
+ }
790
+ if(settings.match == 'both' || settings.match == 'value') {
791
+ value = String(module.get.choiceValue($choice, text));
792
+
793
+ if(value.search(beginsWithRegExp) !== -1) {
794
+ results.push(this);
795
+ return true;
796
+ }
797
+ else if(settings.fullTextSearch && module.fuzzySearch(searchTerm, value)) {
798
+ results.push(this);
799
+ return true;
800
+ }
801
+ }
802
+ })
803
+ ;
804
+ }
805
+ module.debug('Showing only matched items', searchTerm);
806
+ module.remove.filteredItem();
807
+ if(results) {
808
+ $item
809
+ .not(results)
810
+ .addClass(className.filtered)
811
+ ;
812
+ }
813
+ },
814
+
815
+ fuzzySearch: function(query, term) {
816
+ var
817
+ termLength = term.length,
818
+ queryLength = query.length
819
+ ;
820
+ query = query.toLowerCase();
821
+ term = term.toLowerCase();
822
+ if(queryLength > termLength) {
823
+ return false;
824
+ }
825
+ if(queryLength === termLength) {
826
+ return (query === term);
827
+ }
828
+ search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) {
829
+ var
830
+ queryCharacter = query.charCodeAt(characterIndex)
831
+ ;
832
+ while(nextCharacterIndex < termLength) {
833
+ if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) {
834
+ continue search;
835
+ }
836
+ }
837
+ return false;
838
+ }
839
+ return true;
840
+ },
841
+ exactSearch: function (query, term) {
842
+ query = query.toLowerCase();
843
+ term = term.toLowerCase();
844
+ if(term.indexOf(query) > -1) {
845
+ return true;
846
+ }
847
+ return false;
848
+ },
849
+ filterActive: function() {
850
+ if(settings.useLabels) {
851
+ $item.filter('.' + className.active)
852
+ .addClass(className.filtered)
853
+ ;
854
+ }
855
+ },
856
+
857
+ focusSearch: function(skipHandler) {
858
+ if( module.has.search() && !module.is.focusedOnSearch() ) {
859
+ if(skipHandler) {
860
+ $module.off('focus' + eventNamespace, selector.search);
861
+ $search.focus();
862
+ $module.on('focus' + eventNamespace, selector.search, module.event.search.focus);
863
+ }
864
+ else {
865
+ $search.focus();
866
+ }
867
+ }
868
+ },
869
+
870
+ forceSelection: function() {
871
+ var
872
+ $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0),
873
+ $activeItem = $item.not(className.filtered).filter('.' + className.active).eq(0),
874
+ $selectedItem = ($currentlySelected.length > 0)
875
+ ? $currentlySelected
876
+ : $activeItem,
877
+ hasSelected = ($selectedItem.length > 0)
878
+ ;
879
+ if(hasSelected) {
880
+ module.debug('Forcing partial selection to selected item', $selectedItem);
881
+ module.event.item.click.call($selectedItem, {}, true);
882
+ return;
883
+ }
884
+ else {
885
+ if(settings.allowAdditions) {
886
+ module.set.selected(module.get.query());
887
+ module.remove.searchTerm();
888
+ }
889
+ else {
890
+ module.remove.searchTerm();
891
+ }
892
+ }
893
+ },
894
+
895
+ event: {
896
+ change: function() {
897
+ if(!internalChange) {
898
+ module.debug('Input changed, updating selection');
899
+ module.set.selected();
900
+ }
901
+ },
902
+ focus: function() {
903
+ if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) {
904
+ module.show();
905
+ }
906
+ },
907
+ blur: function(event) {
908
+ pageLostFocus = (document.activeElement === this);
909
+ if(!activated && !pageLostFocus) {
910
+ module.remove.activeLabel();
911
+ module.hide();
912
+ }
913
+ },
914
+ mousedown: function() {
915
+ if(module.is.searchSelection()) {
916
+ // prevent menu hiding on immediate re-focus
917
+ willRefocus = true;
918
+ }
919
+ else {
920
+ // prevents focus callback from occurring on mousedown
921
+ activated = true;
922
+ }
923
+ },
924
+ mouseup: function() {
925
+ if(module.is.searchSelection()) {
926
+ // prevent menu hiding on immediate re-focus
927
+ willRefocus = false;
928
+ }
929
+ else {
930
+ activated = false;
931
+ }
932
+ },
933
+ click: function(event) {
934
+ var
935
+ $target = $(event.target)
936
+ ;
937
+ // focus search
938
+ if($target.is($module)) {
939
+ if(!module.is.focusedOnSearch()) {
940
+ module.focusSearch();
941
+ }
942
+ else {
943
+ module.show();
944
+ }
945
+ }
946
+ },
947
+ search: {
948
+ focus: function() {
949
+ activated = true;
950
+ if(module.is.multiple()) {
951
+ module.remove.activeLabel();
952
+ }
953
+ if(settings.showOnFocus) {
954
+ module.search();
955
+ }
956
+ },
957
+ blur: function(event) {
958
+ pageLostFocus = (document.activeElement === this);
959
+ if(!willRefocus) {
960
+ if(!itemActivated && !pageLostFocus) {
961
+ if(settings.forceSelection) {
962
+ module.forceSelection();
963
+ }
964
+ module.hide();
965
+ }
966
+ }
967
+ willRefocus = false;
968
+ }
969
+ },
970
+ icon: {
971
+ click: function(event) {
972
+ module.toggle();
973
+ }
974
+ },
975
+ text: {
976
+ focus: function(event) {
977
+ activated = true;
978
+ module.focusSearch();
979
+ }
980
+ },
981
+ input: function(event) {
982
+ if(module.is.multiple() || module.is.searchSelection()) {
983
+ module.set.filtered();
984
+ }
985
+ clearTimeout(module.timer);
986
+ module.timer = setTimeout(module.search, settings.delay.search);
987
+ },
988
+ label: {
989
+ click: function(event) {
990
+ var
991
+ $label = $(this),
992
+ $labels = $module.find(selector.label),
993
+ $activeLabels = $labels.filter('.' + className.active),
994
+ $nextActive = $label.nextAll('.' + className.active),
995
+ $prevActive = $label.prevAll('.' + className.active),
996
+ $range = ($nextActive.length > 0)
997
+ ? $label.nextUntil($nextActive).add($activeLabels).add($label)
998
+ : $label.prevUntil($prevActive).add($activeLabels).add($label)
999
+ ;
1000
+ if(event.shiftKey) {
1001
+ $activeLabels.removeClass(className.active);
1002
+ $range.addClass(className.active);
1003
+ }
1004
+ else if(event.ctrlKey) {
1005
+ $label.toggleClass(className.active);
1006
+ }
1007
+ else {
1008
+ $activeLabels.removeClass(className.active);
1009
+ $label.addClass(className.active);
1010
+ }
1011
+ settings.onLabelSelect.apply(this, $labels.filter('.' + className.active));
1012
+ }
1013
+ },
1014
+ remove: {
1015
+ click: function() {
1016
+ var
1017
+ $label = $(this).parent()
1018
+ ;
1019
+ if( $label.hasClass(className.active) ) {
1020
+ // remove all selected labels
1021
+ module.remove.activeLabels();
1022
+ }
1023
+ else {
1024
+ // remove this label only
1025
+ module.remove.activeLabels( $label );
1026
+ }
1027
+ }
1028
+ },
1029
+ test: {
1030
+ toggle: function(event) {
1031
+ var
1032
+ toggleBehavior = (module.is.multiple())
1033
+ ? module.show
1034
+ : module.toggle
1035
+ ;
1036
+ if(module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) {
1037
+ return;
1038
+ }
1039
+ if( module.determine.eventOnElement(event, toggleBehavior) ) {
1040
+ event.preventDefault();
1041
+ }
1042
+ },
1043
+ touch: function(event) {
1044
+ module.determine.eventOnElement(event, function() {
1045
+ if(event.type == 'touchstart') {
1046
+ module.timer = setTimeout(function() {
1047
+ module.hide();
1048
+ }, settings.delay.touch);
1049
+ }
1050
+ else if(event.type == 'touchmove') {
1051
+ clearTimeout(module.timer);
1052
+ }
1053
+ });
1054
+ event.stopPropagation();
1055
+ },
1056
+ hide: function(event) {
1057
+ module.determine.eventInModule(event, module.hide);
1058
+ }
1059
+ },
1060
+ select: {
1061
+ mutation: function(mutations) {
1062
+ module.debug('<select> modified, recreating menu');
1063
+ module.setup.select();
1064
+ }
1065
+ },
1066
+ menu: {
1067
+ mutation: function(mutations) {
1068
+ var
1069
+ mutation = mutations[0],
1070
+ $addedNode = mutation.addedNodes
1071
+ ? $(mutation.addedNodes[0])
1072
+ : $(false),
1073
+ $removedNode = mutation.removedNodes
1074
+ ? $(mutation.removedNodes[0])
1075
+ : $(false),
1076
+ $changedNodes = $addedNode.add($removedNode),
1077
+ isUserAddition = $changedNodes.is(selector.addition) || $changedNodes.closest(selector.addition).length > 0,
1078
+ isMessage = $changedNodes.is(selector.message) || $changedNodes.closest(selector.message).length > 0
1079
+ ;
1080
+ if(isUserAddition || isMessage) {
1081
+ module.debug('Updating item selector cache');
1082
+ module.refreshItems();
1083
+ }
1084
+ else {
1085
+ module.debug('Menu modified, updating selector cache');
1086
+ module.refresh();
1087
+ }
1088
+ },
1089
+ mousedown: function() {
1090
+ itemActivated = true;
1091
+ },
1092
+ mouseup: function() {
1093
+ itemActivated = false;
1094
+ }
1095
+ },
1096
+ item: {
1097
+ mouseenter: function(event) {
1098
+ var
1099
+ $target = $(event.target),
1100
+ $item = $(this),
1101
+ $subMenu = $item.children(selector.menu),
1102
+ $otherMenus = $item.siblings(selector.item).children(selector.menu),
1103
+ hasSubMenu = ($subMenu.length > 0),
1104
+ isBubbledEvent = ($subMenu.find($target).length > 0)
1105
+ ;
1106
+ if( !isBubbledEvent && hasSubMenu ) {
1107
+ clearTimeout(module.itemTimer);
1108
+ module.itemTimer = setTimeout(function() {
1109
+ module.verbose('Showing sub-menu', $subMenu);
1110
+ $.each($otherMenus, function() {
1111
+ module.animate.hide(false, $(this));
1112
+ });
1113
+ module.animate.show(false, $subMenu);
1114
+ }, settings.delay.show);
1115
+ event.preventDefault();
1116
+ }
1117
+ },
1118
+ mouseleave: function(event) {
1119
+ var
1120
+ $subMenu = $(this).children(selector.menu)
1121
+ ;
1122
+ if($subMenu.length > 0) {
1123
+ clearTimeout(module.itemTimer);
1124
+ module.itemTimer = setTimeout(function() {
1125
+ module.verbose('Hiding sub-menu', $subMenu);
1126
+ module.animate.hide(false, $subMenu);
1127
+ }, settings.delay.hide);
1128
+ }
1129
+ },
1130
+ click: function (event, skipRefocus) {
1131
+ var
1132
+ $choice = $(this),
1133
+ $target = (event)
1134
+ ? $(event.target)
1135
+ : $(''),
1136
+ $subMenu = $choice.find(selector.menu),
1137
+ text = module.get.choiceText($choice),
1138
+ value = module.get.choiceValue($choice, text),
1139
+ hasSubMenu = ($subMenu.length > 0),
1140
+ isBubbledEvent = ($subMenu.find($target).length > 0)
1141
+ ;
1142
+ if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) {
1143
+ if(module.is.searchSelection()) {
1144
+ if(settings.allowAdditions) {
1145
+ module.remove.userAddition();
1146
+ }
1147
+ module.remove.searchTerm();
1148
+ if(!module.is.focusedOnSearch() && !(skipRefocus == true)) {
1149
+ module.focusSearch(true);
1150
+ }
1151
+ }
1152
+ if(!settings.useLabels) {
1153
+ module.remove.filteredItem();
1154
+ module.set.scrollPosition($choice);
1155
+ }
1156
+ module.determine.selectAction.call(this, text, value);
1157
+ }
1158
+ }
1159
+ },
1160
+
1161
+ document: {
1162
+ // label selection should occur even when element has no focus
1163
+ keydown: function(event) {
1164
+ var
1165
+ pressedKey = event.which,
1166
+ isShortcutKey = module.is.inObject(pressedKey, keys)
1167
+ ;
1168
+ if(isShortcutKey) {
1169
+ var
1170
+ $label = $module.find(selector.label),
1171
+ $activeLabel = $label.filter('.' + className.active),
1172
+ activeValue = $activeLabel.data(metadata.value),
1173
+ labelIndex = $label.index($activeLabel),
1174
+ labelCount = $label.length,
1175
+ hasActiveLabel = ($activeLabel.length > 0),
1176
+ hasMultipleActive = ($activeLabel.length > 1),
1177
+ isFirstLabel = (labelIndex === 0),
1178
+ isLastLabel = (labelIndex + 1 == labelCount),
1179
+ isSearch = module.is.searchSelection(),
1180
+ isFocusedOnSearch = module.is.focusedOnSearch(),
1181
+ isFocused = module.is.focused(),
1182
+ caretAtStart = (isFocusedOnSearch && module.get.caretPosition() === 0),
1183
+ $nextLabel
1184
+ ;
1185
+ if(isSearch && !hasActiveLabel && !isFocusedOnSearch) {
1186
+ return;
1187
+ }
1188
+
1189
+ if(pressedKey == keys.leftArrow) {
1190
+ // activate previous label
1191
+ if((isFocused || caretAtStart) && !hasActiveLabel) {
1192
+ module.verbose('Selecting previous label');
1193
+ $label.last().addClass(className.active);
1194
+ }
1195
+ else if(hasActiveLabel) {
1196
+ if(!event.shiftKey) {
1197
+ module.verbose('Selecting previous label');
1198
+ $label.removeClass(className.active);
1199
+ }
1200
+ else {
1201
+ module.verbose('Adding previous label to selection');
1202
+ }
1203
+ if(isFirstLabel && !hasMultipleActive) {
1204
+ $activeLabel.addClass(className.active);
1205
+ }
1206
+ else {
1207
+ $activeLabel.prev(selector.siblingLabel)
1208
+ .addClass(className.active)
1209
+ .end()
1210
+ ;
1211
+ }
1212
+ event.preventDefault();
1213
+ }
1214
+ }
1215
+ else if(pressedKey == keys.rightArrow) {
1216
+ // activate first label
1217
+ if(isFocused && !hasActiveLabel) {
1218
+ $label.first().addClass(className.active);
1219
+ }
1220
+ // activate next label
1221
+ if(hasActiveLabel) {
1222
+ if(!event.shiftKey) {
1223
+ module.verbose('Selecting next label');
1224
+ $label.removeClass(className.active);
1225
+ }
1226
+ else {
1227
+ module.verbose('Adding next label to selection');
1228
+ }
1229
+ if(isLastLabel) {
1230
+ if(isSearch) {
1231
+ if(!isFocusedOnSearch) {
1232
+ module.focusSearch();
1233
+ }
1234
+ else {
1235
+ $label.removeClass(className.active);
1236
+ }
1237
+ }
1238
+ else if(hasMultipleActive) {
1239
+ $activeLabel.next(selector.siblingLabel).addClass(className.active);
1240
+ }
1241
+ else {
1242
+ $activeLabel.addClass(className.active);
1243
+ }
1244
+ }
1245
+ else {
1246
+ $activeLabel.next(selector.siblingLabel).addClass(className.active);
1247
+ }
1248
+ event.preventDefault();
1249
+ }
1250
+ }
1251
+ else if(pressedKey == keys.deleteKey || pressedKey == keys.backspace) {
1252
+ if(hasActiveLabel) {
1253
+ module.verbose('Removing active labels');
1254
+ if(isLastLabel) {
1255
+ if(isSearch && !isFocusedOnSearch) {
1256
+ module.focusSearch();
1257
+ }
1258
+ }
1259
+ $activeLabel.last().next(selector.siblingLabel).addClass(className.active);
1260
+ module.remove.activeLabels($activeLabel);
1261
+ event.preventDefault();
1262
+ }
1263
+ else if(caretAtStart && !hasActiveLabel && pressedKey == keys.backspace) {
1264
+ module.verbose('Removing last label on input backspace');
1265
+ $activeLabel = $label.last().addClass(className.active);
1266
+ module.remove.activeLabels($activeLabel);
1267
+ }
1268
+ }
1269
+ else {
1270
+ $activeLabel.removeClass(className.active);
1271
+ }
1272
+ }
1273
+ }
1274
+ },
1275
+
1276
+ keydown: function(event) {
1277
+ var
1278
+ pressedKey = event.which,
1279
+ isShortcutKey = module.is.inObject(pressedKey, keys)
1280
+ ;
1281
+ if(isShortcutKey) {
1282
+ var
1283
+ $currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0),
1284
+ $activeItem = $menu.children('.' + className.active).eq(0),
1285
+ $selectedItem = ($currentlySelected.length > 0)
1286
+ ? $currentlySelected
1287
+ : $activeItem,
1288
+ $visibleItems = ($selectedItem.length > 0)
1289
+ ? $selectedItem.siblings(':not(.' + className.filtered +')').addBack()
1290
+ : $menu.children(':not(.' + className.filtered +')'),
1291
+ $subMenu = $selectedItem.children(selector.menu),
1292
+ $parentMenu = $selectedItem.closest(selector.menu),
1293
+ inVisibleMenu = ($parentMenu.hasClass(className.visible) || $parentMenu.hasClass(className.animating) || $parentMenu.parent(selector.menu).length > 0),
1294
+ hasSubMenu = ($subMenu.length> 0),
1295
+ hasSelectedItem = ($selectedItem.length > 0),
1296
+ selectedIsSelectable = ($selectedItem.not(selector.unselectable).length > 0),
1297
+ delimiterPressed = (pressedKey == keys.delimiter && settings.allowAdditions && module.is.multiple()),
1298
+ isAdditionWithoutMenu = (settings.allowAdditions && settings.hideAdditions && (pressedKey == keys.enter || delimiterPressed) && selectedIsSelectable),
1299
+ $nextItem,
1300
+ isSubMenuItem,
1301
+ newIndex
1302
+ ;
1303
+ // allow selection with menu closed
1304
+ if(isAdditionWithoutMenu) {
1305
+ module.verbose('Selecting item from keyboard shortcut', $selectedItem);
1306
+ module.event.item.click.call($selectedItem, event);
1307
+ if(module.is.searchSelection()) {
1308
+ module.remove.searchTerm();
1309
+ }
1310
+ }
1311
+
1312
+ // visible menu keyboard shortcuts
1313
+ if( module.is.visible() ) {
1314
+
1315
+ // enter (select or open sub-menu)
1316
+ if(pressedKey == keys.enter || delimiterPressed) {
1317
+ if(pressedKey == keys.enter && hasSelectedItem && hasSubMenu && !settings.allowCategorySelection) {
1318
+ module.verbose('Pressed enter on unselectable category, opening sub menu');
1319
+ pressedKey = keys.rightArrow;
1320
+ }
1321
+ else if(selectedIsSelectable) {
1322
+ module.verbose('Selecting item from keyboard shortcut', $selectedItem);
1323
+ module.event.item.click.call($selectedItem, event);
1324
+ if(module.is.searchSelection()) {
1325
+ module.remove.searchTerm();
1326
+ }
1327
+ }
1328
+ event.preventDefault();
1329
+ }
1330
+
1331
+ // sub-menu actions
1332
+ if(hasSelectedItem) {
1333
+
1334
+ if(pressedKey == keys.leftArrow) {
1335
+
1336
+ isSubMenuItem = ($parentMenu[0] !== $menu[0]);
1337
+
1338
+ if(isSubMenuItem) {
1339
+ module.verbose('Left key pressed, closing sub-menu');
1340
+ module.animate.hide(false, $parentMenu);
1341
+ $selectedItem
1342
+ .removeClass(className.selected)
1343
+ ;
1344
+ $parentMenu
1345
+ .closest(selector.item)
1346
+ .addClass(className.selected)
1347
+ ;
1348
+ event.preventDefault();
1349
+ }
1350
+ }
1351
+
1352
+ // right arrow (show sub-menu)
1353
+ if(pressedKey == keys.rightArrow) {
1354
+ if(hasSubMenu) {
1355
+ module.verbose('Right key pressed, opening sub-menu');
1356
+ module.animate.show(false, $subMenu);
1357
+ $selectedItem
1358
+ .removeClass(className.selected)
1359
+ ;
1360
+ $subMenu
1361
+ .find(selector.item).eq(0)
1362
+ .addClass(className.selected)
1363
+ ;
1364
+ event.preventDefault();
1365
+ }
1366
+ }
1367
+ }
1368
+
1369
+ // up arrow (traverse menu up)
1370
+ if(pressedKey == keys.upArrow) {
1371
+ $nextItem = (hasSelectedItem && inVisibleMenu)
1372
+ ? $selectedItem.prevAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
1373
+ : $item.eq(0)
1374
+ ;
1375
+ if($visibleItems.index( $nextItem ) < 0) {
1376
+ module.verbose('Up key pressed but reached top of current menu');
1377
+ event.preventDefault();
1378
+ return;
1379
+ }
1380
+ else {
1381
+ module.verbose('Up key pressed, changing active item');
1382
+ $selectedItem
1383
+ .removeClass(className.selected)
1384
+ ;
1385
+ $nextItem
1386
+ .addClass(className.selected)
1387
+ ;
1388
+ module.set.scrollPosition($nextItem);
1389
+ if(settings.selectOnKeydown && module.is.single()) {
1390
+ module.set.selectedItem($nextItem);
1391
+ }
1392
+ }
1393
+ event.preventDefault();
1394
+ }
1395
+
1396
+ // down arrow (traverse menu down)
1397
+ if(pressedKey == keys.downArrow) {
1398
+ $nextItem = (hasSelectedItem && inVisibleMenu)
1399
+ ? $nextItem = $selectedItem.nextAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
1400
+ : $item.eq(0)
1401
+ ;
1402
+ if($nextItem.length === 0) {
1403
+ module.verbose('Down key pressed but reached bottom of current menu');
1404
+ event.preventDefault();
1405
+ return;
1406
+ }
1407
+ else {
1408
+ module.verbose('Down key pressed, changing active item');
1409
+ $item
1410
+ .removeClass(className.selected)
1411
+ ;
1412
+ $nextItem
1413
+ .addClass(className.selected)
1414
+ ;
1415
+ module.set.scrollPosition($nextItem);
1416
+ if(settings.selectOnKeydown && module.is.single()) {
1417
+ module.set.selectedItem($nextItem);
1418
+ }
1419
+ }
1420
+ event.preventDefault();
1421
+ }
1422
+
1423
+ // page down (show next page)
1424
+ if(pressedKey == keys.pageUp) {
1425
+ module.scrollPage('up');
1426
+ event.preventDefault();
1427
+ }
1428
+ if(pressedKey == keys.pageDown) {
1429
+ module.scrollPage('down');
1430
+ event.preventDefault();
1431
+ }
1432
+
1433
+ // escape (close menu)
1434
+ if(pressedKey == keys.escape) {
1435
+ module.verbose('Escape key pressed, closing dropdown');
1436
+ module.hide();
1437
+ }
1438
+
1439
+ }
1440
+ else {
1441
+ // delimiter key
1442
+ if(delimiterPressed) {
1443
+ event.preventDefault();
1444
+ }
1445
+ // down arrow (open menu)
1446
+ if(pressedKey == keys.downArrow && !module.is.visible()) {
1447
+ module.verbose('Down key pressed, showing dropdown');
1448
+ module.select.firstUnfiltered();
1449
+ module.show();
1450
+ event.preventDefault();
1451
+ }
1452
+ }
1453
+ }
1454
+ else {
1455
+ if( !module.has.search() ) {
1456
+ module.set.selectedLetter( String.fromCharCode(pressedKey) );
1457
+ }
1458
+ }
1459
+ }
1460
+ },
1461
+
1462
+ trigger: {
1463
+ change: function() {
1464
+ var
1465
+ events = document.createEvent('HTMLEvents'),
1466
+ inputElement = $input[0]
1467
+ ;
1468
+ if(inputElement) {
1469
+ module.verbose('Triggering native change event');
1470
+ events.initEvent('change', true, false);
1471
+ inputElement.dispatchEvent(events);
1472
+ }
1473
+ }
1474
+ },
1475
+
1476
+ determine: {
1477
+ selectAction: function(text, value) {
1478
+ module.verbose('Determining action', settings.action);
1479
+ if( $.isFunction( module.action[settings.action] ) ) {
1480
+ module.verbose('Triggering preset action', settings.action, text, value);
1481
+ module.action[ settings.action ].call(element, text, value, this);
1482
+ }
1483
+ else if( $.isFunction(settings.action) ) {
1484
+ module.verbose('Triggering user action', settings.action, text, value);
1485
+ settings.action.call(element, text, value, this);
1486
+ }
1487
+ else {
1488
+ module.error(error.action, settings.action);
1489
+ }
1490
+ },
1491
+ eventInModule: function(event, callback) {
1492
+ var
1493
+ $target = $(event.target),
1494
+ inDocument = ($target.closest(document.documentElement).length > 0),
1495
+ inModule = ($target.closest($module).length > 0)
1496
+ ;
1497
+ callback = $.isFunction(callback)
1498
+ ? callback
1499
+ : function(){}
1500
+ ;
1501
+ if(inDocument && !inModule) {
1502
+ module.verbose('Triggering event', callback);
1503
+ callback();
1504
+ return true;
1505
+ }
1506
+ else {
1507
+ module.verbose('Event occurred in dropdown, canceling callback');
1508
+ return false;
1509
+ }
1510
+ },
1511
+ eventOnElement: function(event, callback) {
1512
+ var
1513
+ $target = $(event.target),
1514
+ $label = $target.closest(selector.siblingLabel),
1515
+ inVisibleDOM = document.body.contains(event.target),
1516
+ notOnLabel = ($module.find($label).length === 0),
1517
+ notInMenu = ($target.closest($menu).length === 0)
1518
+ ;
1519
+ callback = $.isFunction(callback)
1520
+ ? callback
1521
+ : function(){}
1522
+ ;
1523
+ if(inVisibleDOM && notOnLabel && notInMenu) {
1524
+ module.verbose('Triggering event', callback);
1525
+ callback();
1526
+ return true;
1527
+ }
1528
+ else {
1529
+ module.verbose('Event occurred in dropdown menu, canceling callback');
1530
+ return false;
1531
+ }
1532
+ }
1533
+ },
1534
+
1535
+ action: {
1536
+
1537
+ nothing: function() {},
1538
+
1539
+ activate: function(text, value, element) {
1540
+ value = (value !== undefined)
1541
+ ? value
1542
+ : text
1543
+ ;
1544
+ if( module.can.activate( $(element) ) ) {
1545
+ module.set.selected(value, $(element));
1546
+ if(module.is.multiple() && !module.is.allFiltered()) {
1547
+ return;
1548
+ }
1549
+ else {
1550
+ module.hideAndClear();
1551
+ }
1552
+ }
1553
+ },
1554
+
1555
+ select: function(text, value, element) {
1556
+ value = (value !== undefined)
1557
+ ? value
1558
+ : text
1559
+ ;
1560
+ if( module.can.activate( $(element) ) ) {
1561
+ module.set.value(value, $(element));
1562
+ if(module.is.multiple() && !module.is.allFiltered()) {
1563
+ return;
1564
+ }
1565
+ else {
1566
+ module.hideAndClear();
1567
+ }
1568
+ }
1569
+ },
1570
+
1571
+ combo: function(text, value, element) {
1572
+ value = (value !== undefined)
1573
+ ? value
1574
+ : text
1575
+ ;
1576
+ module.set.selected(value, $(element));
1577
+ module.hideAndClear();
1578
+ },
1579
+
1580
+ hide: function(text, value, element) {
1581
+ module.set.value(value, text);
1582
+ module.hideAndClear();
1583
+ }
1584
+
1585
+ },
1586
+
1587
+ get: {
1588
+ id: function() {
1589
+ return id;
1590
+ },
1591
+ defaultText: function() {
1592
+ return $module.data(metadata.defaultText);
1593
+ },
1594
+ defaultValue: function() {
1595
+ return $module.data(metadata.defaultValue);
1596
+ },
1597
+ placeholderText: function() {
1598
+ return $module.data(metadata.placeholderText) || '';
1599
+ },
1600
+ text: function() {
1601
+ return $text.text();
1602
+ },
1603
+ query: function() {
1604
+ return $.trim($search.val());
1605
+ },
1606
+ searchWidth: function(value) {
1607
+ value = (value !== undefined)
1608
+ ? value
1609
+ : $search.val()
1610
+ ;
1611
+ $sizer.text(value);
1612
+ // prevent rounding issues
1613
+ return Math.ceil( $sizer.width() + 1);
1614
+ },
1615
+ selectionCount: function() {
1616
+ var
1617
+ values = module.get.values(),
1618
+ count
1619
+ ;
1620
+ count = ( module.is.multiple() )
1621
+ ? $.isArray(values)
1622
+ ? values.length
1623
+ : 0
1624
+ : (module.get.value() !== '')
1625
+ ? 1
1626
+ : 0
1627
+ ;
1628
+ return count;
1629
+ },
1630
+ transition: function($subMenu) {
1631
+ return (settings.transition == 'auto')
1632
+ ? module.is.upward($subMenu)
1633
+ ? 'slide up'
1634
+ : 'slide down'
1635
+ : settings.transition
1636
+ ;
1637
+ },
1638
+ userValues: function() {
1639
+ var
1640
+ values = module.get.values()
1641
+ ;
1642
+ if(!values) {
1643
+ return false;
1644
+ }
1645
+ values = $.isArray(values)
1646
+ ? values
1647
+ : [values]
1648
+ ;
1649
+ return $.grep(values, function(value) {
1650
+ return (module.get.item(value) === false);
1651
+ });
1652
+ },
1653
+ uniqueArray: function(array) {
1654
+ return $.grep(array, function (value, index) {
1655
+ return $.inArray(value, array) === index;
1656
+ });
1657
+ },
1658
+ caretPosition: function() {
1659
+ var
1660
+ input = $search.get(0),
1661
+ range,
1662
+ rangeLength
1663
+ ;
1664
+ if('selectionStart' in input) {
1665
+ return input.selectionStart;
1666
+ }
1667
+ else if (document.selection) {
1668
+ input.focus();
1669
+ range = document.selection.createRange();
1670
+ rangeLength = range.text.length;
1671
+ range.moveStart('character', -input.value.length);
1672
+ return range.text.length - rangeLength;
1673
+ }
1674
+ },
1675
+ value: function() {
1676
+ var
1677
+ value = ($input.length > 0)
1678
+ ? $input.val()
1679
+ : $module.data(metadata.value),
1680
+ isEmptyMultiselect = ($.isArray(value) && value.length === 1 && value[0] === '')
1681
+ ;
1682
+ // prevents placeholder element from being selected when multiple
1683
+ return (value === undefined || isEmptyMultiselect)
1684
+ ? ''
1685
+ : value
1686
+ ;
1687
+ },
1688
+ values: function() {
1689
+ var
1690
+ value = module.get.value()
1691
+ ;
1692
+ if(value === '') {
1693
+ return '';
1694
+ }
1695
+ return ( !module.has.selectInput() && module.is.multiple() )
1696
+ ? (typeof value == 'string') // delimited string
1697
+ ? value.split(settings.delimiter)
1698
+ : ''
1699
+ : value
1700
+ ;
1701
+ },
1702
+ remoteValues: function() {
1703
+ var
1704
+ values = module.get.values(),
1705
+ remoteValues = false
1706
+ ;
1707
+ if(values) {
1708
+ if(typeof values == 'string') {
1709
+ values = [values];
1710
+ }
1711
+ $.each(values, function(index, value) {
1712
+ var
1713
+ name = module.read.remoteData(value)
1714
+ ;
1715
+ module.verbose('Restoring value from session data', name, value);
1716
+ if(name) {
1717
+ if(!remoteValues) {
1718
+ remoteValues = {};
1719
+ }
1720
+ remoteValues[value] = name;
1721
+ }
1722
+ });
1723
+ }
1724
+ return remoteValues;
1725
+ },
1726
+ choiceText: function($choice, preserveHTML) {
1727
+ preserveHTML = (preserveHTML !== undefined)
1728
+ ? preserveHTML
1729
+ : settings.preserveHTML
1730
+ ;
1731
+ if($choice) {
1732
+ if($choice.find(selector.menu).length > 0) {
1733
+ module.verbose('Retrieving text of element with sub-menu');
1734
+ $choice = $choice.clone();
1735
+ $choice.find(selector.menu).remove();
1736
+ $choice.find(selector.menuIcon).remove();
1737
+ }
1738
+ return ($choice.data(metadata.text) !== undefined)
1739
+ ? $choice.data(metadata.text)
1740
+ : (preserveHTML)
1741
+ ? $.trim($choice.html())
1742
+ : $.trim($choice.text())
1743
+ ;
1744
+ }
1745
+ },
1746
+ choiceValue: function($choice, choiceText) {
1747
+ choiceText = choiceText || module.get.choiceText($choice);
1748
+ if(!$choice) {
1749
+ return false;
1750
+ }
1751
+ return ($choice.data(metadata.value) !== undefined)
1752
+ ? String( $choice.data(metadata.value) )
1753
+ : (typeof choiceText === 'string')
1754
+ ? $.trim(choiceText.toLowerCase())
1755
+ : String(choiceText)
1756
+ ;
1757
+ },
1758
+ inputEvent: function() {
1759
+ var
1760
+ input = $search[0]
1761
+ ;
1762
+ if(input) {
1763
+ return (input.oninput !== undefined)
1764
+ ? 'input'
1765
+ : (input.onpropertychange !== undefined)
1766
+ ? 'propertychange'
1767
+ : 'keyup'
1768
+ ;
1769
+ }
1770
+ return false;
1771
+ },
1772
+ selectValues: function() {
1773
+ var
1774
+ select = {}
1775
+ ;
1776
+ select.values = [];
1777
+ $module
1778
+ .find('option')
1779
+ .each(function() {
1780
+ var
1781
+ $option = $(this),
1782
+ name = $option.html(),
1783
+ disabled = $option.attr('disabled'),
1784
+ value = ( $option.attr('value') !== undefined )
1785
+ ? $option.attr('value')
1786
+ : name
1787
+ ;
1788
+ if(settings.placeholder === 'auto' && value === '') {
1789
+ select.placeholder = name;
1790
+ }
1791
+ else {
1792
+ select.values.push({
1793
+ name : name,
1794
+ value : value,
1795
+ disabled : disabled
1796
+ });
1797
+ }
1798
+ })
1799
+ ;
1800
+ if(settings.placeholder && settings.placeholder !== 'auto') {
1801
+ module.debug('Setting placeholder value to', settings.placeholder);
1802
+ select.placeholder = settings.placeholder;
1803
+ }
1804
+ if(settings.sortSelect) {
1805
+ select.values.sort(function(a, b) {
1806
+ return (a.name > b.name)
1807
+ ? 1
1808
+ : -1
1809
+ ;
1810
+ });
1811
+ module.debug('Retrieved and sorted values from select', select);
1812
+ }
1813
+ else {
1814
+ module.debug('Retrieved values from select', select);
1815
+ }
1816
+ return select;
1817
+ },
1818
+ activeItem: function() {
1819
+ return $item.filter('.' + className.active);
1820
+ },
1821
+ selectedItem: function() {
1822
+ var
1823
+ $selectedItem = $item.not(selector.unselectable).filter('.' + className.selected)
1824
+ ;
1825
+ return ($selectedItem.length > 0)
1826
+ ? $selectedItem
1827
+ : $item.eq(0)
1828
+ ;
1829
+ },
1830
+ itemWithAdditions: function(value) {
1831
+ var
1832
+ $items = module.get.item(value),
1833
+ $userItems = module.create.userChoice(value),
1834
+ hasUserItems = ($userItems && $userItems.length > 0)
1835
+ ;
1836
+ if(hasUserItems) {
1837
+ $items = ($items.length > 0)
1838
+ ? $items.add($userItems)
1839
+ : $userItems
1840
+ ;
1841
+ }
1842
+ return $items;
1843
+ },
1844
+ item: function(value, strict) {
1845
+ var
1846
+ $selectedItem = false,
1847
+ shouldSearch,
1848
+ isMultiple
1849
+ ;
1850
+ value = (value !== undefined)
1851
+ ? value
1852
+ : ( module.get.values() !== undefined)
1853
+ ? module.get.values()
1854
+ : module.get.text()
1855
+ ;
1856
+ shouldSearch = (isMultiple)
1857
+ ? (value.length > 0)
1858
+ : (value !== undefined && value !== null)
1859
+ ;
1860
+ isMultiple = (module.is.multiple() && $.isArray(value));
1861
+ strict = (value === '' || value === 0)
1862
+ ? true
1863
+ : strict || false
1864
+ ;
1865
+ if(shouldSearch) {
1866
+ $item
1867
+ .each(function() {
1868
+ var
1869
+ $choice = $(this),
1870
+ optionText = module.get.choiceText($choice),
1871
+ optionValue = module.get.choiceValue($choice, optionText)
1872
+ ;
1873
+ // safe early exit
1874
+ if(optionValue === null || optionValue === undefined) {
1875
+ return;
1876
+ }
1877
+ if(isMultiple) {
1878
+ if($.inArray( String(optionValue), value) !== -1 || $.inArray(optionText, value) !== -1) {
1879
+ $selectedItem = ($selectedItem)
1880
+ ? $selectedItem.add($choice)
1881
+ : $choice
1882
+ ;
1883
+ }
1884
+ }
1885
+ else if(strict) {
1886
+ module.verbose('Ambiguous dropdown value using strict type check', $choice, value);
1887
+ if( optionValue === value || optionText === value) {
1888
+ $selectedItem = $choice;
1889
+ return true;
1890
+ }
1891
+ }
1892
+ else {
1893
+ if( String(optionValue) == String(value) || optionText == value) {
1894
+ module.verbose('Found select item by value', optionValue, value);
1895
+ $selectedItem = $choice;
1896
+ return true;
1897
+ }
1898
+ }
1899
+ })
1900
+ ;
1901
+ }
1902
+ return $selectedItem;
1903
+ }
1904
+ },
1905
+
1906
+ check: {
1907
+ maxSelections: function(selectionCount) {
1908
+ if(settings.maxSelections) {
1909
+ selectionCount = (selectionCount !== undefined)
1910
+ ? selectionCount
1911
+ : module.get.selectionCount()
1912
+ ;
1913
+ if(selectionCount >= settings.maxSelections) {
1914
+ module.debug('Maximum selection count reached');
1915
+ if(settings.useLabels) {
1916
+ $item.addClass(className.filtered);
1917
+ module.add.message(message.maxSelections);
1918
+ }
1919
+ return true;
1920
+ }
1921
+ else {
1922
+ module.verbose('No longer at maximum selection count');
1923
+ module.remove.message();
1924
+ module.remove.filteredItem();
1925
+ if(module.is.searchSelection()) {
1926
+ module.filterItems();
1927
+ }
1928
+ return false;
1929
+ }
1930
+ }
1931
+ return true;
1932
+ }
1933
+ },
1934
+
1935
+ restore: {
1936
+ defaults: function() {
1937
+ module.clear();
1938
+ module.restore.defaultText();
1939
+ module.restore.defaultValue();
1940
+ },
1941
+ defaultText: function() {
1942
+ var
1943
+ defaultText = module.get.defaultText(),
1944
+ placeholderText = module.get.placeholderText
1945
+ ;
1946
+ if(defaultText === placeholderText) {
1947
+ module.debug('Restoring default placeholder text', defaultText);
1948
+ module.set.placeholderText(defaultText);
1949
+ }
1950
+ else {
1951
+ module.debug('Restoring default text', defaultText);
1952
+ module.set.text(defaultText);
1953
+ }
1954
+ },
1955
+ placeholderText: function() {
1956
+ module.set.placeholderText();
1957
+ },
1958
+ defaultValue: function() {
1959
+ var
1960
+ defaultValue = module.get.defaultValue()
1961
+ ;
1962
+ if(defaultValue !== undefined) {
1963
+ module.debug('Restoring default value', defaultValue);
1964
+ if(defaultValue !== '') {
1965
+ module.set.value(defaultValue);
1966
+ module.set.selected();
1967
+ }
1968
+ else {
1969
+ module.remove.activeItem();
1970
+ module.remove.selectedItem();
1971
+ }
1972
+ }
1973
+ },
1974
+ labels: function() {
1975
+ if(settings.allowAdditions) {
1976
+ if(!settings.useLabels) {
1977
+ module.error(error.labels);
1978
+ settings.useLabels = true;
1979
+ }
1980
+ module.debug('Restoring selected values');
1981
+ module.create.userLabels();
1982
+ }
1983
+ module.check.maxSelections();
1984
+ },
1985
+ selected: function() {
1986
+ module.restore.values();
1987
+ if(module.is.multiple()) {
1988
+ module.debug('Restoring previously selected values and labels');
1989
+ module.restore.labels();
1990
+ }
1991
+ else {
1992
+ module.debug('Restoring previously selected values');
1993
+ }
1994
+ },
1995
+ values: function() {
1996
+ // prevents callbacks from occurring on initial load
1997
+ module.set.initialLoad();
1998
+ if(settings.apiSettings && settings.saveRemoteData && module.get.remoteValues()) {
1999
+ module.restore.remoteValues();
2000
+ }
2001
+ else {
2002
+ module.set.selected();
2003
+ }
2004
+ module.remove.initialLoad();
2005
+ },
2006
+ remoteValues: function() {
2007
+ var
2008
+ values = module.get.remoteValues()
2009
+ ;
2010
+ module.debug('Recreating selected from session data', values);
2011
+ if(values) {
2012
+ if( module.is.single() ) {
2013
+ $.each(values, function(value, name) {
2014
+ module.set.text(name);
2015
+ });
2016
+ }
2017
+ else {
2018
+ $.each(values, function(value, name) {
2019
+ module.add.label(value, name);
2020
+ });
2021
+ }
2022
+ }
2023
+ }
2024
+ },
2025
+
2026
+ read: {
2027
+ remoteData: function(value) {
2028
+ var
2029
+ name
2030
+ ;
2031
+ if(window.Storage === undefined) {
2032
+ module.error(error.noStorage);
2033
+ return;
2034
+ }
2035
+ name = sessionStorage.getItem(value);
2036
+ return (name !== undefined)
2037
+ ? name
2038
+ : false
2039
+ ;
2040
+ }
2041
+ },
2042
+
2043
+ save: {
2044
+ defaults: function() {
2045
+ module.save.defaultText();
2046
+ module.save.placeholderText();
2047
+ module.save.defaultValue();
2048
+ },
2049
+ defaultValue: function() {
2050
+ var
2051
+ value = module.get.value()
2052
+ ;
2053
+ module.verbose('Saving default value as', value);
2054
+ $module.data(metadata.defaultValue, value);
2055
+ },
2056
+ defaultText: function() {
2057
+ var
2058
+ text = module.get.text()
2059
+ ;
2060
+ module.verbose('Saving default text as', text);
2061
+ $module.data(metadata.defaultText, text);
2062
+ },
2063
+ placeholderText: function() {
2064
+ var
2065
+ text
2066
+ ;
2067
+ if(settings.placeholder !== false && $text.hasClass(className.placeholder)) {
2068
+ text = module.get.text();
2069
+ module.verbose('Saving placeholder text as', text);
2070
+ $module.data(metadata.placeholderText, text);
2071
+ }
2072
+ },
2073
+ remoteData: function(name, value) {
2074
+ if(window.Storage === undefined) {
2075
+ module.error(error.noStorage);
2076
+ return;
2077
+ }
2078
+ module.verbose('Saving remote data to session storage', value, name);
2079
+ sessionStorage.setItem(value, name);
2080
+ }
2081
+ },
2082
+
2083
+ clear: function() {
2084
+ if(module.is.multiple() && settings.useLabels) {
2085
+ module.remove.labels();
2086
+ }
2087
+ else {
2088
+ module.remove.activeItem();
2089
+ module.remove.selectedItem();
2090
+ }
2091
+ module.set.placeholderText();
2092
+ module.clearValue();
2093
+ },
2094
+
2095
+ clearValue: function() {
2096
+ module.set.value('');
2097
+ },
2098
+
2099
+ scrollPage: function(direction, $selectedItem) {
2100
+ var
2101
+ $currentItem = $selectedItem || module.get.selectedItem(),
2102
+ $menu = $currentItem.closest(selector.menu),
2103
+ menuHeight = $menu.outerHeight(),
2104
+ currentScroll = $menu.scrollTop(),
2105
+ itemHeight = $item.eq(0).outerHeight(),
2106
+ itemsPerPage = Math.floor(menuHeight / itemHeight),
2107
+ maxScroll = $menu.prop('scrollHeight'),
2108
+ newScroll = (direction == 'up')
2109
+ ? currentScroll - (itemHeight * itemsPerPage)
2110
+ : currentScroll + (itemHeight * itemsPerPage),
2111
+ $selectableItem = $item.not(selector.unselectable),
2112
+ isWithinRange,
2113
+ $nextSelectedItem,
2114
+ elementIndex
2115
+ ;
2116
+ elementIndex = (direction == 'up')
2117
+ ? $selectableItem.index($currentItem) - itemsPerPage
2118
+ : $selectableItem.index($currentItem) + itemsPerPage
2119
+ ;
2120
+ isWithinRange = (direction == 'up')
2121
+ ? (elementIndex >= 0)
2122
+ : (elementIndex < $selectableItem.length)
2123
+ ;
2124
+ $nextSelectedItem = (isWithinRange)
2125
+ ? $selectableItem.eq(elementIndex)
2126
+ : (direction == 'up')
2127
+ ? $selectableItem.first()
2128
+ : $selectableItem.last()
2129
+ ;
2130
+ if($nextSelectedItem.length > 0) {
2131
+ module.debug('Scrolling page', direction, $nextSelectedItem);
2132
+ $currentItem
2133
+ .removeClass(className.selected)
2134
+ ;
2135
+ $nextSelectedItem
2136
+ .addClass(className.selected)
2137
+ ;
2138
+ if(settings.selectOnKeydown && module.is.single()) {
2139
+ module.set.selectedItem($nextSelectedItem);
2140
+ }
2141
+ $menu
2142
+ .scrollTop(newScroll)
2143
+ ;
2144
+ }
2145
+ },
2146
+
2147
+ set: {
2148
+ filtered: function() {
2149
+ var
2150
+ isMultiple = module.is.multiple(),
2151
+ isSearch = module.is.searchSelection(),
2152
+ isSearchMultiple = (isMultiple && isSearch),
2153
+ searchValue = (isSearch)
2154
+ ? module.get.query()
2155
+ : '',
2156
+ hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0),
2157
+ searchWidth = module.get.searchWidth(),
2158
+ valueIsSet = searchValue !== ''
2159
+ ;
2160
+ if(isMultiple && hasSearchValue) {
2161
+ module.verbose('Adjusting input width', searchWidth, settings.glyphWidth);
2162
+ $search.css('width', searchWidth);
2163
+ }
2164
+ if(hasSearchValue || (isSearchMultiple && valueIsSet)) {
2165
+ module.verbose('Hiding placeholder text');
2166
+ $text.addClass(className.filtered);
2167
+ }
2168
+ else if(!isMultiple || (isSearchMultiple && !valueIsSet)) {
2169
+ module.verbose('Showing placeholder text');
2170
+ $text.removeClass(className.filtered);
2171
+ }
2172
+ },
2173
+ empty: function() {
2174
+ $module.addClass(className.empty);
2175
+ },
2176
+ loading: function() {
2177
+ $module.addClass(className.loading);
2178
+ },
2179
+ placeholderText: function(text) {
2180
+ text = text || module.get.placeholderText();
2181
+ module.debug('Setting placeholder text', text);
2182
+ module.set.text(text);
2183
+ $text.addClass(className.placeholder);
2184
+ },
2185
+ tabbable: function() {
2186
+ if( module.has.search() ) {
2187
+ module.debug('Added tabindex to searchable dropdown');
2188
+ $search
2189
+ .val('')
2190
+ .attr('tabindex', 0)
2191
+ ;
2192
+ $menu
2193
+ .attr('tabindex', -1)
2194
+ ;
2195
+ }
2196
+ else {
2197
+ module.debug('Added tabindex to dropdown');
2198
+ if( $module.attr('tabindex') === undefined) {
2199
+ $module
2200
+ .attr('tabindex', 0)
2201
+ ;
2202
+ $menu
2203
+ .attr('tabindex', -1)
2204
+ ;
2205
+ }
2206
+ }
2207
+ },
2208
+ initialLoad: function() {
2209
+ module.verbose('Setting initial load');
2210
+ initialLoad = true;
2211
+ },
2212
+ activeItem: function($item) {
2213
+ if( settings.allowAdditions && $item.filter(selector.addition).length > 0 ) {
2214
+ $item.addClass(className.filtered);
2215
+ }
2216
+ else {
2217
+ $item.addClass(className.active);
2218
+ }
2219
+ },
2220
+ partialSearch: function(text) {
2221
+ var
2222
+ length = module.get.query().length
2223
+ ;
2224
+ $search.val( text.substr(0 , length));
2225
+ },
2226
+ scrollPosition: function($item, forceScroll) {
2227
+ var
2228
+ edgeTolerance = 5,
2229
+ $menu,
2230
+ hasActive,
2231
+ offset,
2232
+ itemHeight,
2233
+ itemOffset,
2234
+ menuOffset,
2235
+ menuScroll,
2236
+ menuHeight,
2237
+ abovePage,
2238
+ belowPage
2239
+ ;
2240
+
2241
+ $item = $item || module.get.selectedItem();
2242
+ $menu = $item.closest(selector.menu);
2243
+ hasActive = ($item && $item.length > 0);
2244
+ forceScroll = (forceScroll !== undefined)
2245
+ ? forceScroll
2246
+ : false
2247
+ ;
2248
+ if($item && $menu.length > 0 && hasActive) {
2249
+ itemOffset = $item.position().top;
2250
+
2251
+ $menu.addClass(className.loading);
2252
+ menuScroll = $menu.scrollTop();
2253
+ menuOffset = $menu.offset().top;
2254
+ itemOffset = $item.offset().top;
2255
+ offset = menuScroll - menuOffset + itemOffset;
2256
+ if(!forceScroll) {
2257
+ menuHeight = $menu.height();
2258
+ belowPage = menuScroll + menuHeight < (offset + edgeTolerance);
2259
+ abovePage = ((offset - edgeTolerance) < menuScroll);
2260
+ }
2261
+ module.debug('Scrolling to active item', offset);
2262
+ if(forceScroll || abovePage || belowPage) {
2263
+ $menu.scrollTop(offset);
2264
+ }
2265
+ $menu.removeClass(className.loading);
2266
+ }
2267
+ },
2268
+ text: function(text) {
2269
+ if(settings.action !== 'select') {
2270
+ if(settings.action == 'combo') {
2271
+ module.debug('Changing combo button text', text, $combo);
2272
+ if(settings.preserveHTML) {
2273
+ $combo.html(text);
2274
+ }
2275
+ else {
2276
+ $combo.text(text);
2277
+ }
2278
+ }
2279
+ else {
2280
+ if(text !== module.get.placeholderText()) {
2281
+ $text.removeClass(className.placeholder);
2282
+ }
2283
+ module.debug('Changing text', text, $text);
2284
+ $text
2285
+ .removeClass(className.filtered)
2286
+ ;
2287
+ if(settings.preserveHTML) {
2288
+ $text.html(text);
2289
+ }
2290
+ else {
2291
+ $text.text(text);
2292
+ }
2293
+ }
2294
+ }
2295
+ },
2296
+ selectedItem: function($item) {
2297
+ var
2298
+ value = module.get.choiceValue($item),
2299
+ text = module.get.choiceText($item, false)
2300
+ ;
2301
+ module.debug('Setting user selection to item', $item);
2302
+ module.remove.activeItem();
2303
+ module.set.partialSearch(text);
2304
+ module.set.activeItem($item);
2305
+ module.set.selected(value, $item);
2306
+ module.set.text(text);
2307
+ },
2308
+ selectedLetter: function(letter) {
2309
+ var
2310
+ $selectedItem = $item.filter('.' + className.selected),
2311
+ alreadySelectedLetter = $selectedItem.length > 0 && module.has.firstLetter($selectedItem, letter),
2312
+ $nextValue = false,
2313
+ $nextItem
2314
+ ;
2315
+ // check next of same letter
2316
+ if(alreadySelectedLetter) {
2317
+ $nextItem = $selectedItem.nextAll($item).eq(0);
2318
+ if( module.has.firstLetter($nextItem, letter) ) {
2319
+ $nextValue = $nextItem;
2320
+ }
2321
+ }
2322
+ // check all values
2323
+ if(!$nextValue) {
2324
+ $item
2325
+ .each(function(){
2326
+ if(module.has.firstLetter($(this), letter)) {
2327
+ $nextValue = $(this);
2328
+ return false;
2329
+ }
2330
+ })
2331
+ ;
2332
+ }
2333
+ // set next value
2334
+ if($nextValue) {
2335
+ module.verbose('Scrolling to next value with letter', letter);
2336
+ module.set.scrollPosition($nextValue);
2337
+ $selectedItem.removeClass(className.selected);
2338
+ $nextValue.addClass(className.selected);
2339
+ if(settings.selectOnKeydown && module.is.single()) {
2340
+ module.set.selectedItem($nextValue);
2341
+ }
2342
+ }
2343
+ },
2344
+ direction: function($menu) {
2345
+ if(settings.direction == 'auto') {
2346
+ if(module.is.onScreen($menu)) {
2347
+ module.remove.upward($menu);
2348
+ }
2349
+ else {
2350
+ module.set.upward($menu);
2351
+ }
2352
+ }
2353
+ else if(settings.direction == 'upward') {
2354
+ module.set.upward($menu);
2355
+ }
2356
+ },
2357
+ upward: function($menu) {
2358
+ var $element = $menu || $module;
2359
+ $element.addClass(className.upward);
2360
+ },
2361
+ value: function(value, text, $selected) {
2362
+ var
2363
+ escapedValue = module.escape.value(value),
2364
+ hasInput = ($input.length > 0),
2365
+ isAddition = !module.has.value(value),
2366
+ currentValue = module.get.values(),
2367
+ stringValue = (value !== undefined)
2368
+ ? String(value)
2369
+ : value,
2370
+ newValue
2371
+ ;
2372
+ if(hasInput) {
2373
+ if(!settings.allowReselection && stringValue == currentValue) {
2374
+ module.verbose('Skipping value update already same value', value, currentValue);
2375
+ if(!module.is.initialLoad()) {
2376
+ return;
2377
+ }
2378
+ }
2379
+
2380
+ if( module.is.single() && module.has.selectInput() && module.can.extendSelect() ) {
2381
+ module.debug('Adding user option', value);
2382
+ module.add.optionValue(value);
2383
+ }
2384
+ module.debug('Updating input value', escapedValue, currentValue);
2385
+ internalChange = true;
2386
+ $input
2387
+ .val(escapedValue)
2388
+ ;
2389
+ if(settings.fireOnInit === false && module.is.initialLoad()) {
2390
+ module.debug('Input native change event ignored on initial load');
2391
+ }
2392
+ else {
2393
+ module.trigger.change();
2394
+ }
2395
+ internalChange = false;
2396
+ }
2397
+ else {
2398
+ module.verbose('Storing value in metadata', escapedValue, $input);
2399
+ if(escapedValue !== currentValue) {
2400
+ $module.data(metadata.value, stringValue);
2401
+ }
2402
+ }
2403
+ if(settings.fireOnInit === false && module.is.initialLoad()) {
2404
+ module.verbose('No callback on initial load', settings.onChange);
2405
+ }
2406
+ else {
2407
+ settings.onChange.call(element, value, text, $selected);
2408
+ }
2409
+ },
2410
+ active: function() {
2411
+ $module
2412
+ .addClass(className.active)
2413
+ ;
2414
+ },
2415
+ multiple: function() {
2416
+ $module.addClass(className.multiple);
2417
+ },
2418
+ visible: function() {
2419
+ $module.addClass(className.visible);
2420
+ },
2421
+ exactly: function(value, $selectedItem) {
2422
+ module.debug('Setting selected to exact values');
2423
+ module.clear();
2424
+ module.set.selected(value, $selectedItem);
2425
+ },
2426
+ selected: function(value, $selectedItem) {
2427
+ var
2428
+ isMultiple = module.is.multiple(),
2429
+ $userSelectedItem
2430
+ ;
2431
+ $selectedItem = (settings.allowAdditions)
2432
+ ? $selectedItem || module.get.itemWithAdditions(value)
2433
+ : $selectedItem || module.get.item(value)
2434
+ ;
2435
+ if(!$selectedItem) {
2436
+ return;
2437
+ }
2438
+ module.debug('Setting selected menu item to', $selectedItem);
2439
+ if(module.is.multiple()) {
2440
+ module.remove.searchWidth();
2441
+ }
2442
+ if(module.is.single()) {
2443
+ module.remove.activeItem();
2444
+ module.remove.selectedItem();
2445
+ }
2446
+ else if(settings.useLabels) {
2447
+ module.remove.selectedItem();
2448
+ }
2449
+ // select each item
2450
+ $selectedItem
2451
+ .each(function() {
2452
+ var
2453
+ $selected = $(this),
2454
+ selectedText = module.get.choiceText($selected),
2455
+ selectedValue = module.get.choiceValue($selected, selectedText),
2456
+
2457
+ isFiltered = $selected.hasClass(className.filtered),
2458
+ isActive = $selected.hasClass(className.active),
2459
+ isUserValue = $selected.hasClass(className.addition),
2460
+ shouldAnimate = (isMultiple && $selectedItem.length == 1)
2461
+ ;
2462
+ if(isMultiple) {
2463
+ if(!isActive || isUserValue) {
2464
+ if(settings.apiSettings && settings.saveRemoteData) {
2465
+ module.save.remoteData(selectedText, selectedValue);
2466
+ }
2467
+ if(settings.useLabels) {
2468
+ module.add.value(selectedValue, selectedText, $selected);
2469
+ module.add.label(selectedValue, selectedText, shouldAnimate);
2470
+ module.set.activeItem($selected);
2471
+ module.filterActive();
2472
+ module.select.nextAvailable($selectedItem);
2473
+ }
2474
+ else {
2475
+ module.add.value(selectedValue, selectedText, $selected);
2476
+ module.set.text(module.add.variables(message.count));
2477
+ module.set.activeItem($selected);
2478
+ }
2479
+ }
2480
+ else if(!isFiltered) {
2481
+ module.debug('Selected active value, removing label');
2482
+ module.remove.selected(selectedValue);
2483
+ }
2484
+ }
2485
+ else {
2486
+ if(settings.apiSettings && settings.saveRemoteData) {
2487
+ module.save.remoteData(selectedText, selectedValue);
2488
+ }
2489
+ module.set.text(selectedText);
2490
+ module.set.value(selectedValue, selectedText, $selected);
2491
+ $selected
2492
+ .addClass(className.active)
2493
+ .addClass(className.selected)
2494
+ ;
2495
+ }
2496
+ })
2497
+ ;
2498
+ }
2499
+ },
2500
+
2501
+ add: {
2502
+ label: function(value, text, shouldAnimate) {
2503
+ var
2504
+ $next = module.is.searchSelection()
2505
+ ? $search
2506
+ : $text,
2507
+ escapedValue = module.escape.value(value),
2508
+ $label
2509
+ ;
2510
+ $label = $('<a />')
2511
+ .addClass(className.label)
2512
+ .attr('data-value', escapedValue)
2513
+ .html(templates.label(escapedValue, text))
2514
+ ;
2515
+ $label = settings.onLabelCreate.call($label, escapedValue, text);
2516
+
2517
+ if(module.has.label(value)) {
2518
+ module.debug('Label already exists, skipping', escapedValue);
2519
+ return;
2520
+ }
2521
+ if(settings.label.variation) {
2522
+ $label.addClass(settings.label.variation);
2523
+ }
2524
+ if(shouldAnimate === true) {
2525
+ module.debug('Animating in label', $label);
2526
+ $label
2527
+ .addClass(className.hidden)
2528
+ .insertBefore($next)
2529
+ .transition(settings.label.transition, settings.label.duration)
2530
+ ;
2531
+ }
2532
+ else {
2533
+ module.debug('Adding selection label', $label);
2534
+ $label
2535
+ .insertBefore($next)
2536
+ ;
2537
+ }
2538
+ },
2539
+ message: function(message) {
2540
+ var
2541
+ $message = $menu.children(selector.message),
2542
+ html = settings.templates.message(module.add.variables(message))
2543
+ ;
2544
+ if($message.length > 0) {
2545
+ $message
2546
+ .html(html)
2547
+ ;
2548
+ }
2549
+ else {
2550
+ $message = $('<div/>')
2551
+ .html(html)
2552
+ .addClass(className.message)
2553
+ .appendTo($menu)
2554
+ ;
2555
+ }
2556
+ },
2557
+ optionValue: function(value) {
2558
+ var
2559
+ escapedValue = module.escape.value(value),
2560
+ $option = $input.find('option[value="' + escapedValue + '"]'),
2561
+ hasOption = ($option.length > 0)
2562
+ ;
2563
+ if(hasOption) {
2564
+ return;
2565
+ }
2566
+ // temporarily disconnect observer
2567
+ module.disconnect.selectObserver();
2568
+ if( module.is.single() ) {
2569
+ module.verbose('Removing previous user addition');
2570
+ $input.find('option.' + className.addition).remove();
2571
+ }
2572
+ $('<option/>')
2573
+ .prop('value', escapedValue)
2574
+ .addClass(className.addition)
2575
+ .html(value)
2576
+ .appendTo($input)
2577
+ ;
2578
+ module.verbose('Adding user addition as an <option>', value);
2579
+ module.observe.select();
2580
+ },
2581
+ userSuggestion: function(value) {
2582
+ var
2583
+ $addition = $menu.children(selector.addition),
2584
+ $existingItem = module.get.item(value),
2585
+ alreadyHasValue = $existingItem && $existingItem.not(selector.addition).length,
2586
+ hasUserSuggestion = $addition.length > 0,
2587
+ html
2588
+ ;
2589
+ if(settings.useLabels && module.has.maxSelections()) {
2590
+ return;
2591
+ }
2592
+ if(value === '' || alreadyHasValue) {
2593
+ $addition.remove();
2594
+ return;
2595
+ }
2596
+ if(hasUserSuggestion) {
2597
+ $addition
2598
+ .data(metadata.value, value)
2599
+ .data(metadata.text, value)
2600
+ .attr('data-' + metadata.value, value)
2601
+ .attr('data-' + metadata.text, value)
2602
+ .removeClass(className.filtered)
2603
+ ;
2604
+ if(!settings.hideAdditions) {
2605
+ html = settings.templates.addition( module.add.variables(message.addResult, value) );
2606
+ $addition
2607
+ .html(html)
2608
+ ;
2609
+ }
2610
+ module.verbose('Replacing user suggestion with new value', $addition);
2611
+ }
2612
+ else {
2613
+ $addition = module.create.userChoice(value);
2614
+ $addition
2615
+ .prependTo($menu)
2616
+ ;
2617
+ module.verbose('Adding item choice to menu corresponding with user choice addition', $addition);
2618
+ }
2619
+ if(!settings.hideAdditions || module.is.allFiltered()) {
2620
+ $addition
2621
+ .addClass(className.selected)
2622
+ .siblings()
2623
+ .removeClass(className.selected)
2624
+ ;
2625
+ }
2626
+ module.refreshItems();
2627
+ },
2628
+ variables: function(message, term) {
2629
+ var
2630
+ hasCount = (message.search('{count}') !== -1),
2631
+ hasMaxCount = (message.search('{maxCount}') !== -1),
2632
+ hasTerm = (message.search('{term}') !== -1),
2633
+ values,
2634
+ count,
2635
+ query
2636
+ ;
2637
+ module.verbose('Adding templated variables to message', message);
2638
+ if(hasCount) {
2639
+ count = module.get.selectionCount();
2640
+ message = message.replace('{count}', count);
2641
+ }
2642
+ if(hasMaxCount) {
2643
+ count = module.get.selectionCount();
2644
+ message = message.replace('{maxCount}', settings.maxSelections);
2645
+ }
2646
+ if(hasTerm) {
2647
+ query = term || module.get.query();
2648
+ message = message.replace('{term}', query);
2649
+ }
2650
+ return message;
2651
+ },
2652
+ value: function(addedValue, addedText, $selectedItem) {
2653
+ var
2654
+ currentValue = module.get.values(),
2655
+ newValue
2656
+ ;
2657
+ if(addedValue === '') {
2658
+ module.debug('Cannot select blank values from multiselect');
2659
+ return;
2660
+ }
2661
+ // extend current array
2662
+ if($.isArray(currentValue)) {
2663
+ newValue = currentValue.concat([addedValue]);
2664
+ newValue = module.get.uniqueArray(newValue);
2665
+ }
2666
+ else {
2667
+ newValue = [addedValue];
2668
+ }
2669
+ // add values
2670
+ if( module.has.selectInput() ) {
2671
+ if(module.can.extendSelect()) {
2672
+ module.debug('Adding value to select', addedValue, newValue, $input);
2673
+ module.add.optionValue(addedValue);
2674
+ }
2675
+ }
2676
+ else {
2677
+ newValue = newValue.join(settings.delimiter);
2678
+ module.debug('Setting hidden input to delimited value', newValue, $input);
2679
+ }
2680
+
2681
+ if(settings.fireOnInit === false && module.is.initialLoad()) {
2682
+ module.verbose('Skipping onadd callback on initial load', settings.onAdd);
2683
+ }
2684
+ else {
2685
+ settings.onAdd.call(element, addedValue, addedText, $selectedItem);
2686
+ }
2687
+ module.set.value(newValue, addedValue, addedText, $selectedItem);
2688
+ module.check.maxSelections();
2689
+ }
2690
+ },
2691
+
2692
+ remove: {
2693
+ active: function() {
2694
+ $module.removeClass(className.active);
2695
+ },
2696
+ activeLabel: function() {
2697
+ $module.find(selector.label).removeClass(className.active);
2698
+ },
2699
+ empty: function() {
2700
+ $module.removeClass(className.empty);
2701
+ },
2702
+ loading: function() {
2703
+ $module.removeClass(className.loading);
2704
+ },
2705
+ initialLoad: function() {
2706
+ initialLoad = false;
2707
+ },
2708
+ upward: function($menu) {
2709
+ var $element = $menu || $module;
2710
+ $element.removeClass(className.upward);
2711
+ },
2712
+ visible: function() {
2713
+ $module.removeClass(className.visible);
2714
+ },
2715
+ activeItem: function() {
2716
+ $item.removeClass(className.active);
2717
+ },
2718
+ filteredItem: function() {
2719
+ if(settings.useLabels && module.has.maxSelections() ) {
2720
+ return;
2721
+ }
2722
+ if(settings.useLabels && module.is.multiple()) {
2723
+ $item.not('.' + className.active).removeClass(className.filtered);
2724
+ }
2725
+ else {
2726
+ $item.removeClass(className.filtered);
2727
+ }
2728
+ module.remove.empty();
2729
+ },
2730
+ optionValue: function(value) {
2731
+ var
2732
+ escapedValue = module.escape.value(value),
2733
+ $option = $input.find('option[value="' + escapedValue + '"]'),
2734
+ hasOption = ($option.length > 0)
2735
+ ;
2736
+ if(!hasOption || !$option.hasClass(className.addition)) {
2737
+ return;
2738
+ }
2739
+ // temporarily disconnect observer
2740
+ if(selectObserver) {
2741
+ selectObserver.disconnect();
2742
+ module.verbose('Temporarily disconnecting mutation observer');
2743
+ }
2744
+ $option.remove();
2745
+ module.verbose('Removing user addition as an <option>', escapedValue);
2746
+ if(selectObserver) {
2747
+ selectObserver.observe($input[0], {
2748
+ childList : true,
2749
+ subtree : true
2750
+ });
2751
+ }
2752
+ },
2753
+ message: function() {
2754
+ $menu.children(selector.message).remove();
2755
+ },
2756
+ searchWidth: function() {
2757
+ $search.css('width', '');
2758
+ },
2759
+ searchTerm: function() {
2760
+ module.verbose('Cleared search term');
2761
+ $search.val('');
2762
+ module.set.filtered();
2763
+ },
2764
+ userAddition: function() {
2765
+ $item.filter(selector.addition).remove();
2766
+ },
2767
+ selected: function(value, $selectedItem) {
2768
+ $selectedItem = (settings.allowAdditions)
2769
+ ? $selectedItem || module.get.itemWithAdditions(value)
2770
+ : $selectedItem || module.get.item(value)
2771
+ ;
2772
+
2773
+ if(!$selectedItem) {
2774
+ return false;
2775
+ }
2776
+
2777
+ $selectedItem
2778
+ .each(function() {
2779
+ var
2780
+ $selected = $(this),
2781
+ selectedText = module.get.choiceText($selected),
2782
+ selectedValue = module.get.choiceValue($selected, selectedText)
2783
+ ;
2784
+ if(module.is.multiple()) {
2785
+ if(settings.useLabels) {
2786
+ module.remove.value(selectedValue, selectedText, $selected);
2787
+ module.remove.label(selectedValue);
2788
+ }
2789
+ else {
2790
+ module.remove.value(selectedValue, selectedText, $selected);
2791
+ if(module.get.selectionCount() === 0) {
2792
+ module.set.placeholderText();
2793
+ }
2794
+ else {
2795
+ module.set.text(module.add.variables(message.count));
2796
+ }
2797
+ }
2798
+ }
2799
+ else {
2800
+ module.remove.value(selectedValue, selectedText, $selected);
2801
+ }
2802
+ $selected
2803
+ .removeClass(className.filtered)
2804
+ .removeClass(className.active)
2805
+ ;
2806
+ if(settings.useLabels) {
2807
+ $selected.removeClass(className.selected);
2808
+ }
2809
+ })
2810
+ ;
2811
+ },
2812
+ selectedItem: function() {
2813
+ $item.removeClass(className.selected);
2814
+ },
2815
+ value: function(removedValue, removedText, $removedItem) {
2816
+ var
2817
+ values = module.get.values(),
2818
+ newValue
2819
+ ;
2820
+ if( module.has.selectInput() ) {
2821
+ module.verbose('Input is <select> removing selected option', removedValue);
2822
+ newValue = module.remove.arrayValue(removedValue, values);
2823
+ module.remove.optionValue(removedValue);
2824
+ }
2825
+ else {
2826
+ module.verbose('Removing from delimited values', removedValue);
2827
+ newValue = module.remove.arrayValue(removedValue, values);
2828
+ newValue = newValue.join(settings.delimiter);
2829
+ }
2830
+ if(settings.fireOnInit === false && module.is.initialLoad()) {
2831
+ module.verbose('No callback on initial load', settings.onRemove);
2832
+ }
2833
+ else {
2834
+ settings.onRemove.call(element, removedValue, removedText, $removedItem);
2835
+ }
2836
+ module.set.value(newValue, removedText, $removedItem);
2837
+ module.check.maxSelections();
2838
+ },
2839
+ arrayValue: function(removedValue, values) {
2840
+ if( !$.isArray(values) ) {
2841
+ values = [values];
2842
+ }
2843
+ values = $.grep(values, function(value){
2844
+ return (removedValue != value);
2845
+ });
2846
+ module.verbose('Removed value from delimited string', removedValue, values);
2847
+ return values;
2848
+ },
2849
+ label: function(value, shouldAnimate) {
2850
+ var
2851
+ $labels = $module.find(selector.label),
2852
+ $removedLabel = $labels.filter('[data-value="' + value +'"]')
2853
+ ;
2854
+ module.verbose('Removing label', $removedLabel);
2855
+ $removedLabel.remove();
2856
+ },
2857
+ activeLabels: function($activeLabels) {
2858
+ $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active);
2859
+ module.verbose('Removing active label selections', $activeLabels);
2860
+ module.remove.labels($activeLabels);
2861
+ },
2862
+ labels: function($labels) {
2863
+ $labels = $labels || $module.find(selector.label);
2864
+ module.verbose('Removing labels', $labels);
2865
+ $labels
2866
+ .each(function(){
2867
+ var
2868
+ $label = $(this),
2869
+ value = $label.data(metadata.value),
2870
+ stringValue = (value !== undefined)
2871
+ ? String(value)
2872
+ : value,
2873
+ isUserValue = module.is.userValue(stringValue)
2874
+ ;
2875
+ if(settings.onLabelRemove.call($label, value) === false) {
2876
+ module.debug('Label remove callback cancelled removal');
2877
+ return;
2878
+ }
2879
+ module.remove.message();
2880
+ if(isUserValue) {
2881
+ module.remove.value(stringValue);
2882
+ module.remove.label(stringValue);
2883
+ }
2884
+ else {
2885
+ // selected will also remove label
2886
+ module.remove.selected(stringValue);
2887
+ }
2888
+ })
2889
+ ;
2890
+ },
2891
+ tabbable: function() {
2892
+ if( module.has.search() ) {
2893
+ module.debug('Searchable dropdown initialized');
2894
+ $search
2895
+ .removeAttr('tabindex')
2896
+ ;
2897
+ $menu
2898
+ .removeAttr('tabindex')
2899
+ ;
2900
+ }
2901
+ else {
2902
+ module.debug('Simple selection dropdown initialized');
2903
+ $module
2904
+ .removeAttr('tabindex')
2905
+ ;
2906
+ $menu
2907
+ .removeAttr('tabindex')
2908
+ ;
2909
+ }
2910
+ }
2911
+ },
2912
+
2913
+ has: {
2914
+ menuSearch: function() {
2915
+ return (module.has.search() && $search.closest($menu).length > 0);
2916
+ },
2917
+ search: function() {
2918
+ return ($search.length > 0);
2919
+ },
2920
+ sizer: function() {
2921
+ return ($sizer.length > 0);
2922
+ },
2923
+ selectInput: function() {
2924
+ return ( $input.is('select') );
2925
+ },
2926
+ minCharacters: function(searchTerm) {
2927
+ if(settings.minCharacters) {
2928
+ searchTerm = (searchTerm !== undefined)
2929
+ ? String(searchTerm)
2930
+ : String(module.get.query())
2931
+ ;
2932
+ return (searchTerm.length >= settings.minCharacters);
2933
+ }
2934
+ return true;
2935
+ },
2936
+ firstLetter: function($item, letter) {
2937
+ var
2938
+ text,
2939
+ firstLetter
2940
+ ;
2941
+ if(!$item || $item.length === 0 || typeof letter !== 'string') {
2942
+ return false;
2943
+ }
2944
+ text = module.get.choiceText($item, false);
2945
+ letter = letter.toLowerCase();
2946
+ firstLetter = String(text).charAt(0).toLowerCase();
2947
+ return (letter == firstLetter);
2948
+ },
2949
+ input: function() {
2950
+ return ($input.length > 0);
2951
+ },
2952
+ items: function() {
2953
+ return ($item.length > 0);
2954
+ },
2955
+ menu: function() {
2956
+ return ($menu.length > 0);
2957
+ },
2958
+ message: function() {
2959
+ return ($menu.children(selector.message).length !== 0);
2960
+ },
2961
+ label: function(value) {
2962
+ var
2963
+ escapedValue = module.escape.value(value),
2964
+ $labels = $module.find(selector.label)
2965
+ ;
2966
+ return ($labels.filter('[data-value="' + escapedValue +'"]').length > 0);
2967
+ },
2968
+ maxSelections: function() {
2969
+ return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections);
2970
+ },
2971
+ allResultsFiltered: function() {
2972
+ var
2973
+ $normalResults = $item.not(selector.addition)
2974
+ ;
2975
+ return ($normalResults.filter(selector.unselectable).length === $normalResults.length);
2976
+ },
2977
+ userSuggestion: function() {
2978
+ return ($menu.children(selector.addition).length > 0);
2979
+ },
2980
+ query: function() {
2981
+ return (module.get.query() !== '');
2982
+ },
2983
+ value: function(value) {
2984
+ var
2985
+ values = module.get.values(),
2986
+ hasValue = $.isArray(values)
2987
+ ? values && ($.inArray(value, values) !== -1)
2988
+ : (values == value)
2989
+ ;
2990
+ return (hasValue)
2991
+ ? true
2992
+ : false
2993
+ ;
2994
+ }
2995
+ },
2996
+
2997
+ is: {
2998
+ active: function() {
2999
+ return $module.hasClass(className.active);
3000
+ },
3001
+ bubbledLabelClick: function(event) {
3002
+ return $(event.target).is('select, input') && $module.closest('label').length > 0;
3003
+ },
3004
+ bubbledIconClick: function(event) {
3005
+ return $(event.target).closest($icon).length > 0;
3006
+ },
3007
+ alreadySetup: function() {
3008
+ return ($module.is('select') && $module.parent(selector.dropdown).length > 0 && $module.prev().length === 0);
3009
+ },
3010
+ animating: function($subMenu) {
3011
+ return ($subMenu)
3012
+ ? $subMenu.transition && $subMenu.transition('is animating')
3013
+ : $menu.transition && $menu.transition('is animating')
3014
+ ;
3015
+ },
3016
+ disabled: function() {
3017
+ return $module.hasClass(className.disabled);
3018
+ },
3019
+ focused: function() {
3020
+ return (document.activeElement === $module[0]);
3021
+ },
3022
+ focusedOnSearch: function() {
3023
+ return (document.activeElement === $search[0]);
3024
+ },
3025
+ allFiltered: function() {
3026
+ return( (module.is.multiple() || module.has.search()) && !(settings.hideAdditions == false && module.has.userSuggestion()) && !module.has.message() && module.has.allResultsFiltered() );
3027
+ },
3028
+ hidden: function($subMenu) {
3029
+ return !module.is.visible($subMenu);
3030
+ },
3031
+ initialLoad: function() {
3032
+ return initialLoad;
3033
+ },
3034
+ onScreen: function($subMenu) {
3035
+ var
3036
+ $currentMenu = $subMenu || $menu,
3037
+ canOpenDownward = true,
3038
+ onScreen = {},
3039
+ calculations
3040
+ ;
3041
+ $currentMenu.addClass(className.loading);
3042
+ calculations = {
3043
+ context: {
3044
+ scrollTop : $context.scrollTop(),
3045
+ height : $context.outerHeight()
3046
+ },
3047
+ menu : {
3048
+ offset: $currentMenu.offset(),
3049
+ height: $currentMenu.outerHeight()
3050
+ }
3051
+ };
3052
+ onScreen = {
3053
+ above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.menu.height,
3054
+ below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top + calculations.menu.height
3055
+ };
3056
+ if(onScreen.below) {
3057
+ module.verbose('Dropdown can fit in context downward', onScreen);
3058
+ canOpenDownward = true;
3059
+ }
3060
+ else if(!onScreen.below && !onScreen.above) {
3061
+ module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen);
3062
+ canOpenDownward = true;
3063
+ }
3064
+ else {
3065
+ module.verbose('Dropdown cannot fit below, opening upward', onScreen);
3066
+ canOpenDownward = false;
3067
+ }
3068
+ $currentMenu.removeClass(className.loading);
3069
+ return canOpenDownward;
3070
+ },
3071
+ inObject: function(needle, object) {
3072
+ var
3073
+ found = false
3074
+ ;
3075
+ $.each(object, function(index, property) {
3076
+ if(property == needle) {
3077
+ found = true;
3078
+ return true;
3079
+ }
3080
+ });
3081
+ return found;
3082
+ },
3083
+ multiple: function() {
3084
+ return $module.hasClass(className.multiple);
3085
+ },
3086
+ single: function() {
3087
+ return !module.is.multiple();
3088
+ },
3089
+ selectMutation: function(mutations) {
3090
+ var
3091
+ selectChanged = false
3092
+ ;
3093
+ $.each(mutations, function(index, mutation) {
3094
+ if(mutation.target && $(mutation.target).is('select')) {
3095
+ selectChanged = true;
3096
+ return true;
3097
+ }
3098
+ });
3099
+ return selectChanged;
3100
+ },
3101
+ search: function() {
3102
+ return $module.hasClass(className.search);
3103
+ },
3104
+ searchSelection: function() {
3105
+ return ( module.has.search() && $search.parent(selector.dropdown).length === 1 );
3106
+ },
3107
+ selection: function() {
3108
+ return $module.hasClass(className.selection);
3109
+ },
3110
+ userValue: function(value) {
3111
+ return ($.inArray(value, module.get.userValues()) !== -1);
3112
+ },
3113
+ upward: function($menu) {
3114
+ var $element = $menu || $module;
3115
+ return $element.hasClass(className.upward);
3116
+ },
3117
+ visible: function($subMenu) {
3118
+ return ($subMenu)
3119
+ ? $subMenu.hasClass(className.visible)
3120
+ : $menu.hasClass(className.visible)
3121
+ ;
3122
+ }
3123
+ },
3124
+
3125
+ can: {
3126
+ activate: function($item) {
3127
+ if(settings.useLabels) {
3128
+ return true;
3129
+ }
3130
+ if(!module.has.maxSelections()) {
3131
+ return true;
3132
+ }
3133
+ if(module.has.maxSelections() && $item.hasClass(className.active)) {
3134
+ return true;
3135
+ }
3136
+ return false;
3137
+ },
3138
+ click: function() {
3139
+ return (hasTouch || settings.on == 'click');
3140
+ },
3141
+ extendSelect: function() {
3142
+ return settings.allowAdditions || settings.apiSettings;
3143
+ },
3144
+ show: function() {
3145
+ return !module.is.disabled() && (module.has.items() || module.has.message());
3146
+ },
3147
+ useAPI: function() {
3148
+ return $.fn.api !== undefined;
3149
+ }
3150
+ },
3151
+
3152
+ animate: {
3153
+ show: function(callback, $subMenu) {
3154
+ var
3155
+ $currentMenu = $subMenu || $menu,
3156
+ start = ($subMenu)
3157
+ ? function() {}
3158
+ : function() {
3159
+ module.hideSubMenus();
3160
+ module.hideOthers();
3161
+ module.set.active();
3162
+ },
3163
+ transition
3164
+ ;
3165
+ callback = $.isFunction(callback)
3166
+ ? callback
3167
+ : function(){}
3168
+ ;
3169
+ module.verbose('Doing menu show animation', $currentMenu);
3170
+ module.set.direction($subMenu);
3171
+ transition = module.get.transition($subMenu);
3172
+ if( module.is.selection() ) {
3173
+ module.set.scrollPosition(module.get.selectedItem(), true);
3174
+ }
3175
+ if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) {
3176
+ if(transition == 'none') {
3177
+ start();
3178
+ $currentMenu.transition('show');
3179
+ callback.call(element);
3180
+ }
3181
+ else if($.fn.transition !== undefined && $module.transition('is supported')) {
3182
+ $currentMenu
3183
+ .transition({
3184
+ animation : transition + ' in',
3185
+ debug : settings.debug,
3186
+ verbose : settings.verbose,
3187
+ duration : settings.duration,
3188
+ queue : true,
3189
+ onStart : start,
3190
+ onComplete : function() {
3191
+ callback.call(element);
3192
+ }
3193
+ })
3194
+ ;
3195
+ }
3196
+ else {
3197
+ module.error(error.noTransition, transition);
3198
+ }
3199
+ }
3200
+ },
3201
+ hide: function(callback, $subMenu) {
3202
+ var
3203
+ $currentMenu = $subMenu || $menu,
3204
+ duration = ($subMenu)
3205
+ ? (settings.duration * 0.9)
3206
+ : settings.duration,
3207
+ start = ($subMenu)
3208
+ ? function() {}
3209
+ : function() {
3210
+ if( module.can.click() ) {
3211
+ module.unbind.intent();
3212
+ }
3213
+ module.remove.active();
3214
+ },
3215
+ transition = module.get.transition($subMenu)
3216
+ ;
3217
+ callback = $.isFunction(callback)
3218
+ ? callback
3219
+ : function(){}
3220
+ ;
3221
+ if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) {
3222
+ module.verbose('Doing menu hide animation', $currentMenu);
3223
+
3224
+ if(transition == 'none') {
3225
+ start();
3226
+ $currentMenu.transition('hide');
3227
+ callback.call(element);
3228
+ }
3229
+ else if($.fn.transition !== undefined && $module.transition('is supported')) {
3230
+ $currentMenu
3231
+ .transition({
3232
+ animation : transition + ' out',
3233
+ duration : settings.duration,
3234
+ debug : settings.debug,
3235
+ verbose : settings.verbose,
3236
+ queue : true,
3237
+ onStart : start,
3238
+ onComplete : function() {
3239
+ if(settings.direction == 'auto') {
3240
+ module.remove.upward($subMenu);
3241
+ }
3242
+ callback.call(element);
3243
+ }
3244
+ })
3245
+ ;
3246
+ }
3247
+ else {
3248
+ module.error(error.transition);
3249
+ }
3250
+ }
3251
+ }
3252
+ },
3253
+
3254
+ hideAndClear: function() {
3255
+ module.remove.searchTerm();
3256
+ if( module.has.maxSelections() ) {
3257
+ return;
3258
+ }
3259
+ if(module.has.search()) {
3260
+ module.hide(function() {
3261
+ module.remove.filteredItem();
3262
+ });
3263
+ }
3264
+ else {
3265
+ module.hide();
3266
+ }
3267
+ },
3268
+
3269
+ delay: {
3270
+ show: function() {
3271
+ module.verbose('Delaying show event to ensure user intent');
3272
+ clearTimeout(module.timer);
3273
+ module.timer = setTimeout(module.show, settings.delay.show);
3274
+ },
3275
+ hide: function() {
3276
+ module.verbose('Delaying hide event to ensure user intent');
3277
+ clearTimeout(module.timer);
3278
+ module.timer = setTimeout(module.hide, settings.delay.hide);
3279
+ }
3280
+ },
3281
+
3282
+ escape: {
3283
+ value: function(value) {
3284
+ var
3285
+ multipleValues = $.isArray(value),
3286
+ stringValue = (typeof value === 'string'),
3287
+ isUnparsable = (!stringValue && !multipleValues),
3288
+ hasQuotes = (stringValue && value.search(regExp.quote) !== -1),
3289
+ values = []
3290
+ ;
3291
+ if(!module.has.selectInput() || isUnparsable || !hasQuotes) {
3292
+ return value;
3293
+ }
3294
+ module.debug('Encoding quote values for use in select', value);
3295
+ if(multipleValues) {
3296
+ $.each(value, function(index, value){
3297
+ values.push(value.replace(regExp.quote, '&quot;'));
3298
+ });
3299
+ return values;
3300
+ }
3301
+ return value.replace(regExp.quote, '&quot;');
3302
+ },
3303
+ regExp: function(text) {
3304
+ text = String(text);
3305
+ return text.replace(regExp.escape, '\\$&');
3306
+ }
3307
+ },
3308
+
3309
+ setting: function(name, value) {
3310
+ module.debug('Changing setting', name, value);
3311
+ if( $.isPlainObject(name) ) {
3312
+ $.extend(true, settings, name);
3313
+ }
3314
+ else if(value !== undefined) {
3315
+ if($.isPlainObject(settings[name])) {
3316
+ $.extend(true, settings[name], value);
3317
+ }
3318
+ else {
3319
+ settings[name] = value;
3320
+ }
3321
+ }
3322
+ else {
3323
+ return settings[name];
3324
+ }
3325
+ },
3326
+ internal: function(name, value) {
3327
+ if( $.isPlainObject(name) ) {
3328
+ $.extend(true, module, name);
3329
+ }
3330
+ else if(value !== undefined) {
3331
+ module[name] = value;
3332
+ }
3333
+ else {
3334
+ return module[name];
3335
+ }
3336
+ },
3337
+ debug: function() {
3338
+ if(!settings.silent && settings.debug) {
3339
+ if(settings.performance) {
3340
+ module.performance.log(arguments);
3341
+ }
3342
+ else {
3343
+ module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
3344
+ module.debug.apply(console, arguments);
3345
+ }
3346
+ }
3347
+ },
3348
+ verbose: function() {
3349
+ if(!settings.silent && settings.verbose && settings.debug) {
3350
+ if(settings.performance) {
3351
+ module.performance.log(arguments);
3352
+ }
3353
+ else {
3354
+ module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
3355
+ module.verbose.apply(console, arguments);
3356
+ }
3357
+ }
3358
+ },
3359
+ error: function() {
3360
+ if(!settings.silent) {
3361
+ module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
3362
+ module.error.apply(console, arguments);
3363
+ }
3364
+ },
3365
+ performance: {
3366
+ log: function(message) {
3367
+ var
3368
+ currentTime,
3369
+ executionTime,
3370
+ previousTime
3371
+ ;
3372
+ if(settings.performance) {
3373
+ currentTime = new Date().getTime();
3374
+ previousTime = time || currentTime;
3375
+ executionTime = currentTime - previousTime;
3376
+ time = currentTime;
3377
+ performance.push({
3378
+ 'Name' : message[0],
3379
+ 'Arguments' : [].slice.call(message, 1) || '',
3380
+ 'Element' : element,
3381
+ 'Execution Time' : executionTime
3382
+ });
3383
+ }
3384
+ clearTimeout(module.performance.timer);
3385
+ module.performance.timer = setTimeout(module.performance.display, 500);
3386
+ },
3387
+ display: function() {
3388
+ var
3389
+ title = settings.name + ':',
3390
+ totalTime = 0
3391
+ ;
3392
+ time = false;
3393
+ clearTimeout(module.performance.timer);
3394
+ $.each(performance, function(index, data) {
3395
+ totalTime += data['Execution Time'];
3396
+ });
3397
+ title += ' ' + totalTime + 'ms';
3398
+ if(moduleSelector) {
3399
+ title += ' \'' + moduleSelector + '\'';
3400
+ }
3401
+ if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
3402
+ console.groupCollapsed(title);
3403
+ if(console.table) {
3404
+ console.table(performance);
3405
+ }
3406
+ else {
3407
+ $.each(performance, function(index, data) {
3408
+ console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
3409
+ });
3410
+ }
3411
+ console.groupEnd();
3412
+ }
3413
+ performance = [];
3414
+ }
3415
+ },
3416
+ invoke: function(query, passedArguments, context) {
3417
+ var
3418
+ object = instance,
3419
+ maxDepth,
3420
+ found,
3421
+ response
3422
+ ;
3423
+ passedArguments = passedArguments || queryArguments;
3424
+ context = element || context;
3425
+ if(typeof query == 'string' && object !== undefined) {
3426
+ query = query.split(/[\. ]/);
3427
+ maxDepth = query.length - 1;
3428
+ $.each(query, function(depth, value) {
3429
+ var camelCaseValue = (depth != maxDepth)
3430
+ ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
3431
+ : query
3432
+ ;
3433
+ if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
3434
+ object = object[camelCaseValue];
3435
+ }
3436
+ else if( object[camelCaseValue] !== undefined ) {
3437
+ found = object[camelCaseValue];
3438
+ return false;
3439
+ }
3440
+ else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
3441
+ object = object[value];
3442
+ }
3443
+ else if( object[value] !== undefined ) {
3444
+ found = object[value];
3445
+ return false;
3446
+ }
3447
+ else {
3448
+ module.error(error.method, query);
3449
+ return false;
3450
+ }
3451
+ });
3452
+ }
3453
+ if ( $.isFunction( found ) ) {
3454
+ response = found.apply(context, passedArguments);
3455
+ }
3456
+ else if(found !== undefined) {
3457
+ response = found;
3458
+ }
3459
+ if($.isArray(returnedValue)) {
3460
+ returnedValue.push(response);
3461
+ }
3462
+ else if(returnedValue !== undefined) {
3463
+ returnedValue = [returnedValue, response];
3464
+ }
3465
+ else if(response !== undefined) {
3466
+ returnedValue = response;
3467
+ }
3468
+ return found;
3469
+ }
3470
+ };
3471
+
3472
+ if(methodInvoked) {
3473
+ if(instance === undefined) {
3474
+ module.initialize();
3475
+ }
3476
+ module.invoke(query);
3477
+ }
3478
+ else {
3479
+ if(instance !== undefined) {
3480
+ instance.invoke('destroy');
3481
+ }
3482
+ module.initialize();
3483
+ }
3484
+ })
3485
+ ;
3486
+ return (returnedValue !== undefined)
3487
+ ? returnedValue
3488
+ : $allModules
3489
+ ;
3490
+ };
3491
+
3492
+ $.fn.dropdown.settings = {
3493
+
3494
+ silent : false,
3495
+ debug : false,
3496
+ verbose : false,
3497
+ performance : true,
3498
+
3499
+ on : 'click', // what event should show menu action on item selection
3500
+ action : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){})
3501
+
3502
+
3503
+ apiSettings : false,
3504
+ selectOnKeydown : true, // Whether selection should occur automatically when keyboard shortcuts used
3505
+ minCharacters : 0, // Minimum characters required to trigger API call
3506
+ saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh
3507
+ throttle : 200, // How long to wait after last user input to search remotely
3508
+
3509
+ context : window, // Context to use when determining if on screen
3510
+ direction : 'auto', // Whether dropdown should always open in one direction
3511
+ keepOnScreen : true, // Whether dropdown should check whether it is on screen before showing
3512
+
3513
+ match : 'both', // what to match against with search selection (both, text, or label)
3514
+ fullTextSearch : false, // search anywhere in value (set to 'exact' to require exact matches)
3515
+
3516
+ placeholder : 'auto', // whether to convert blank <select> values to placeholder text
3517
+ preserveHTML : true, // preserve html when selecting value
3518
+ sortSelect : false, // sort selection on init
3519
+
3520
+ forceSelection : true, // force a choice on blur with search selection
3521
+
3522
+ allowAdditions : false, // whether multiple select should allow user added values
3523
+ hideAdditions : true, // whether or not to hide special message prompting a user they can enter a value
3524
+
3525
+ maxSelections : false, // When set to a number limits the number of selections to this count
3526
+ useLabels : true, // whether multiple select should filter currently active selections from choices
3527
+ delimiter : ',', // when multiselect uses normal <input> the values will be delimited with this character
3528
+
3529
+ showOnFocus : true, // show menu on focus
3530
+ allowReselection : false, // whether current value should trigger callbacks when reselected
3531
+ allowTab : true, // add tabindex to element
3532
+ allowCategorySelection : false, // allow elements with sub-menus to be selected
3533
+
3534
+ fireOnInit : false, // Whether callbacks should fire when initializing dropdown values
3535
+
3536
+ transition : 'auto', // auto transition will slide down or up based on direction
3537
+ duration : 200, // duration of transition
3538
+
3539
+ glyphWidth : 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width
3540
+
3541
+ // label settings on multi-select
3542
+ label: {
3543
+ transition : 'scale',
3544
+ duration : 200,
3545
+ variation : false
3546
+ },
3547
+
3548
+ // delay before event
3549
+ delay : {
3550
+ hide : 300,
3551
+ show : 200,
3552
+ search : 20,
3553
+ touch : 50
3554
+ },
3555
+
3556
+ /* Callbacks */
3557
+ onChange : function(value, text, $selected){},
3558
+ onAdd : function(value, text, $selected){},
3559
+ onRemove : function(value, text, $selected){},
3560
+
3561
+ onLabelSelect : function($selectedLabels){},
3562
+ onLabelCreate : function(value, text) { return $(this); },
3563
+ onLabelRemove : function(value) { return true; },
3564
+ onNoResults : function(searchTerm) { return true; },
3565
+ onShow : function(){},
3566
+ onHide : function(){},
3567
+
3568
+ /* Component */
3569
+ name : 'Dropdown',
3570
+ namespace : 'dropdown',
3571
+
3572
+ message: {
3573
+ addResult : 'Add <b>{term}</b>',
3574
+ count : '{count} selected',
3575
+ maxSelections : 'Max {maxCount} selections',
3576
+ noResults : 'No results found.',
3577
+ serverError : 'There was an error contacting the server'
3578
+ },
3579
+
3580
+ error : {
3581
+ action : 'You called a dropdown action that was not defined',
3582
+ alreadySetup : 'Once a select has been initialized behaviors must be called on the created ui dropdown',
3583
+ labels : 'Allowing user additions currently requires the use of labels.',
3584
+ missingMultiple : '<select> requires multiple property to be set to correctly preserve multiple values',
3585
+ method : 'The method you called is not defined.',
3586
+ noAPI : 'The API module is required to load resources remotely',
3587
+ noStorage : 'Saving remote data requires session storage',
3588
+ noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>'
3589
+ },
3590
+
3591
+ regExp : {
3592
+ escape : /[-[\]{}()*+?.,\\^$|#\s]/g,
3593
+ quote : /"/g
3594
+ },
3595
+
3596
+ metadata : {
3597
+ defaultText : 'defaultText',
3598
+ defaultValue : 'defaultValue',
3599
+ placeholderText : 'placeholder',
3600
+ text : 'text',
3601
+ value : 'value'
3602
+ },
3603
+
3604
+ // property names for remote query
3605
+ fields: {
3606
+ remoteValues : 'results', // grouping for api results
3607
+ values : 'values', // grouping for all dropdown values
3608
+ disabled : 'disabled', // whether value should be disabled
3609
+ name : 'name', // displayed dropdown text
3610
+ value : 'value', // actual dropdown value
3611
+ text : 'text' // displayed text when selected
3612
+ },
3613
+
3614
+ keys : {
3615
+ backspace : 8,
3616
+ delimiter : 188, // comma
3617
+ deleteKey : 46,
3618
+ enter : 13,
3619
+ escape : 27,
3620
+ pageUp : 33,
3621
+ pageDown : 34,
3622
+ leftArrow : 37,
3623
+ upArrow : 38,
3624
+ rightArrow : 39,
3625
+ downArrow : 40
3626
+ },
3627
+
3628
+ selector : {
3629
+ addition : '.addition',
3630
+ dropdown : '.ui.dropdown',
3631
+ hidden : '.hidden',
3632
+ icon : '> .dropdown.icon',
3633
+ input : '> input[type="hidden"], > select',
3634
+ item : '.item',
3635
+ label : '> .label',
3636
+ remove : '> .label > .delete.icon',
3637
+ siblingLabel : '.label',
3638
+ menu : '.menu',
3639
+ message : '.message',
3640
+ menuIcon : '.dropdown.icon',
3641
+ search : 'input.search, .menu > .search > input, .menu input.search',
3642
+ sizer : '> input.sizer',
3643
+ text : '> .text:not(.icon)',
3644
+ unselectable : '.disabled, .filtered'
3645
+ },
3646
+
3647
+ className : {
3648
+ active : 'active',
3649
+ addition : 'addition',
3650
+ animating : 'animating',
3651
+ disabled : 'disabled',
3652
+ empty : 'empty',
3653
+ dropdown : 'ui dropdown',
3654
+ filtered : 'filtered',
3655
+ hidden : 'hidden transition',
3656
+ item : 'item',
3657
+ label : 'ui label',
3658
+ loading : 'loading',
3659
+ menu : 'menu',
3660
+ message : 'message',
3661
+ multiple : 'multiple',
3662
+ placeholder : 'default',
3663
+ sizer : 'sizer',
3664
+ search : 'search',
3665
+ selected : 'selected',
3666
+ selection : 'selection',
3667
+ upward : 'upward',
3668
+ visible : 'visible'
3669
+ }
3670
+
3671
+ };
3672
+
3673
+ /* Templates */
3674
+ $.fn.dropdown.settings.templates = {
3675
+
3676
+ // generates dropdown from select values
3677
+ dropdown: function(select) {
3678
+ var
3679
+ placeholder = select.placeholder || false,
3680
+ values = select.values || {},
3681
+ html = ''
3682
+ ;
3683
+ html += '<i class="dropdown icon"></i>';
3684
+ if(select.placeholder) {
3685
+ html += '<div class="default text">' + placeholder + '</div>';
3686
+ }
3687
+ else {
3688
+ html += '<div class="text"></div>';
3689
+ }
3690
+ html += '<div class="menu">';
3691
+ $.each(select.values, function(index, option) {
3692
+ html += (option.disabled)
3693
+ ? '<div class="disabled item" data-value="' + option.value + '">' + option.name + '</div>'
3694
+ : '<div class="item" data-value="' + option.value + '">' + option.name + '</div>'
3695
+ ;
3696
+ });
3697
+ html += '</div>';
3698
+ return html;
3699
+ },
3700
+
3701
+ // generates just menu from select
3702
+ menu: function(response, fields) {
3703
+ var
3704
+ values = response[fields.values] || {},
3705
+ html = ''
3706
+ ;
3707
+ $.each(values, function(index, option) {
3708
+ var
3709
+ maybeText = (option[fields.text])
3710
+ ? 'data-text="' + option[fields.text] + '"'
3711
+ : '',
3712
+ maybeDisabled = (option[fields.disabled])
3713
+ ? 'disabled '
3714
+ : ''
3715
+ ;
3716
+ html += '<div class="'+ maybeDisabled +'item" data-value="' + option[fields.value] + '"' + maybeText + '>'
3717
+ html += option[fields.name];
3718
+ html += '</div>';
3719
+ });
3720
+ return html;
3721
+ },
3722
+
3723
+ // generates label for multiselect
3724
+ label: function(value, text) {
3725
+ return text + '<i class="delete icon"></i>';
3726
+ },
3727
+
3728
+
3729
+ // generates messages like "No results"
3730
+ message: function(message) {
3731
+ return message;
3732
+ },
3733
+
3734
+ // generates user addition to selection menu
3735
+ addition: function(choice) {
3736
+ return choice;
3737
+ }
3738
+
3739
+ };
3740
+
3741
+ })( jQuery, window, document );