@pageboard/html 0.14.19 → 0.14.20

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