public/lib/semantic/components/dropdown.js   F
last analyzed

Complexity

Total Complexity 849
Complexity/F 2.81

Size

Lines of Code 3830
Function Count 302

Duplication

Duplicated Lines 3830
Ratio 100 %

Importance

Changes 0
Metric Value
cc 0
eloc 2640
nc 0
dl 3830
loc 3830
rs 0.8
c 0
b 0
f 0
wmc 849
mnd 7
bc 722
fnc 302
bpm 2.3907
cpm 2.8112
noi 89

15 Functions

Rating   Name   Duplication   Size   Complexity  
A $.fn.dropdown.settings.onShow 1 1 1
A $.fn.dropdown.settings.templates.label 3 3 1
A $.fn.dropdown.settings.onLabelRemove 1 1 1
A $.fn.dropdown.settings.onChange 1 1 1
A $.fn.dropdown.settings.onLabelCreate 1 1 1
A $.fn.dropdown.settings.templates.message 3 3 1
A $.fn.dropdown.settings.onLabelSelect 1 1 1
A $.fn.dropdown.settings.onHide 1 1 1
A $.fn.dropdown.settings.templates.dropdown 23 23 2
A $.fn.dropdown.settings.templates.menu 20 20 1
B $.fn.dropdown 3564 3564 2
A $.fn.dropdown.settings.onRemove 1 1 1
A $.fn.dropdown.settings.onNoResults 1 1 1
A $.fn.dropdown.settings.onAdd 1 1 1
A $.fn.dropdown.settings.templates.addition 3 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like public/lib/semantic/components/dropdown.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*!
2
 * # Semantic UI 2.2.11 - 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 View Code Duplication
;(function ($, window, document, undefined) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
12
13
"use strict";
14
15
window = (typeof window != 'undefined' && window.Math == Math)
16
  ? window
17
  : (typeof self != 'undefined' && self.Math == Math)
0 ignored issues
show
Bug introduced by
The variable self seems to be never declared. If this is a global, consider adding a /** global: self */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
18
    ? self
19
    : Function('return this')()
0 ignored issues
show
Performance Best Practice introduced by
Using new Function() to create a function is slow and difficult to debug. Such functions do not create a closure. Consider using another way to define your function.
Loading history...
20
;
21
22
$.fn.dropdown = function(parameters) {
23
  var
24
    $allModules    = $(this),
25
    $document      = $(document),
26
27
    moduleSelector = $allModules.selector || '',
28
29
    hasTouch       = ('ontouchstart' in document.documentElement),
30
    time           = new Date().getTime(),
31
    performance    = [],
32
33
    query          = arguments[0],
34
    methodInvoked  = (typeof query == 'string'),
35
    queryArguments = [].slice.call(arguments, 1),
36
    returnedValue
37
  ;
38
39
  $allModules
40
    .each(function(elementIndex) {
41
      var
42
        settings          = ( $.isPlainObject(parameters) )
43
          ? $.extend(true, {}, $.fn.dropdown.settings, parameters)
44
          : $.extend({}, $.fn.dropdown.settings),
45
46
        className       = settings.className,
47
        message         = settings.message,
48
        fields          = settings.fields,
49
        keys            = settings.keys,
50
        metadata        = settings.metadata,
51
        namespace       = settings.namespace,
52
        regExp          = settings.regExp,
53
        selector        = settings.selector,
54
        error           = settings.error,
55
        templates       = settings.templates,
56
57
        eventNamespace  = '.' + namespace,
58
        moduleNamespace = 'module-' + namespace,
59
60
        $module         = $(this),
61
        $context        = $(settings.context),
62
        $text           = $module.find(selector.text),
63
        $search         = $module.find(selector.search),
64
        $sizer          = $module.find(selector.sizer),
65
        $input          = $module.find(selector.input),
66
        $icon           = $module.find(selector.icon),
67
68
        $combo = ($module.prev().find(selector.text).length > 0)
69
          ? $module.prev().find(selector.text)
70
          : $module.prev(),
71
72
        $menu           = $module.children(selector.menu),
73
        $item           = $menu.find(selector.item),
74
75
        activated       = false,
76
        itemActivated   = false,
77
        internalChange  = false,
78
        element         = this,
79
        instance        = $module.data(moduleNamespace),
80
81
        initialLoad,
82
        pageLostFocus,
83
        willRefocus,
84
        elementNamespace,
85
        id,
86
        selectObserver,
87
        menuObserver,
88
        module
89
      ;
90
91
      module = {
92
93
        initialize: function() {
94
          module.debug('Initializing dropdown', settings);
95
96
          if( module.is.alreadySetup() ) {
97
            module.setup.reference();
98
          }
99
          else {
100
            module.setup.layout();
101
            module.refreshData();
102
103
            module.save.defaults();
104
            module.restore.selected();
105
106
            module.create.id();
107
            module.bind.events();
108
109
            module.observeChanges();
110
            module.instantiate();
111
          }
112
113
        },
114
115
        instantiate: function() {
116
          module.verbose('Storing instance of dropdown', module);
117
          instance = module;
118
          $module
119
            .data(moduleNamespace, module)
120
          ;
121
        },
122
123
        destroy: function() {
124
          module.verbose('Destroying previous dropdown', $module);
125
          module.remove.tabbable();
126
          $module
127
            .off(eventNamespace)
128
            .removeData(moduleNamespace)
129
          ;
130
          $menu
131
            .off(eventNamespace)
132
          ;
133
          $document
134
            .off(elementNamespace)
135
          ;
136
          module.disconnect.menuObserver();
137
          module.disconnect.selectObserver();
138
        },
139
140
        observeChanges: function() {
141
          if('MutationObserver' in window) {
142
            selectObserver = new MutationObserver(module.event.select.mutation);
0 ignored issues
show
Bug introduced by
The variable MutationObserver seems to be never declared. If this is a global, consider adding a /** global: MutationObserver */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
143
            menuObserver   = new MutationObserver(module.event.menu.mutation);
144
            module.debug('Setting up mutation observer', selectObserver, menuObserver);
145
            module.observe.select();
146
            module.observe.menu();
147
          }
148
        },
149
150
        disconnect: {
151
          menuObserver: function() {
152
            if(menuObserver) {
153
              menuObserver.disconnect();
154
            }
155
          },
156
          selectObserver: function() {
157
            if(selectObserver) {
158
              selectObserver.disconnect();
159
            }
160
          }
161
        },
162
        observe: {
163
          select: function() {
164
            if(module.has.input()) {
165
              selectObserver.observe($input[0], {
166
                childList : true,
167
                subtree   : true
168
              });
169
            }
170
          },
171
          menu: function() {
172
            if(module.has.menu()) {
173
              menuObserver.observe($menu[0], {
174
                childList : true,
175
                subtree   : true
176
              });
177
            }
178
          }
179
        },
180
181
        create: {
182
          id: function() {
183
            id = (Math.random().toString(16) + '000000000').substr(2, 8);
184
            elementNamespace = '.' + id;
185
            module.verbose('Creating unique id for element', id);
186
          },
187
          userChoice: function(values) {
188
            var
189
              $userChoices,
190
              $userChoice,
191
              isUserValue,
0 ignored issues
show
Unused Code introduced by
The variable isUserValue seems to be never used. Consider removing it.
Loading history...
192
              html
193
            ;
194
            values = values || module.get.userValues();
195
            if(!values) {
196
              return false;
197
            }
198
            values = $.isArray(values)
199
              ? values
200
              : [values]
201
            ;
202
            $.each(values, function(index, value) {
203
              if(module.get.item(value) === false) {
204
                html         = settings.templates.addition( module.add.variables(message.addResult, value) );
205
                $userChoice  = $('<div />')
206
                  .html(html)
207
                  .attr('data-' + metadata.value, value)
208
                  .attr('data-' + metadata.text, value)
209
                  .addClass(className.addition)
210
                  .addClass(className.item)
211
                ;
212
                if(settings.hideAdditions) {
213
                  $userChoice.addClass(className.hidden);
214
                }
215
                $userChoices = ($userChoices === undefined)
216
                  ? $userChoice
217
                  : $userChoices.add($userChoice)
218
                ;
219
                module.verbose('Creating user choices for value', value, $userChoice);
220
              }
221
            });
222
            return $userChoices;
223
          },
224
          userLabels: function(value) {
225
            var
226
              userValues = module.get.userValues()
227
            ;
228
            if(userValues) {
229
              module.debug('Adding user labels', userValues);
230
              $.each(userValues, function(index, value) {
231
                module.verbose('Adding custom user value');
232
                module.add.label(value, value);
233
              });
234
            }
235
          },
236
          menu: function() {
237
            $menu = $('<div />')
238
              .addClass(className.menu)
239
              .appendTo($module)
240
            ;
241
          },
242
          sizer: function() {
243
            $sizer = $('<span />')
244
              .addClass(className.sizer)
245
              .insertAfter($search)
246
            ;
247
          }
248
        },
249
250
        search: function(query) {
251
          query = (query !== undefined)
252
            ? query
253
            : module.get.query()
254
          ;
255
          module.verbose('Searching for query', query);
256
          if(module.has.minCharacters(query)) {
257
            module.filter(query);
258
          }
259
          else {
260
            module.hide();
261
          }
262
        },
263
264
        select: {
265
          firstUnfiltered: function() {
266
            module.verbose('Selecting first non-filtered element');
267
            module.remove.selectedItem();
268
            $item
269
              .not(selector.unselectable)
270
              .not(selector.addition + selector.hidden)
271
                .eq(0)
272
                .addClass(className.selected)
273
            ;
274
          },
275
          nextAvailable: function($selected) {
276
            $selected = $selected.eq(0);
277
            var
278
              $nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0),
279
              $prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0),
280
              hasNext        = ($nextAvailable.length > 0)
281
            ;
282
            if(hasNext) {
283
              module.verbose('Moving selection to', $nextAvailable);
284
              $nextAvailable.addClass(className.selected);
285
            }
286
            else {
287
              module.verbose('Moving selection to', $prevAvailable);
288
              $prevAvailable.addClass(className.selected);
289
            }
290
          }
291
        },
292
293
        setup: {
294
          api: function() {
295
            var
296
              apiSettings = {
297
                debug   : settings.debug,
298
                urlData : {
299
                  value : module.get.value(),
300
                  query : module.get.query()
301
                },
302
                on    : false
303
              }
304
            ;
305
            module.verbose('First request, initializing API');
306
            $module
307
              .api(apiSettings)
308
            ;
309
          },
310
          layout: function() {
311
            if( $module.is('select') ) {
312
              module.setup.select();
313
              module.setup.returnedObject();
314
            }
315
            if( !module.has.menu() ) {
316
              module.create.menu();
317
            }
318
            if( module.is.search() && !module.has.search() ) {
319
              module.verbose('Adding search input');
320
              $search = $('<input />')
321
                .addClass(className.search)
322
                .prop('autocomplete', 'off')
323
                .insertBefore($text)
324
              ;
325
            }
326
            if( module.is.multiple() && module.is.searchSelection() && !module.has.sizer()) {
327
              module.create.sizer();
328
            }
329
            if(settings.allowTab) {
330
              module.set.tabbable();
331
            }
332
          },
333
          select: function() {
334
            var
335
              selectValues  = module.get.selectValues()
336
            ;
337
            module.debug('Dropdown initialized on a select', selectValues);
338
            if( $module.is('select') ) {
339
              $input = $module;
340
            }
341
            // see if select is placed correctly already
342
            if($input.parent(selector.dropdown).length > 0) {
343
              module.debug('UI dropdown already exists. Creating dropdown menu only');
344
              $module = $input.closest(selector.dropdown);
345
              if( !module.has.menu() ) {
346
                module.create.menu();
347
              }
348
              $menu = $module.children(selector.menu);
349
              module.setup.menu(selectValues);
350
            }
351
            else {
352
              module.debug('Creating entire dropdown from select');
353
              $module = $('<div />')
354
                .attr('class', $input.attr('class') )
355
                .addClass(className.selection)
356
                .addClass(className.dropdown)
357
                .html( templates.dropdown(selectValues) )
358
                .insertBefore($input)
359
              ;
360
              if($input.hasClass(className.multiple) && $input.prop('multiple') === false) {
361
                module.error(error.missingMultiple);
362
                $input.prop('multiple', true);
363
              }
364
              if($input.is('[multiple]')) {
365
                module.set.multiple();
366
              }
367
              if ($input.prop('disabled')) {
368
                module.debug('Disabling dropdown');
369
                $module.addClass(className.disabled);
370
              }
371
              $input
372
                .removeAttr('class')
373
                .detach()
374
                .prependTo($module)
375
              ;
376
            }
377
            module.refresh();
378
          },
379
          menu: function(values) {
380
            $menu.html( templates.menu(values, fields));
381
            $item = $menu.find(selector.item);
382
          },
383
          reference: function() {
384
            module.debug('Dropdown behavior was called on select, replacing with closest dropdown');
385
            // replace module reference
386
            $module = $module.parent(selector.dropdown);
387
            module.refresh();
388
            module.setup.returnedObject();
389
            // invoke method in context of current instance
390
            if(methodInvoked) {
391
              instance = module;
392
              module.invoke(query);
393
            }
394
          },
395
          returnedObject: function() {
396
            var
397
              $firstModules = $allModules.slice(0, elementIndex),
398
              $lastModules = $allModules.slice(elementIndex + 1)
399
            ;
400
            // adjust all modules to use correct reference
401
            $allModules = $firstModules.add($module).add($lastModules);
402
          }
403
        },
404
405
        refresh: function() {
406
          module.refreshSelectors();
407
          module.refreshData();
408
        },
409
410
        refreshItems: function() {
411
          $item = $menu.find(selector.item);
412
        },
413
414
        refreshSelectors: function() {
415
          module.verbose('Refreshing selector cache');
416
          $text   = $module.find(selector.text);
417
          $search = $module.find(selector.search);
418
          $input  = $module.find(selector.input);
419
          $icon   = $module.find(selector.icon);
420
          $combo  = ($module.prev().find(selector.text).length > 0)
421
            ? $module.prev().find(selector.text)
422
            : $module.prev()
423
          ;
424
          $menu    = $module.children(selector.menu);
425
          $item    = $menu.find(selector.item);
426
        },
427
428
        refreshData: function() {
429
          module.verbose('Refreshing cached metadata');
430
          $item
431
            .removeData(metadata.text)
432
            .removeData(metadata.value)
433
          ;
434
        },
435
436
        clearData: function() {
437
          module.verbose('Clearing metadata');
438
          $item
439
            .removeData(metadata.text)
440
            .removeData(metadata.value)
441
          ;
442
          $module
443
            .removeData(metadata.defaultText)
444
            .removeData(metadata.defaultValue)
445
            .removeData(metadata.placeholderText)
446
          ;
447
        },
448
449
        toggle: function() {
450
          module.verbose('Toggling menu visibility');
451
          if( !module.is.active() ) {
452
            module.show();
453
          }
454
          else {
455
            module.hide();
456
          }
457
        },
458
459
        show: function(callback) {
460
          callback = $.isFunction(callback)
461
            ? callback
462
            : function(){}
463
          ;
464
          if(!module.can.show() && module.is.remote()) {
465
            module.debug('No API results retrieved, searching before show');
466
            module.queryRemote(module.get.query(), module.show);
467
          }
468
          if( module.can.show() && !module.is.active() ) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if module.can.show() && !module.is.active() is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
469
            module.debug('Showing dropdown');
470
            if(module.has.message() && !(module.has.maxSelections() || module.has.allResultsFiltered()) ) {
471
              module.remove.message();
472
            }
473
            if(module.is.allFiltered()) {
474
              return true;
475
            }
476
            if(settings.onShow.call(element) !== false) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if settings.onShow.call(element) !== false is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
477
              module.animate.show(function() {
478
                if( module.can.click() ) {
479
                  module.bind.intent();
480
                }
481
                if(module.has.menuSearch()) {
482
                  module.focusSearch();
483
                }
484
                module.set.visible();
485
                callback.call(element);
486
              });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
487
            }
488
          }
489
        },
490
491
        hide: function(callback) {
492
          callback = $.isFunction(callback)
493
            ? callback
494
            : function(){}
495
          ;
496
          if( module.is.active() ) {
497
            module.debug('Hiding dropdown');
498
            if(settings.onHide.call(element) !== false) {
499
              module.animate.hide(function() {
500
                module.remove.visible();
501
                callback.call(element);
502
              });
503
            }
504
          }
505
        },
506
507
        hideOthers: function() {
508
          module.verbose('Finding other dropdowns to hide');
509
          $allModules
510
            .not($module)
511
              .has(selector.menu + '.' + className.visible)
512
                .dropdown('hide')
513
          ;
514
        },
515
516
        hideMenu: function() {
517
          module.verbose('Hiding menu  instantaneously');
518
          module.remove.active();
519
          module.remove.visible();
520
          $menu.transition('hide');
521
        },
522
523
        hideSubMenus: function() {
524
          var
525
            $subMenus = $menu.children(selector.item).find(selector.menu)
526
          ;
527
          module.verbose('Hiding sub menus', $subMenus);
528
          $subMenus.transition('hide');
529
        },
530
531
        bind: {
532
          events: function() {
533
            if(hasTouch) {
534
              module.bind.touchEvents();
535
            }
536
            module.bind.keyboardEvents();
537
            module.bind.inputEvents();
538
            module.bind.mouseEvents();
539
          },
540
          touchEvents: function() {
541
            module.debug('Touch device detected binding additional touch events');
542
            if( module.is.searchSelection() ) {
0 ignored issues
show
Comprehensibility Documentation Best Practice introduced by
This code block is empty. Consider removing it or adding a comment to explain.
Loading history...
543
              // do nothing special yet
544
            }
545
            else if( module.is.single() ) {
546
              $module
547
                .on('touchstart' + eventNamespace, module.event.test.toggle)
548
              ;
549
            }
550
            $menu
551
              .on('touchstart' + eventNamespace, selector.item, module.event.item.mouseenter)
552
            ;
553
          },
554
          keyboardEvents: function() {
555
            module.verbose('Binding keyboard events');
556
            $module
557
              .on('keydown' + eventNamespace, module.event.keydown)
558
            ;
559
            if( module.has.search() ) {
560
              $module
561
                .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input)
562
              ;
563
            }
564
            if( module.is.multiple() ) {
565
              $document
566
                .on('keydown' + elementNamespace, module.event.document.keydown)
567
              ;
568
            }
569
          },
570
          inputEvents: function() {
571
            module.verbose('Binding input change events');
572
            $module
573
              .on('change' + eventNamespace, selector.input, module.event.change)
574
            ;
575
          },
576
          mouseEvents: function() {
577
            module.verbose('Binding mouse events');
578
            if(module.is.multiple()) {
579
              $module
580
                .on('click'   + eventNamespace, selector.label,  module.event.label.click)
581
                .on('click'   + eventNamespace, selector.remove, module.event.remove.click)
582
              ;
583
            }
584
            if( module.is.searchSelection() ) {
585
              $module
586
                .on('mousedown' + eventNamespace, module.event.mousedown)
587
                .on('mouseup'   + eventNamespace, module.event.mouseup)
588
                .on('mousedown' + eventNamespace, selector.menu,   module.event.menu.mousedown)
589
                .on('mouseup'   + eventNamespace, selector.menu,   module.event.menu.mouseup)
590
                .on('click'     + eventNamespace, selector.icon,   module.event.icon.click)
591
                .on('focus'     + eventNamespace, selector.search, module.event.search.focus)
592
                .on('click'     + eventNamespace, selector.search, module.event.search.focus)
593
                .on('blur'      + eventNamespace, selector.search, module.event.search.blur)
594
                .on('click'     + eventNamespace, selector.text,   module.event.text.focus)
595
              ;
596
              if(module.is.multiple()) {
597
                $module
598
                  .on('click' + eventNamespace, module.event.click)
599
                ;
600
              }
601
            }
602
            else {
603
              if(settings.on == 'click') {
604
                $module
605
                  .on('click' + eventNamespace, selector.icon, module.event.icon.click)
606
                  .on('click' + eventNamespace, module.event.test.toggle)
607
                ;
608
              }
609
              else if(settings.on == 'hover') {
610
                $module
611
                  .on('mouseenter' + eventNamespace, module.delay.show)
612
                  .on('mouseleave' + eventNamespace, module.delay.hide)
613
                ;
614
              }
615
              else {
616
                $module
617
                  .on(settings.on + eventNamespace, module.toggle)
618
                ;
619
              }
620
              $module
621
                .on('mousedown' + eventNamespace, module.event.mousedown)
622
                .on('mouseup'   + eventNamespace, module.event.mouseup)
623
                .on('focus'     + eventNamespace, module.event.focus)
624
              ;
625
              if(module.has.menuSearch() ) {
626
                $module
627
                  .on('blur' + eventNamespace, selector.search, module.event.search.blur)
628
                ;
629
              }
630
              else {
631
                $module
632
                  .on('blur' + eventNamespace, module.event.blur)
633
                ;
634
              }
635
            }
636
            $menu
637
              .on('mouseenter' + eventNamespace, selector.item, module.event.item.mouseenter)
638
              .on('mouseleave' + eventNamespace, selector.item, module.event.item.mouseleave)
639
              .on('click'      + eventNamespace, selector.item, module.event.item.click)
640
            ;
641
          },
642
          intent: function() {
643
            module.verbose('Binding hide intent event to document');
644
            if(hasTouch) {
645
              $document
646
                .on('touchstart' + elementNamespace, module.event.test.touch)
647
                .on('touchmove'  + elementNamespace, module.event.test.touch)
648
              ;
649
            }
650
            $document
651
              .on('click' + elementNamespace, module.event.test.hide)
652
            ;
653
          }
654
        },
655
656
        unbind: {
657
          intent: function() {
658
            module.verbose('Removing hide intent event from document');
659
            if(hasTouch) {
660
              $document
661
                .off('touchstart' + elementNamespace)
662
                .off('touchmove' + elementNamespace)
663
              ;
664
            }
665
            $document
666
              .off('click' + elementNamespace)
667
            ;
668
          }
669
        },
670
671
        filter: function(query) {
672
          var
673
            searchTerm = (query !== undefined)
674
              ? query
675
              : module.get.query(),
676
            afterFiltered = function() {
677
              if(module.is.multiple()) {
678
                module.filterActive();
679
              }
680
              if(query || (!query && module.get.activeItem().length == 0)) {
0 ignored issues
show
Best Practice introduced by
Comparing module.get.activeItem().length to 0 using the == operator is not safe. Consider using === instead.
Loading history...
681
                module.select.firstUnfiltered();
682
              }
683
              if( module.has.allResultsFiltered() ) {
684
                if( settings.onNoResults.call(element, searchTerm) ) {
685
                  if(settings.allowAdditions) {
686
                    if(settings.hideAdditions) {
687
                      module.verbose('User addition with no menu, setting empty style');
688
                      module.set.empty();
689
                      module.hideMenu();
690
                    }
691
                  }
692
                  else {
693
                    module.verbose('All items filtered, showing message', searchTerm);
694
                    module.add.message(message.noResults);
695
                  }
696
                }
697
                else {
698
                  module.verbose('All items filtered, hiding dropdown', searchTerm);
699
                  module.hideMenu();
700
                }
701
              }
702
              else {
703
                module.remove.empty();
704
                module.remove.message();
705
              }
706
              if(settings.allowAdditions) {
707
                module.add.userSuggestion(query);
708
              }
709
              if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) {
710
                module.show();
711
              }
712
            }
713
          ;
714
          if(settings.useLabels && module.has.maxSelections()) {
715
            return;
716
          }
717
          if(settings.apiSettings) {
718
            if( module.can.useAPI() ) {
719
              module.queryRemote(searchTerm, function() {
720
                if(settings.filterRemoteData) {
721
                  module.filterItems(searchTerm);
722
                }
723
                afterFiltered();
724
              });
725
            }
726
            else {
727
              module.error(error.noAPI);
728
            }
729
          }
730
          else {
731
            module.filterItems(searchTerm);
732
            afterFiltered();
733
          }
734
        },
735
736
        queryRemote: function(query, callback) {
737
          var
738
            apiSettings = {
739
              errorDuration : false,
740
              cache         : 'local',
741
              throttle      : settings.throttle,
742
              urlData       : {
743
                query: query
744
              },
745
              onError: function() {
746
                module.add.message(message.serverError);
747
                callback();
748
              },
749
              onFailure: function() {
750
                module.add.message(message.serverError);
751
                callback();
752
              },
753
              onSuccess : function(response) {
754
                module.remove.message();
755
                module.setup.menu({
756
                  values: response[fields.remoteValues]
757
                });
758
                callback();
759
              }
760
            }
761
          ;
762
          if( !$module.api('get request') ) {
763
            module.setup.api();
764
          }
765
          apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings);
766
          $module
767
            .api('setting', apiSettings)
768
            .api('query')
769
          ;
770
        },
771
772
        filterItems: function(query) {
773
          var
774
            searchTerm = (query !== undefined)
775
              ? query
776
              : module.get.query(),
777
            results          =  null,
778
            escapedTerm      = module.escape.string(searchTerm),
779
            beginsWithRegExp = new RegExp('^' + escapedTerm, 'igm')
780
          ;
781
          // avoid loop if we're matching nothing
782
          if( module.has.query() ) {
783
            results = [];
784
785
            module.verbose('Searching for matching values', searchTerm);
786
            $item
787
              .each(function(){
788
                var
789
                  $choice = $(this),
790
                  text,
791
                  value
792
                ;
793
                if(settings.match == 'both' || settings.match == 'text') {
794
                  text = String(module.get.choiceText($choice, false));
795
                  if(text.search(beginsWithRegExp) !== -1) {
796
                    results.push(this);
797
                    return true;
798
                  }
799
                  else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text)) {
800
                    results.push(this);
801
                    return true;
802
                  }
803
                  else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, text)) {
804
                    results.push(this);
805
                    return true;
806
                  }
807
                }
808
                if(settings.match == 'both' || settings.match == 'value') {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if settings.match == "both"...ttings.match == "value" is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
809
                  value = String(module.get.choiceValue($choice, text));
0 ignored issues
show
Bug introduced by
The variable text does not seem to be initialized in case settings.match == "both"...ettings.match == "text" on line 793 is false. Are you sure the function choiceValue handles undefined variables?
Loading history...
810
                  if(value.search(beginsWithRegExp) !== -1) {
811
                    results.push(this);
812
                    return true;
813
                  }
814
                  else if (settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, value)) {
815
                    results.push(this);
816
                    return true;
817
                  }
818
                  else if (settings.fullTextSearch === true && module.fuzzySearch(searchTerm, value)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if settings.fullTextSearch ...arch(searchTerm, value) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
819
                    results.push(this);
820
                    return true;
821
                  }
822
                }
823
              })
824
            ;
825
          }
826
          module.debug('Showing only matched items', searchTerm);
827
          module.remove.filteredItem();
828
          if(results) {
829
            $item
830
              .not(results)
831
              .addClass(className.filtered)
832
            ;
833
          }
834
        },
835
836
        fuzzySearch: function(query, term) {
837
          var
838
            termLength  = term.length,
839
            queryLength = query.length
840
          ;
841
          query = query.toLowerCase();
842
          term  = term.toLowerCase();
843
          if(queryLength > termLength) {
844
            return false;
845
          }
846
          if(queryLength === termLength) {
847
            return (query === term);
848
          }
849
          search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) {
850
            var
851
              queryCharacter = query.charCodeAt(characterIndex)
852
            ;
853
            while(nextCharacterIndex < termLength) {
854
              if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) {
855
                continue search;
856
              }
857
            }
858
            return false;
859
          }
860
          return true;
861
        },
862
        exactSearch: function (query, term) {
863
          query = query.toLowerCase();
864
          term  = term.toLowerCase();
865
          if(term.indexOf(query) > -1) {
866
             return true;
867
          }
868
          return false;
869
        },
870
        filterActive: function() {
871
          if(settings.useLabels) {
872
            $item.filter('.' + className.active)
873
              .addClass(className.filtered)
874
            ;
875
          }
876
        },
877
878
        focusSearch: function(skipHandler) {
879
          if( module.has.search() && !module.is.focusedOnSearch() ) {
880
            if(skipHandler) {
881
              $module.off('focus' + eventNamespace, selector.search);
882
              $search.focus();
883
              $module.on('focus'  + eventNamespace, selector.search, module.event.search.focus);
884
            }
885
            else {
886
              $search.focus();
887
            }
888
          }
889
        },
890
891
        forceSelection: function() {
892
          var
893
            $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0),
894
            $activeItem        = $item.not(className.filtered).filter('.' + className.active).eq(0),
895
            $selectedItem      = ($currentlySelected.length > 0)
896
              ? $currentlySelected
897
              : $activeItem,
898
            hasSelected = ($selectedItem.length > 0)
899
          ;
900
          if(hasSelected && !module.is.multiple()) {
901
            module.debug('Forcing partial selection to selected item', $selectedItem);
902
            module.event.item.click.call($selectedItem, {}, true);
903
            return;
0 ignored issues
show
Unused Code introduced by
This return has no effect and can be removed.
Loading history...
904
          }
905
          else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
906
            if(settings.allowAdditions) {
907
              module.set.selected(module.get.query());
908
              module.remove.searchTerm();
909
            }
910
            else {
911
              module.remove.searchTerm();
912
            }
913
          }
914
        },
915
916
        event: {
917
          change: function() {
918
            if(!internalChange) {
919
              module.debug('Input changed, updating selection');
920
              module.set.selected();
921
            }
922
          },
923
          focus: function() {
924
            if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) {
925
              module.show();
926
            }
927
          },
928
          blur: function(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
929
            pageLostFocus = (document.activeElement === this);
930
            if(!activated && !pageLostFocus) {
931
              module.remove.activeLabel();
932
              module.hide();
933
            }
934
          },
935
          mousedown: function() {
936
            if(module.is.searchSelection()) {
937
              // prevent menu hiding on immediate re-focus
938
              willRefocus = true;
939
            }
940
            else {
941
              // prevents focus callback from occurring on mousedown
942
              activated = true;
943
            }
944
          },
945
          mouseup: function() {
946
            if(module.is.searchSelection()) {
947
              // prevent menu hiding on immediate re-focus
948
              willRefocus = false;
949
            }
950
            else {
951
              activated = false;
952
            }
953
          },
954
          click: function(event) {
955
            var
956
              $target = $(event.target)
957
            ;
958
            // focus search
959
            if($target.is($module)) {
960
              if(!module.is.focusedOnSearch()) {
961
                module.focusSearch();
962
              }
963
              else {
964
                module.show();
965
              }
966
            }
967
          },
968
          search: {
969
            focus: function() {
970
              activated = true;
971
              if(module.is.multiple()) {
972
                module.remove.activeLabel();
973
              }
974
              if(settings.showOnFocus) {
975
                module.search();
976
              }
977
            },
978
            blur: function(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
979
              pageLostFocus = (document.activeElement === this);
980
              if(module.is.searchSelection() && !willRefocus) {
981
                if(!itemActivated && !pageLostFocus) {
982
                  if(settings.forceSelection) {
983
                    module.forceSelection();
984
                  }
985
                  module.hide();
986
                }
987
              }
988
              willRefocus = false;
989
            }
990
          },
991
          icon: {
992
            click: function(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
993
              module.toggle();
994
            }
995
          },
996
          text: {
997
            focus: function(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
998
              activated = true;
999
              module.focusSearch();
1000
            }
1001
          },
1002
          input: function(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1003
            if(module.is.multiple() || module.is.searchSelection()) {
1004
              module.set.filtered();
1005
            }
1006
            clearTimeout(module.timer);
1007
            module.timer = setTimeout(module.search, settings.delay.search);
1008
          },
1009
          label: {
1010
            click: function(event) {
1011
              var
1012
                $label        = $(this),
1013
                $labels       = $module.find(selector.label),
1014
                $activeLabels = $labels.filter('.' + className.active),
1015
                $nextActive   = $label.nextAll('.' + className.active),
1016
                $prevActive   = $label.prevAll('.' + className.active),
1017
                $range = ($nextActive.length > 0)
1018
                  ? $label.nextUntil($nextActive).add($activeLabels).add($label)
1019
                  : $label.prevUntil($prevActive).add($activeLabels).add($label)
1020
              ;
1021
              if(event.shiftKey) {
1022
                $activeLabels.removeClass(className.active);
1023
                $range.addClass(className.active);
1024
              }
1025
              else if(event.ctrlKey) {
1026
                $label.toggleClass(className.active);
1027
              }
1028
              else {
1029
                $activeLabels.removeClass(className.active);
1030
                $label.addClass(className.active);
1031
              }
1032
              settings.onLabelSelect.apply(this, $labels.filter('.' + className.active));
1033
            }
1034
          },
1035
          remove: {
1036
            click: function() {
1037
              var
1038
                $label = $(this).parent()
1039
              ;
1040
              if( $label.hasClass(className.active) ) {
1041
                // remove all selected labels
1042
                module.remove.activeLabels();
1043
              }
1044
              else {
1045
                // remove this label only
1046
                module.remove.activeLabels( $label );
1047
              }
1048
            }
1049
          },
1050
          test: {
1051
            toggle: function(event) {
1052
              var
1053
                toggleBehavior = (module.is.multiple())
1054
                  ? module.show
1055
                  : module.toggle
1056
              ;
1057
              if(module.is.bubbledLabelClick(event) || module.is.bubbledIconClick(event)) {
1058
                return;
1059
              }
1060
              if( module.determine.eventOnElement(event, toggleBehavior) ) {
1061
                event.preventDefault();
1062
              }
1063
            },
1064
            touch: function(event) {
1065
              module.determine.eventOnElement(event, function() {
1066
                if(event.type == 'touchstart') {
1067
                  module.timer = setTimeout(function() {
1068
                    module.hide();
1069
                  }, settings.delay.touch);
1070
                }
1071
                else if(event.type == 'touchmove') {
1072
                  clearTimeout(module.timer);
1073
                }
1074
              });
1075
              event.stopPropagation();
1076
            },
1077
            hide: function(event) {
1078
              module.determine.eventInModule(event, module.hide);
1079
            }
1080
          },
1081
          select: {
1082
            mutation: function(mutations) {
0 ignored issues
show
Unused Code introduced by
The parameter mutations is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1083
              module.debug('<select> modified, recreating menu');
1084
              module.setup.select();
1085
            }
1086
          },
1087
          menu: {
1088
            mutation: function(mutations) {
1089
              var
1090
                mutation   = mutations[0],
1091
                $addedNode = mutation.addedNodes
1092
                  ? $(mutation.addedNodes[0])
1093
                  : $(false),
1094
                $removedNode = mutation.removedNodes
1095
                  ? $(mutation.removedNodes[0])
1096
                  : $(false),
1097
                $changedNodes  = $addedNode.add($removedNode),
1098
                isUserAddition = $changedNodes.is(selector.addition) || $changedNodes.closest(selector.addition).length > 0,
1099
                isMessage      = $changedNodes.is(selector.message)  || $changedNodes.closest(selector.message).length > 0
1100
              ;
1101
              if(isUserAddition || isMessage) {
1102
                module.debug('Updating item selector cache');
1103
                module.refreshItems();
1104
              }
1105
              else {
1106
                module.debug('Menu modified, updating selector cache');
1107
                module.refresh();
1108
              }
1109
            },
1110
            mousedown: function() {
1111
              itemActivated = true;
1112
            },
1113
            mouseup: function() {
1114
              itemActivated = false;
1115
            }
1116
          },
1117
          item: {
1118
            mouseenter: function(event) {
1119
              var
1120
                $target        = $(event.target),
1121
                $item          = $(this),
1122
                $subMenu       = $item.children(selector.menu),
1123
                $otherMenus    = $item.siblings(selector.item).children(selector.menu),
1124
                hasSubMenu     = ($subMenu.length > 0),
1125
                isBubbledEvent = ($subMenu.find($target).length > 0)
1126
              ;
1127
              if( !isBubbledEvent && hasSubMenu ) {
1128
                clearTimeout(module.itemTimer);
1129
                module.itemTimer = setTimeout(function() {
1130
                  module.verbose('Showing sub-menu', $subMenu);
1131
                  $.each($otherMenus, function() {
1132
                    module.animate.hide(false, $(this));
1133
                  });
1134
                  module.animate.show(false, $subMenu);
1135
                }, settings.delay.show);
1136
                event.preventDefault();
1137
              }
1138
            },
1139
            mouseleave: function(event) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1140
              var
1141
                $subMenu = $(this).children(selector.menu)
1142
              ;
1143
              if($subMenu.length > 0) {
1144
                clearTimeout(module.itemTimer);
1145
                module.itemTimer = setTimeout(function() {
1146
                  module.verbose('Hiding sub-menu', $subMenu);
1147
                  module.animate.hide(false, $subMenu);
1148
                }, settings.delay.hide);
1149
              }
1150
            },
1151
            click: function (event, skipRefocus) {
1152
              var
1153
                $choice        = $(this),
1154
                $target        = (event)
1155
                  ? $(event.target)
1156
                  : $(''),
1157
                $subMenu       = $choice.find(selector.menu),
1158
                text           = module.get.choiceText($choice),
1159
                value          = module.get.choiceValue($choice, text),
1160
                hasSubMenu     = ($subMenu.length > 0),
1161
                isBubbledEvent = ($subMenu.find($target).length > 0)
1162
              ;
1163
              // prevents IE11 bug where menu receives focus even though `tabindex=-1`
1164
              if(module.has.menuSearch()) {
1165
                $(document.activeElement).blur();
1166
              }
1167
              if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) {
1168
                if(module.is.searchSelection()) {
1169
                  if(settings.allowAdditions) {
1170
                    module.remove.userAddition();
1171
                  }
1172
                  module.remove.searchTerm();
1173
                  if(!module.is.focusedOnSearch() && !(skipRefocus == true)) {
0 ignored issues
show
Best Practice introduced by
Comparing skipRefocus to true using the == operator is not safe. Consider using === instead.
Loading history...
1174
                    module.focusSearch(true);
1175
                  }
1176
                }
1177
                if(!settings.useLabels) {
1178
                  module.remove.filteredItem();
1179
                  module.set.scrollPosition($choice);
1180
                }
1181
                module.determine.selectAction.call(this, text, value);
1182
              }
1183
            }
1184
          },
1185
1186
          document: {
1187
            // label selection should occur even when element has no focus
1188
            keydown: function(event) {
1189
              var
1190
                pressedKey    = event.which,
1191
                isShortcutKey = module.is.inObject(pressedKey, keys)
1192
              ;
1193
              if(isShortcutKey) {
1194
                var
1195
                  $label            = $module.find(selector.label),
1196
                  $activeLabel      = $label.filter('.' + className.active),
1197
                  activeValue       = $activeLabel.data(metadata.value),
0 ignored issues
show
Unused Code introduced by
The variable activeValue seems to be never used. Consider removing it.
Loading history...
1198
                  labelIndex        = $label.index($activeLabel),
1199
                  labelCount        = $label.length,
1200
                  hasActiveLabel    = ($activeLabel.length > 0),
1201
                  hasMultipleActive = ($activeLabel.length > 1),
1202
                  isFirstLabel      = (labelIndex === 0),
1203
                  isLastLabel       = (labelIndex + 1 == labelCount),
1204
                  isSearch          = module.is.searchSelection(),
1205
                  isFocusedOnSearch = module.is.focusedOnSearch(),
1206
                  isFocused         = module.is.focused(),
1207
                  caretAtStart      = (isFocusedOnSearch && module.get.caretPosition() === 0),
1208
                  $nextLabel
0 ignored issues
show
Unused Code introduced by
The variable $nextLabel seems to be never used. Consider removing it.
Loading history...
1209
                ;
1210
                if(isSearch && !hasActiveLabel && !isFocusedOnSearch) {
1211
                  return;
1212
                }
1213
1214
                if(pressedKey == keys.leftArrow) {
1215
                  // activate previous label
1216
                  if((isFocused || caretAtStart) && !hasActiveLabel) {
1217
                    module.verbose('Selecting previous label');
1218
                    $label.last().addClass(className.active);
1219
                  }
1220
                  else if(hasActiveLabel) {
1221
                    if(!event.shiftKey) {
1222
                      module.verbose('Selecting previous label');
1223
                      $label.removeClass(className.active);
1224
                    }
1225
                    else {
1226
                      module.verbose('Adding previous label to selection');
1227
                    }
1228
                    if(isFirstLabel && !hasMultipleActive) {
1229
                      $activeLabel.addClass(className.active);
1230
                    }
1231
                    else {
1232
                      $activeLabel.prev(selector.siblingLabel)
1233
                        .addClass(className.active)
1234
                        .end()
1235
                      ;
1236
                    }
1237
                    event.preventDefault();
1238
                  }
1239
                }
1240
                else if(pressedKey == keys.rightArrow) {
1241
                  // activate first label
1242
                  if(isFocused && !hasActiveLabel) {
1243
                    $label.first().addClass(className.active);
1244
                  }
1245
                  // activate next label
1246
                  if(hasActiveLabel) {
1247
                    if(!event.shiftKey) {
1248
                      module.verbose('Selecting next label');
1249
                      $label.removeClass(className.active);
1250
                    }
1251
                    else {
1252
                      module.verbose('Adding next label to selection');
1253
                    }
1254
                    if(isLastLabel) {
1255
                      if(isSearch) {
1256
                        if(!isFocusedOnSearch) {
1257
                          module.focusSearch();
1258
                        }
1259
                        else {
1260
                          $label.removeClass(className.active);
1261
                        }
1262
                      }
1263
                      else if(hasMultipleActive) {
1264
                        $activeLabel.next(selector.siblingLabel).addClass(className.active);
1265
                      }
1266
                      else {
1267
                        $activeLabel.addClass(className.active);
1268
                      }
1269
                    }
1270
                    else {
1271
                      $activeLabel.next(selector.siblingLabel).addClass(className.active);
1272
                    }
1273
                    event.preventDefault();
1274
                  }
1275
                }
1276
                else if(pressedKey == keys.deleteKey || pressedKey == keys.backspace) {
1277
                  if(hasActiveLabel) {
1278
                    module.verbose('Removing active labels');
1279
                    if(isLastLabel) {
1280
                      if(isSearch && !isFocusedOnSearch) {
1281
                        module.focusSearch();
1282
                      }
1283
                    }
1284
                    $activeLabel.last().next(selector.siblingLabel).addClass(className.active);
1285
                    module.remove.activeLabels($activeLabel);
1286
                    event.preventDefault();
1287
                  }
1288
                  else if(caretAtStart && !hasActiveLabel && pressedKey == keys.backspace) {
1289
                    module.verbose('Removing last label on input backspace');
1290
                    $activeLabel = $label.last().addClass(className.active);
1291
                    module.remove.activeLabels($activeLabel);
1292
                  }
1293
                }
1294
                else {
1295
                  $activeLabel.removeClass(className.active);
1296
                }
1297
              }
1298
            }
1299
          },
1300
1301
          keydown: function(event) {
1302
            var
1303
              pressedKey    = event.which,
1304
              isShortcutKey = module.is.inObject(pressedKey, keys)
1305
            ;
1306
            if(isShortcutKey) {
1307
              var
1308
                $currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0),
1309
                $activeItem        = $menu.children('.' + className.active).eq(0),
1310
                $selectedItem      = ($currentlySelected.length > 0)
1311
                  ? $currentlySelected
1312
                  : $activeItem,
1313
                $visibleItems = ($selectedItem.length > 0)
1314
                  ? $selectedItem.siblings(':not(.' + className.filtered +')').addBack()
1315
                  : $menu.children(':not(.' + className.filtered +')'),
1316
                $subMenu              = $selectedItem.children(selector.menu),
1317
                $parentMenu           = $selectedItem.closest(selector.menu),
1318
                inVisibleMenu         = ($parentMenu.hasClass(className.visible) || $parentMenu.hasClass(className.animating) || $parentMenu.parent(selector.menu).length > 0),
1319
                hasSubMenu            = ($subMenu.length> 0),
1320
                hasSelectedItem       = ($selectedItem.length > 0),
1321
                selectedIsSelectable  = ($selectedItem.not(selector.unselectable).length > 0),
1322
                delimiterPressed      = (pressedKey == keys.delimiter && settings.allowAdditions && module.is.multiple()),
1323
                isAdditionWithoutMenu = (settings.allowAdditions && settings.hideAdditions && (pressedKey == keys.enter || delimiterPressed) && selectedIsSelectable),
1324
                $nextItem,
1325
                isSubMenuItem,
1326
                newIndex
0 ignored issues
show
Unused Code introduced by
The variable newIndex seems to be never used. Consider removing it.
Loading history...
1327
              ;
1328
              // allow selection with menu closed
1329
              if(isAdditionWithoutMenu) {
1330
                module.verbose('Selecting item from keyboard shortcut', $selectedItem);
1331
                module.event.item.click.call($selectedItem, event);
1332
                if(module.is.searchSelection()) {
1333
                  module.remove.searchTerm();
1334
                }
1335
              }
1336
1337
              // visible menu keyboard shortcuts
1338
              if( module.is.visible() ) {
1339
1340
                // enter (select or open sub-menu)
1341
                if(pressedKey == keys.enter || delimiterPressed) {
1342
                  if(pressedKey == keys.enter && hasSelectedItem && hasSubMenu && !settings.allowCategorySelection) {
1343
                    module.verbose('Pressed enter on unselectable category, opening sub menu');
1344
                    pressedKey = keys.rightArrow;
1345
                  }
1346
                  else if(selectedIsSelectable) {
1347
                    module.verbose('Selecting item from keyboard shortcut', $selectedItem);
1348
                    module.event.item.click.call($selectedItem, event);
1349
                    if(module.is.searchSelection()) {
1350
                      module.remove.searchTerm();
1351
                    }
1352
                  }
1353
                  event.preventDefault();
1354
                }
1355
1356
                // sub-menu actions
1357
                if(hasSelectedItem) {
1358
1359
                  if(pressedKey == keys.leftArrow) {
1360
1361
                    isSubMenuItem = ($parentMenu[0] !== $menu[0]);
1362
1363
                    if(isSubMenuItem) {
1364
                      module.verbose('Left key pressed, closing sub-menu');
1365
                      module.animate.hide(false, $parentMenu);
1366
                      $selectedItem
1367
                        .removeClass(className.selected)
1368
                      ;
1369
                      $parentMenu
1370
                        .closest(selector.item)
1371
                          .addClass(className.selected)
1372
                      ;
1373
                      event.preventDefault();
1374
                    }
1375
                  }
1376
1377
                  // right arrow (show sub-menu)
1378
                  if(pressedKey == keys.rightArrow) {
1379
                    if(hasSubMenu) {
1380
                      module.verbose('Right key pressed, opening sub-menu');
1381
                      module.animate.show(false, $subMenu);
1382
                      $selectedItem
1383
                        .removeClass(className.selected)
1384
                      ;
1385
                      $subMenu
1386
                        .find(selector.item).eq(0)
1387
                          .addClass(className.selected)
1388
                      ;
1389
                      event.preventDefault();
1390
                    }
1391
                  }
1392
                }
1393
1394
                // up arrow (traverse menu up)
1395
                if(pressedKey == keys.upArrow) {
1396
                  $nextItem = (hasSelectedItem && inVisibleMenu)
1397
                    ? $selectedItem.prevAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
1398
                    : $item.eq(0)
1399
                  ;
1400
                  if($visibleItems.index( $nextItem ) < 0) {
1401
                    module.verbose('Up key pressed but reached top of current menu');
1402
                    event.preventDefault();
1403
                    return;
1404
                  }
1405
                  else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1406
                    module.verbose('Up key pressed, changing active item');
1407
                    $selectedItem
1408
                      .removeClass(className.selected)
1409
                    ;
1410
                    $nextItem
1411
                      .addClass(className.selected)
1412
                    ;
1413
                    module.set.scrollPosition($nextItem);
1414
                    if(settings.selectOnKeydown && module.is.single()) {
1415
                      module.set.selectedItem($nextItem);
1416
                    }
1417
                  }
1418
                  event.preventDefault();
1419
                }
1420
1421
                // down arrow (traverse menu down)
1422
                if(pressedKey == keys.downArrow) {
1423
                  $nextItem = (hasSelectedItem && inVisibleMenu)
1424
                    ? $nextItem = $selectedItem.nextAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
0 ignored issues
show
Unused Code introduced by
The assignment to variable $nextItem seems to be never used. Consider removing it.
Loading history...
1425
                    : $item.eq(0)
1426
                  ;
1427
                  if($nextItem.length === 0) {
1428
                    module.verbose('Down key pressed but reached bottom of current menu');
1429
                    event.preventDefault();
1430
                    return;
1431
                  }
1432
                  else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1433
                    module.verbose('Down key pressed, changing active item');
1434
                    $item
1435
                      .removeClass(className.selected)
1436
                    ;
1437
                    $nextItem
1438
                      .addClass(className.selected)
1439
                    ;
1440
                    module.set.scrollPosition($nextItem);
1441
                    if(settings.selectOnKeydown && module.is.single()) {
1442
                      module.set.selectedItem($nextItem);
1443
                    }
1444
                  }
1445
                  event.preventDefault();
1446
                }
1447
1448
                // page down (show next page)
1449
                if(pressedKey == keys.pageUp) {
1450
                  module.scrollPage('up');
1451
                  event.preventDefault();
1452
                }
1453
                if(pressedKey == keys.pageDown) {
1454
                  module.scrollPage('down');
1455
                  event.preventDefault();
1456
                }
1457
1458
                // escape (close menu)
1459
                if(pressedKey == keys.escape) {
1460
                  module.verbose('Escape key pressed, closing dropdown');
1461
                  module.hide();
1462
                }
1463
1464
              }
1465
              else {
1466
                // delimiter key
1467
                if(delimiterPressed) {
1468
                  event.preventDefault();
1469
                }
1470
                // down arrow (open menu)
1471
                if(pressedKey == keys.downArrow && !module.is.visible()) {
1472
                  module.verbose('Down key pressed, showing dropdown');
1473
                  module.show();
1474
                  event.preventDefault();
1475
                }
1476
              }
1477
            }
1478
            else {
1479
              if( !module.has.search() ) {
1480
                module.set.selectedLetter( String.fromCharCode(pressedKey) );
1481
              }
1482
            }
1483
          }
1484
        },
1485
1486
        trigger: {
1487
          change: function() {
1488
            var
1489
              events       = document.createEvent('HTMLEvents'),
1490
              inputElement = $input[0]
1491
            ;
1492
            if(inputElement) {
1493
              module.verbose('Triggering native change event');
1494
              events.initEvent('change', true, false);
1495
              inputElement.dispatchEvent(events);
1496
            }
1497
          }
1498
        },
1499
1500
        determine: {
1501
          selectAction: function(text, value) {
1502
            module.verbose('Determining action', settings.action);
1503
            if( $.isFunction( module.action[settings.action] ) ) {
1504
              module.verbose('Triggering preset action', settings.action, text, value);
1505
              module.action[ settings.action ].call(element, text, value, this);
1506
            }
1507
            else if( $.isFunction(settings.action) ) {
1508
              module.verbose('Triggering user action', settings.action, text, value);
1509
              settings.action.call(element, text, value, this);
1510
            }
1511
            else {
1512
              module.error(error.action, settings.action);
1513
            }
1514
          },
1515
          eventInModule: function(event, callback) {
1516
            var
1517
              $target    = $(event.target),
1518
              inDocument = ($target.closest(document.documentElement).length > 0),
1519
              inModule   = ($target.closest($module).length > 0)
1520
            ;
1521
            callback = $.isFunction(callback)
1522
              ? callback
1523
              : function(){}
1524
            ;
1525
            if(inDocument && !inModule) {
1526
              module.verbose('Triggering event', callback);
1527
              callback();
1528
              return true;
1529
            }
1530
            else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1531
              module.verbose('Event occurred in dropdown, canceling callback');
1532
              return false;
1533
            }
1534
          },
1535
          eventOnElement: function(event, callback) {
1536
            var
1537
              $target      = $(event.target),
1538
              $label       = $target.closest(selector.siblingLabel),
1539
              inVisibleDOM = document.body.contains(event.target),
1540
              notOnLabel   = ($module.find($label).length === 0),
1541
              notInMenu    = ($target.closest($menu).length === 0)
1542
            ;
1543
            callback = $.isFunction(callback)
1544
              ? callback
1545
              : function(){}
1546
            ;
1547
            if(inVisibleDOM && notOnLabel && notInMenu) {
1548
              module.verbose('Triggering event', callback);
1549
              callback();
1550
              return true;
1551
            }
1552
            else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1553
              module.verbose('Event occurred in dropdown menu, canceling callback');
1554
              return false;
1555
            }
1556
          }
1557
        },
1558
1559
        action: {
1560
1561
          nothing: function() {},
1562
1563
          activate: function(text, value, element) {
1564
            value = (value !== undefined)
1565
              ? value
1566
              : text
1567
            ;
1568
            if( module.can.activate( $(element) ) ) {
1569
              module.set.selected(value, $(element));
1570
              if(module.is.multiple() && !module.is.allFiltered()) {
1571
                return;
0 ignored issues
show
Unused Code introduced by
This return has no effect and can be removed.
Loading history...
1572
              }
1573
              else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1574
                module.hideAndClear();
1575
              }
1576
            }
1577
          },
1578
1579
          select: function(text, value, element) {
1580
            value = (value !== undefined)
1581
              ? value
1582
              : text
1583
            ;
1584
            if( module.can.activate( $(element) ) ) {
1585
              module.set.value(value, $(element));
1586
              if(module.is.multiple() && !module.is.allFiltered()) {
1587
                return;
0 ignored issues
show
Unused Code introduced by
This return has no effect and can be removed.
Loading history...
1588
              }
1589
              else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1590
                module.hideAndClear();
1591
              }
1592
            }
1593
          },
1594
1595
          combo: function(text, value, element) {
1596
            value = (value !== undefined)
1597
              ? value
1598
              : text
1599
            ;
1600
            module.set.selected(value, $(element));
1601
            module.hideAndClear();
1602
          },
1603
1604
          hide: function(text, value, element) {
0 ignored issues
show
Unused Code introduced by
The parameter element is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1605
            module.set.value(value, text);
1606
            module.hideAndClear();
1607
          }
1608
1609
        },
1610
1611
        get: {
1612
          id: function() {
1613
            return id;
1614
          },
1615
          defaultText: function() {
1616
            return $module.data(metadata.defaultText);
1617
          },
1618
          defaultValue: function() {
1619
            return $module.data(metadata.defaultValue);
1620
          },
1621
          placeholderText: function() {
1622
            return $module.data(metadata.placeholderText) || '';
1623
          },
1624
          text: function() {
1625
            return $text.text();
1626
          },
1627
          query: function() {
1628
            return $.trim($search.val());
1629
          },
1630
          searchWidth: function(value) {
1631
            value = (value !== undefined)
1632
              ? value
1633
              : $search.val()
1634
            ;
1635
            $sizer.text(value);
1636
            // prevent rounding issues
1637
            return Math.ceil( $sizer.width() + 1);
1638
          },
1639
          selectionCount: function() {
1640
            var
1641
              values = module.get.values(),
1642
              count
1643
            ;
1644
            count = ( module.is.multiple() )
1645
              ? $.isArray(values)
1646
                ? values.length
1647
                : 0
1648
              : (module.get.value() !== '')
1649
                ? 1
1650
                : 0
1651
            ;
1652
            return count;
1653
          },
1654
          transition: function($subMenu) {
1655
            return (settings.transition == 'auto')
1656
              ? module.is.upward($subMenu)
1657
                ? 'slide up'
1658
                : 'slide down'
1659
              : settings.transition
1660
            ;
1661
          },
1662
          userValues: function() {
1663
            var
1664
              values = module.get.values()
1665
            ;
1666
            if(!values) {
1667
              return false;
1668
            }
1669
            values = $.isArray(values)
1670
              ? values
1671
              : [values]
1672
            ;
1673
            return $.grep(values, function(value) {
1674
              return (module.get.item(value) === false);
1675
            });
1676
          },
1677
          uniqueArray: function(array) {
1678
            return $.grep(array, function (value, index) {
1679
                return $.inArray(value, array) === index;
1680
            });
1681
          },
1682
          caretPosition: function() {
1683
            var
1684
              input = $search.get(0),
1685
              range,
1686
              rangeLength
1687
            ;
1688
            if('selectionStart' in input) {
1689
              return input.selectionStart;
1690
            }
1691
            else if (document.selection) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if document.selection is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
1692
              input.focus();
1693
              range       = document.selection.createRange();
1694
              rangeLength = range.text.length;
1695
              range.moveStart('character', -input.value.length);
1696
              return range.text.length - rangeLength;
1697
            }
1698
          },
1699
          value: function() {
1700
            var
1701
              value = ($input.length > 0)
1702
                ? $input.val()
1703
                : $module.data(metadata.value),
1704
              isEmptyMultiselect = ($.isArray(value) && value.length === 1 && value[0] === '')
1705
            ;
1706
            // prevents placeholder element from being selected when multiple
1707
            return (value === undefined || isEmptyMultiselect)
1708
              ? ''
1709
              : value
1710
            ;
1711
          },
1712
          values: function() {
1713
            var
1714
              value = module.get.value()
1715
            ;
1716
            if(value === '') {
1717
              return '';
1718
            }
1719
            return ( !module.has.selectInput() && module.is.multiple() )
1720
              ? (typeof value == 'string') // delimited string
1721
                ? value.split(settings.delimiter)
1722
                : ''
1723
              : value
1724
            ;
1725
          },
1726
          remoteValues: function() {
1727
            var
1728
              values = module.get.values(),
1729
              remoteValues = false
1730
            ;
1731
            if(values) {
1732
              if(typeof values == 'string') {
1733
                values = [values];
1734
              }
1735
              $.each(values, function(index, value) {
1736
                var
1737
                  name = module.read.remoteData(value)
1738
                ;
1739
                module.verbose('Restoring value from session data', name, value);
1740
                if(name) {
1741
                  if(!remoteValues) {
1742
                    remoteValues = {};
1743
                  }
1744
                  remoteValues[value] = name;
1745
                }
1746
              });
1747
            }
1748
            return remoteValues;
1749
          },
1750
          choiceText: function($choice, preserveHTML) {
1751
            preserveHTML = (preserveHTML !== undefined)
1752
              ? preserveHTML
1753
              : settings.preserveHTML
1754
            ;
1755
            if($choice) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if $choice is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
1756
              if($choice.find(selector.menu).length > 0) {
1757
                module.verbose('Retrieving text of element with sub-menu');
1758
                $choice = $choice.clone();
1759
                $choice.find(selector.menu).remove();
1760
                $choice.find(selector.menuIcon).remove();
1761
              }
1762
              return ($choice.data(metadata.text) !== undefined)
1763
                ? $choice.data(metadata.text)
1764
                : (preserveHTML)
1765
                  ? $.trim($choice.html())
1766
                  : $.trim($choice.text())
1767
              ;
1768
            }
1769
          },
1770
          choiceValue: function($choice, choiceText) {
1771
            choiceText = choiceText || module.get.choiceText($choice);
1772
            if(!$choice) {
1773
              return false;
1774
            }
1775
            return ($choice.data(metadata.value) !== undefined)
1776
              ? String( $choice.data(metadata.value) )
1777
              : (typeof choiceText === 'string')
1778
                ? $.trim(choiceText.toLowerCase())
1779
                : String(choiceText)
1780
            ;
1781
          },
1782
          inputEvent: function() {
1783
            var
1784
              input = $search[0]
1785
            ;
1786
            if(input) {
1787
              return (input.oninput !== undefined)
1788
                ? 'input'
1789
                : (input.onpropertychange !== undefined)
1790
                  ? 'propertychange'
1791
                  : 'keyup'
1792
              ;
1793
            }
1794
            return false;
1795
          },
1796
          selectValues: function() {
1797
            var
1798
              select = {}
1799
            ;
1800
            select.values = [];
1801
            $module
1802
              .find('option')
1803
                .each(function() {
1804
                  var
1805
                    $option  = $(this),
1806
                    name     = $option.html(),
1807
                    disabled = $option.attr('disabled'),
1808
                    value    = ( $option.attr('value') !== undefined )
1809
                      ? $option.attr('value')
1810
                      : name
1811
                  ;
1812
                  if(settings.placeholder === 'auto' && value === '') {
1813
                    select.placeholder = name;
1814
                  }
1815
                  else {
1816
                    select.values.push({
1817
                      name     : name,
1818
                      value    : value,
1819
                      disabled : disabled
1820
                    });
1821
                  }
1822
                })
1823
            ;
1824
            if(settings.placeholder && settings.placeholder !== 'auto') {
1825
              module.debug('Setting placeholder value to', settings.placeholder);
1826
              select.placeholder = settings.placeholder;
1827
            }
1828
            if(settings.sortSelect) {
1829
              select.values.sort(function(a, b) {
1830
                return (a.name > b.name)
1831
                  ? 1
1832
                  : -1
1833
                ;
1834
              });
1835
              module.debug('Retrieved and sorted values from select', select);
1836
            }
1837
            else {
1838
              module.debug('Retrieved values from select', select);
1839
            }
1840
            return select;
1841
          },
1842
          activeItem: function() {
1843
            return $item.filter('.'  + className.active);
1844
          },
1845
          selectedItem: function() {
1846
            var
1847
              $selectedItem = $item.not(selector.unselectable).filter('.'  + className.selected)
1848
            ;
1849
            return ($selectedItem.length > 0)
1850
              ? $selectedItem
1851
              : $item.eq(0)
1852
            ;
1853
          },
1854
          itemWithAdditions: function(value) {
1855
            var
1856
              $items       = module.get.item(value),
1857
              $userItems   = module.create.userChoice(value),
1858
              hasUserItems = ($userItems && $userItems.length > 0)
1859
            ;
1860
            if(hasUserItems) {
1861
              $items = ($items.length > 0)
1862
                ? $items.add($userItems)
1863
                : $userItems
1864
              ;
1865
            }
1866
            return $items;
1867
          },
1868
          item: function(value, strict) {
1869
            var
1870
              $selectedItem = false,
1871
              shouldSearch,
1872
              isMultiple
1873
            ;
1874
            value = (value !== undefined)
1875
              ? value
1876
              : ( module.get.values() !== undefined)
1877
                ? module.get.values()
1878
                : module.get.text()
1879
            ;
1880
            shouldSearch = (isMultiple)
0 ignored issues
show
Bug introduced by
The variable isMultiple seems to be never initialized.
Loading history...
1881
              ? (value.length > 0)
1882
              : (value !== undefined && value !== null)
1883
            ;
1884
            isMultiple = (module.is.multiple() && $.isArray(value));
1885
            strict     = (value === '' || value === 0)
1886
              ? true
1887
              : strict || false
1888
            ;
1889
            if(shouldSearch) {
1890
              $item
1891
                .each(function() {
1892
                  var
1893
                    $choice       = $(this),
1894
                    optionText    = module.get.choiceText($choice),
1895
                    optionValue   = module.get.choiceValue($choice, optionText)
1896
                  ;
1897
                  // safe early exit
1898
                  if(optionValue === null || optionValue === undefined) {
1899
                    return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
1900
                  }
1901
                  if(isMultiple) {
1902
                    if($.inArray( String(optionValue), value) !== -1 || $.inArray(optionText, value) !== -1) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if $.inArray(String(optionV...tionText, value) !== -1 is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
1903
                      $selectedItem = ($selectedItem)
1904
                        ? $selectedItem.add($choice)
1905
                        : $choice
1906
                      ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1907
                    }
1908
                  }
1909
                  else if(strict) {
1910
                    module.verbose('Ambiguous dropdown value using strict type check', $choice, value);
1911
                    if( optionValue === value || optionText === value) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if optionValue === value || optionText === value is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
1912
                      $selectedItem = $choice;
1913
                      return true;
1914
                    }
1915
                  }
1916
                  else {
1917
                    if( String(optionValue) == String(value) || optionText == value) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if String(optionValue) == S... || optionText == value is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
1918
                      module.verbose('Found select item by value', optionValue, value);
1919
                      $selectedItem = $choice;
1920
                      return true;
1921
                    }
1922
                  }
1923
                })
1924
              ;
1925
            }
1926
            return $selectedItem;
1927
          }
1928
        },
1929
1930
        check: {
1931
          maxSelections: function(selectionCount) {
1932
            if(settings.maxSelections) {
1933
              selectionCount = (selectionCount !== undefined)
1934
                ? selectionCount
1935
                : module.get.selectionCount()
1936
              ;
1937
              if(selectionCount >= settings.maxSelections) {
1938
                module.debug('Maximum selection count reached');
1939
                if(settings.useLabels) {
1940
                  $item.addClass(className.filtered);
1941
                  module.add.message(message.maxSelections);
1942
                }
1943
                return true;
1944
              }
1945
              else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1946
                module.verbose('No longer at maximum selection count');
1947
                module.remove.message();
1948
                module.remove.filteredItem();
1949
                if(module.is.searchSelection()) {
1950
                  module.filterItems();
1951
                }
1952
                return false;
1953
              }
1954
            }
1955
            return true;
1956
          }
1957
        },
1958
1959
        restore: {
1960
          defaults: function() {
1961
            module.clear();
1962
            module.restore.defaultText();
1963
            module.restore.defaultValue();
1964
          },
1965
          defaultText: function() {
1966
            var
1967
              defaultText     = module.get.defaultText(),
1968
              placeholderText = module.get.placeholderText
1969
            ;
1970
            if(defaultText === placeholderText) {
1971
              module.debug('Restoring default placeholder text', defaultText);
1972
              module.set.placeholderText(defaultText);
1973
            }
1974
            else {
1975
              module.debug('Restoring default text', defaultText);
1976
              module.set.text(defaultText);
1977
            }
1978
          },
1979
          placeholderText: function() {
1980
            module.set.placeholderText();
1981
          },
1982
          defaultValue: function() {
1983
            var
1984
              defaultValue = module.get.defaultValue()
1985
            ;
1986
            if(defaultValue !== undefined) {
1987
              module.debug('Restoring default value', defaultValue);
1988
              if(defaultValue !== '') {
1989
                module.set.value(defaultValue);
1990
                module.set.selected();
1991
              }
1992
              else {
1993
                module.remove.activeItem();
1994
                module.remove.selectedItem();
1995
              }
1996
            }
1997
          },
1998
          labels: function() {
1999
            if(settings.allowAdditions) {
2000
              if(!settings.useLabels) {
2001
                module.error(error.labels);
2002
                settings.useLabels = true;
2003
              }
2004
              module.debug('Restoring selected values');
2005
              module.create.userLabels();
2006
            }
2007
            module.check.maxSelections();
2008
          },
2009
          selected: function() {
2010
            module.restore.values();
2011
            if(module.is.multiple()) {
2012
              module.debug('Restoring previously selected values and labels');
2013
              module.restore.labels();
2014
            }
2015
            else {
2016
              module.debug('Restoring previously selected values');
2017
            }
2018
          },
2019
          values: function() {
2020
            // prevents callbacks from occurring on initial load
2021
            module.set.initialLoad();
2022
            if(settings.apiSettings && settings.saveRemoteData && module.get.remoteValues()) {
2023
              module.restore.remoteValues();
2024
            }
2025
            else {
2026
              module.set.selected();
2027
            }
2028
            module.remove.initialLoad();
2029
          },
2030
          remoteValues: function() {
2031
            var
2032
              values = module.get.remoteValues()
2033
            ;
2034
            module.debug('Recreating selected from session data', values);
2035
            if(values) {
2036
              if( module.is.single() ) {
2037
                $.each(values, function(value, name) {
2038
                  module.set.text(name);
2039
                });
2040
              }
2041
              else {
2042
                $.each(values, function(value, name) {
2043
                  module.add.label(value, name);
2044
                });
2045
              }
2046
            }
2047
          }
2048
        },
2049
2050
        read: {
2051
          remoteData: function(value) {
2052
            var
2053
              name
2054
            ;
2055
            if(window.Storage === undefined) {
2056
              module.error(error.noStorage);
2057
              return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
2058
            }
2059
            name = sessionStorage.getItem(value);
0 ignored issues
show
Bug introduced by
The variable sessionStorage seems to be never declared. If this is a global, consider adding a /** global: sessionStorage */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2060
            return (name !== undefined)
2061
              ? name
2062
              : false
2063
            ;
2064
          }
2065
        },
2066
2067
        save: {
2068
          defaults: function() {
2069
            module.save.defaultText();
2070
            module.save.placeholderText();
2071
            module.save.defaultValue();
2072
          },
2073
          defaultValue: function() {
2074
            var
2075
              value = module.get.value()
2076
            ;
2077
            module.verbose('Saving default value as', value);
2078
            $module.data(metadata.defaultValue, value);
2079
          },
2080
          defaultText: function() {
2081
            var
2082
              text = module.get.text()
2083
            ;
2084
            module.verbose('Saving default text as', text);
2085
            $module.data(metadata.defaultText, text);
2086
          },
2087
          placeholderText: function() {
2088
            var
2089
              text
2090
            ;
2091
            if(settings.placeholder !== false && $text.hasClass(className.placeholder)) {
2092
              text = module.get.text();
2093
              module.verbose('Saving placeholder text as', text);
2094
              $module.data(metadata.placeholderText, text);
2095
            }
2096
          },
2097
          remoteData: function(name, value) {
2098
            if(window.Storage === undefined) {
2099
              module.error(error.noStorage);
2100
              return;
2101
            }
2102
            module.verbose('Saving remote data to session storage', value, name);
2103
            sessionStorage.setItem(value, name);
0 ignored issues
show
Bug introduced by
The variable sessionStorage seems to be never declared. If this is a global, consider adding a /** global: sessionStorage */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2104
          }
2105
        },
2106
2107
        clear: function() {
2108
          if(module.is.multiple() && settings.useLabels) {
2109
            module.remove.labels();
2110
          }
2111
          else {
2112
            module.remove.activeItem();
2113
            module.remove.selectedItem();
2114
          }
2115
          module.set.placeholderText();
2116
          module.clearValue();
2117
        },
2118
2119
        clearValue: function() {
2120
          module.set.value('');
2121
        },
2122
2123
        scrollPage: function(direction, $selectedItem) {
2124
          var
2125
            $currentItem  = $selectedItem || module.get.selectedItem(),
2126
            $menu         = $currentItem.closest(selector.menu),
2127
            menuHeight    = $menu.outerHeight(),
2128
            currentScroll = $menu.scrollTop(),
2129
            itemHeight    = $item.eq(0).outerHeight(),
2130
            itemsPerPage  = Math.floor(menuHeight / itemHeight),
2131
            maxScroll     = $menu.prop('scrollHeight'),
0 ignored issues
show
Unused Code introduced by
The variable maxScroll seems to be never used. Consider removing it.
Loading history...
2132
            newScroll     = (direction == 'up')
2133
              ? currentScroll - (itemHeight * itemsPerPage)
2134
              : currentScroll + (itemHeight * itemsPerPage),
2135
            $selectableItem = $item.not(selector.unselectable),
2136
            isWithinRange,
2137
            $nextSelectedItem,
2138
            elementIndex
2139
          ;
2140
          elementIndex      = (direction == 'up')
2141
            ? $selectableItem.index($currentItem) - itemsPerPage
2142
            : $selectableItem.index($currentItem) + itemsPerPage
2143
          ;
2144
          isWithinRange = (direction == 'up')
2145
            ? (elementIndex >= 0)
2146
            : (elementIndex < $selectableItem.length)
2147
          ;
2148
          $nextSelectedItem = (isWithinRange)
2149
            ? $selectableItem.eq(elementIndex)
2150
            : (direction == 'up')
2151
              ? $selectableItem.first()
2152
              : $selectableItem.last()
2153
          ;
2154
          if($nextSelectedItem.length > 0) {
2155
            module.debug('Scrolling page', direction, $nextSelectedItem);
2156
            $currentItem
2157
              .removeClass(className.selected)
2158
            ;
2159
            $nextSelectedItem
2160
              .addClass(className.selected)
2161
            ;
2162
            if(settings.selectOnKeydown && module.is.single()) {
2163
              module.set.selectedItem($nextSelectedItem);
2164
            }
2165
            $menu
2166
              .scrollTop(newScroll)
2167
            ;
2168
          }
2169
        },
2170
2171
        set: {
2172
          filtered: function() {
2173
            var
2174
              isMultiple       = module.is.multiple(),
2175
              isSearch         = module.is.searchSelection(),
2176
              isSearchMultiple = (isMultiple && isSearch),
2177
              searchValue      = (isSearch)
2178
                ? module.get.query()
2179
                : '',
2180
              hasSearchValue   = (typeof searchValue === 'string' && searchValue.length > 0),
2181
              searchWidth      = module.get.searchWidth(),
2182
              valueIsSet       = searchValue !== ''
2183
            ;
2184
            if(isMultiple && hasSearchValue) {
2185
              module.verbose('Adjusting input width', searchWidth, settings.glyphWidth);
2186
              $search.css('width', searchWidth);
2187
            }
2188
            if(hasSearchValue || (isSearchMultiple && valueIsSet)) {
2189
              module.verbose('Hiding placeholder text');
2190
              $text.addClass(className.filtered);
2191
            }
2192
            else if(!isMultiple || (isSearchMultiple && !valueIsSet)) {
2193
              module.verbose('Showing placeholder text');
2194
              $text.removeClass(className.filtered);
2195
            }
2196
          },
2197
          empty: function() {
2198
            $module.addClass(className.empty);
2199
          },
2200
          loading: function() {
2201
            $module.addClass(className.loading);
2202
          },
2203
          placeholderText: function(text) {
2204
            text = text || module.get.placeholderText();
2205
            module.debug('Setting placeholder text', text);
2206
            module.set.text(text);
2207
            $text.addClass(className.placeholder);
2208
          },
2209
          tabbable: function() {
2210
            if( module.is.searchSelection() ) {
2211
              module.debug('Added tabindex to searchable dropdown');
2212
              $search
2213
                .val('')
2214
                .attr('tabindex', 0)
2215
              ;
2216
              $menu
2217
                .attr('tabindex', -1)
2218
              ;
2219
            }
2220
            else {
2221
              module.debug('Added tabindex to dropdown');
2222
              if( $module.attr('tabindex') === undefined) {
2223
                $module
2224
                  .attr('tabindex', 0)
2225
                ;
2226
                $menu
2227
                  .attr('tabindex', -1)
2228
                ;
2229
              }
2230
            }
2231
          },
2232
          initialLoad: function() {
2233
            module.verbose('Setting initial load');
2234
            initialLoad = true;
2235
          },
2236
          activeItem: function($item) {
2237
            if( settings.allowAdditions && $item.filter(selector.addition).length > 0 ) {
2238
              $item.addClass(className.filtered);
2239
            }
2240
            else {
2241
              $item.addClass(className.active);
2242
            }
2243
          },
2244
          partialSearch: function(text) {
2245
            var
2246
              length = module.get.query().length
2247
            ;
2248
            $search.val( text.substr(0 , length));
2249
          },
2250
          scrollPosition: function($item, forceScroll) {
2251
            var
2252
              edgeTolerance = 5,
2253
              $menu,
2254
              hasActive,
2255
              offset,
2256
              itemHeight,
0 ignored issues
show
Unused Code introduced by
The variable itemHeight seems to be never used. Consider removing it.
Loading history...
2257
              itemOffset,
2258
              menuOffset,
2259
              menuScroll,
2260
              menuHeight,
2261
              abovePage,
2262
              belowPage
2263
            ;
2264
2265
            $item       = $item || module.get.selectedItem();
2266
            $menu       = $item.closest(selector.menu);
2267
            hasActive   = ($item && $item.length > 0);
2268
            forceScroll = (forceScroll !== undefined)
2269
              ? forceScroll
2270
              : false
2271
            ;
2272
            if($item && $menu.length > 0 && hasActive) {
2273
              itemOffset = $item.position().top;
0 ignored issues
show
Unused Code introduced by
The assignment to variable itemOffset seems to be never used. Consider removing it.
Loading history...
2274
2275
              $menu.addClass(className.loading);
2276
              menuScroll = $menu.scrollTop();
2277
              menuOffset = $menu.offset().top;
2278
              itemOffset = $item.offset().top;
2279
              offset     = menuScroll - menuOffset + itemOffset;
2280
              if(!forceScroll) {
2281
                menuHeight = $menu.height();
2282
                belowPage  = menuScroll + menuHeight < (offset + edgeTolerance);
2283
                abovePage  = ((offset - edgeTolerance) < menuScroll);
2284
              }
2285
              module.debug('Scrolling to active item', offset);
2286
              if(forceScroll || abovePage || belowPage) {
2287
                $menu.scrollTop(offset);
2288
              }
2289
              $menu.removeClass(className.loading);
2290
            }
2291
          },
2292
          text: function(text) {
2293
            if(settings.action !== 'select') {
2294
              if(settings.action == 'combo') {
2295
                module.debug('Changing combo button text', text, $combo);
2296
                if(settings.preserveHTML) {
2297
                  $combo.html(text);
2298
                }
2299
                else {
2300
                  $combo.text(text);
2301
                }
2302
              }
2303
              else {
2304
                if(text !== module.get.placeholderText()) {
2305
                  $text.removeClass(className.placeholder);
2306
                }
2307
                module.debug('Changing text', text, $text);
2308
                $text
2309
                  .removeClass(className.filtered)
2310
                ;
2311
                if(settings.preserveHTML) {
2312
                  $text.html(text);
2313
                }
2314
                else {
2315
                  $text.text(text);
2316
                }
2317
              }
2318
            }
2319
          },
2320
          selectedItem: function($item) {
2321
            var
2322
              value      = module.get.choiceValue($item),
2323
              searchText = module.get.choiceText($item, false),
2324
              text       = module.get.choiceText($item, true)
2325
            ;
2326
            module.debug('Setting user selection to item', $item);
2327
            module.remove.activeItem();
2328
            module.set.partialSearch(searchText);
2329
            module.set.activeItem($item);
2330
            module.set.selected(value, $item);
2331
            module.set.text(text);
2332
          },
2333
          selectedLetter: function(letter) {
2334
            var
2335
              $selectedItem         = $item.filter('.' + className.selected),
2336
              alreadySelectedLetter = $selectedItem.length > 0 && module.has.firstLetter($selectedItem, letter),
2337
              $nextValue            = false,
2338
              $nextItem
2339
            ;
2340
            // check next of same letter
2341
            if(alreadySelectedLetter) {
2342
              $nextItem = $selectedItem.nextAll($item).eq(0);
2343
              if( module.has.firstLetter($nextItem, letter) ) {
2344
                $nextValue  = $nextItem;
2345
              }
2346
            }
2347
            // check all values
2348
            if(!$nextValue) {
2349
              $item
2350
                .each(function(){
2351
                  if(module.has.firstLetter($(this), letter)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if module.has.firstLetter($(this), letter) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
2352
                    $nextValue = $(this);
2353
                    return false;
2354
                  }
2355
                })
2356
              ;
2357
            }
2358
            // set next value
2359
            if($nextValue) {
2360
              module.verbose('Scrolling to next value with letter', letter);
2361
              module.set.scrollPosition($nextValue);
2362
              $selectedItem.removeClass(className.selected);
2363
              $nextValue.addClass(className.selected);
2364
              if(settings.selectOnKeydown && module.is.single()) {
2365
                module.set.selectedItem($nextValue);
2366
              }
2367
            }
2368
          },
2369
          direction: function($menu) {
2370
            if(settings.direction == 'auto') {
2371
              // reset position
2372
              module.remove.upward();
2373
2374
              if(module.can.openDownward($menu)) {
2375
                module.remove.upward($menu);
2376
              }
2377
              else {
2378
                module.set.upward($menu);
2379
              }
2380
              if(!module.is.leftward($menu) && !module.can.openRightward($menu)) {
2381
                module.set.leftward($menu);
2382
              }
2383
            }
2384
            else if(settings.direction == 'upward') {
2385
              module.set.upward($menu);
2386
            }
2387
          },
2388
          upward: function($currentMenu) {
2389
            var $element = $currentMenu || $module;
2390
            $element.addClass(className.upward);
2391
          },
2392
          leftward: function($currentMenu) {
2393
            var $element = $currentMenu || $menu;
2394
            $element.addClass(className.leftward);
2395
          },
2396
          value: function(value, text, $selected) {
2397
            var
2398
              escapedValue = module.escape.value(value),
2399
              hasInput     = ($input.length > 0),
2400
              isAddition   = !module.has.value(value),
0 ignored issues
show
Unused Code introduced by
The variable isAddition seems to be never used. Consider removing it.
Loading history...
2401
              currentValue = module.get.values(),
2402
              stringValue  = (value !== undefined)
2403
                ? String(value)
2404
                : value,
2405
              newValue
0 ignored issues
show
Unused Code introduced by
The variable newValue seems to be never used. Consider removing it.
Loading history...
2406
            ;
2407
            if(hasInput) {
2408
              if(!settings.allowReselection && stringValue == currentValue) {
2409
                module.verbose('Skipping value update already same value', value, currentValue);
2410
                if(!module.is.initialLoad()) {
2411
                  return;
2412
                }
2413
              }
2414
2415
              if( module.is.single() && module.has.selectInput() && module.can.extendSelect() ) {
2416
                module.debug('Adding user option', value);
2417
                module.add.optionValue(value);
2418
              }
2419
              module.debug('Updating input value', escapedValue, currentValue);
2420
              internalChange = true;
2421
              $input
2422
                .val(escapedValue)
2423
              ;
2424
              if(settings.fireOnInit === false && module.is.initialLoad()) {
2425
                module.debug('Input native change event ignored on initial load');
2426
              }
2427
              else {
2428
                module.trigger.change();
2429
              }
2430
              internalChange = false;
2431
            }
2432
            else {
2433
              module.verbose('Storing value in metadata', escapedValue, $input);
2434
              if(escapedValue !== currentValue) {
2435
                $module.data(metadata.value, stringValue);
2436
              }
2437
            }
2438
            if(settings.fireOnInit === false && module.is.initialLoad()) {
2439
              module.verbose('No callback on initial load', settings.onChange);
2440
            }
2441
            else {
2442
              settings.onChange.call(element, value, text, $selected);
2443
            }
2444
          },
2445
          active: function() {
2446
            $module
2447
              .addClass(className.active)
2448
            ;
2449
          },
2450
          multiple: function() {
2451
            $module.addClass(className.multiple);
2452
          },
2453
          visible: function() {
2454
            $module.addClass(className.visible);
2455
          },
2456
          exactly: function(value, $selectedItem) {
2457
            module.debug('Setting selected to exact values');
2458
            module.clear();
2459
            module.set.selected(value, $selectedItem);
2460
          },
2461
          selected: function(value, $selectedItem) {
2462
            var
2463
              isMultiple = module.is.multiple(),
2464
              $userSelectedItem
0 ignored issues
show
Unused Code introduced by
The variable $userSelectedItem seems to be never used. Consider removing it.
Loading history...
2465
            ;
2466
            $selectedItem = (settings.allowAdditions)
2467
              ? $selectedItem || module.get.itemWithAdditions(value)
2468
              : $selectedItem || module.get.item(value)
2469
            ;
2470
            if(!$selectedItem) {
2471
              return;
2472
            }
2473
            module.debug('Setting selected menu item to', $selectedItem);
2474
            if(module.is.multiple()) {
2475
              module.remove.searchWidth();
2476
            }
2477
            if(module.is.single()) {
2478
              module.remove.activeItem();
2479
              module.remove.selectedItem();
2480
            }
2481
            else if(settings.useLabels) {
2482
              module.remove.selectedItem();
2483
            }
2484
            // select each item
2485
            $selectedItem
2486
              .each(function() {
2487
                var
2488
                  $selected      = $(this),
2489
                  selectedText   = module.get.choiceText($selected),
2490
                  selectedValue  = module.get.choiceValue($selected, selectedText),
2491
2492
                  isFiltered     = $selected.hasClass(className.filtered),
2493
                  isActive       = $selected.hasClass(className.active),
2494
                  isUserValue    = $selected.hasClass(className.addition),
2495
                  shouldAnimate  = (isMultiple && $selectedItem.length == 1)
0 ignored issues
show
Best Practice introduced by
Comparing $selectedItem.length to 1 using the == operator is not safe. Consider using === instead.
Loading history...
2496
                ;
2497
                if(isMultiple) {
2498
                  if(!isActive || isUserValue) {
2499
                    if(settings.apiSettings && settings.saveRemoteData) {
2500
                      module.save.remoteData(selectedText, selectedValue);
2501
                    }
2502
                    if(settings.useLabels) {
2503
                      module.add.value(selectedValue, selectedText, $selected);
2504
                      module.add.label(selectedValue, selectedText, shouldAnimate);
2505
                      module.set.activeItem($selected);
2506
                      module.filterActive();
2507
                      module.select.nextAvailable($selectedItem);
2508
                    }
2509
                    else {
2510
                      module.add.value(selectedValue, selectedText, $selected);
2511
                      module.set.text(module.add.variables(message.count));
2512
                      module.set.activeItem($selected);
2513
                    }
2514
                  }
2515
                  else if(!isFiltered) {
2516
                    module.debug('Selected active value, removing label');
2517
                    module.remove.selected(selectedValue);
2518
                  }
2519
                }
2520
                else {
2521
                  if(settings.apiSettings && settings.saveRemoteData) {
2522
                    module.save.remoteData(selectedText, selectedValue);
2523
                  }
2524
                  module.set.text(selectedText);
2525
                  module.set.value(selectedValue, selectedText, $selected);
2526
                  $selected
2527
                    .addClass(className.active)
2528
                    .addClass(className.selected)
2529
                  ;
2530
                }
2531
              })
2532
            ;
2533
          }
2534
        },
2535
2536
        add: {
2537
          label: function(value, text, shouldAnimate) {
2538
            var
2539
              $next  = module.is.searchSelection()
2540
                ? $search
2541
                : $text,
2542
              escapedValue = module.escape.value(value),
2543
              $label
2544
            ;
2545
            $label =  $('<a />')
2546
              .addClass(className.label)
2547
              .attr('data-' + metadata.value, escapedValue)
2548
              .html(templates.label(escapedValue, text))
2549
            ;
2550
            $label = settings.onLabelCreate.call($label, escapedValue, text);
2551
2552
            if(module.has.label(value)) {
2553
              module.debug('Label already exists, skipping', escapedValue);
2554
              return;
2555
            }
2556
            if(settings.label.variation) {
2557
              $label.addClass(settings.label.variation);
2558
            }
2559
            if(shouldAnimate === true) {
2560
              module.debug('Animating in label', $label);
2561
              $label
2562
                .addClass(className.hidden)
2563
                .insertBefore($next)
2564
                .transition(settings.label.transition, settings.label.duration)
2565
              ;
2566
            }
2567
            else {
2568
              module.debug('Adding selection label', $label);
2569
              $label
2570
                .insertBefore($next)
2571
              ;
2572
            }
2573
          },
2574
          message: function(message) {
2575
            var
2576
              $message = $menu.children(selector.message),
2577
              html     = settings.templates.message(module.add.variables(message))
2578
            ;
2579
            if($message.length > 0) {
2580
              $message
2581
                .html(html)
2582
              ;
2583
            }
2584
            else {
2585
              $message = $('<div/>')
0 ignored issues
show
Unused Code introduced by
The assignment to variable $message seems to be never used. Consider removing it.
Loading history...
2586
                .html(html)
2587
                .addClass(className.message)
2588
                .appendTo($menu)
2589
              ;
2590
            }
2591
          },
2592
          optionValue: function(value) {
2593
            var
2594
              escapedValue = module.escape.value(value),
2595
              $option      = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'),
2596
              hasOption    = ($option.length > 0)
2597
            ;
2598
            if(hasOption) {
2599
              return;
2600
            }
2601
            // temporarily disconnect observer
2602
            module.disconnect.selectObserver();
2603
            if( module.is.single() ) {
2604
              module.verbose('Removing previous user addition');
2605
              $input.find('option.' + className.addition).remove();
2606
            }
2607
            $('<option/>')
2608
              .prop('value', escapedValue)
2609
              .addClass(className.addition)
2610
              .html(value)
2611
              .appendTo($input)
2612
            ;
2613
            module.verbose('Adding user addition as an <option>', value);
2614
            module.observe.select();
2615
          },
2616
          userSuggestion: function(value) {
2617
            var
2618
              $addition         = $menu.children(selector.addition),
2619
              $existingItem     = module.get.item(value),
2620
              alreadyHasValue   = $existingItem && $existingItem.not(selector.addition).length,
2621
              hasUserSuggestion = $addition.length > 0,
2622
              html
2623
            ;
2624
            if(settings.useLabels && module.has.maxSelections()) {
2625
              return;
2626
            }
2627
            if(value === '' || alreadyHasValue) {
2628
              $addition.remove();
2629
              return;
2630
            }
2631
            if(hasUserSuggestion) {
2632
              $addition
2633
                .data(metadata.value, value)
2634
                .data(metadata.text, value)
2635
                .attr('data-' + metadata.value, value)
2636
                .attr('data-' + metadata.text, value)
2637
                .removeClass(className.filtered)
2638
              ;
2639
              if(!settings.hideAdditions) {
2640
                html = settings.templates.addition( module.add.variables(message.addResult, value) );
2641
                $addition
2642
                  .html(html)
2643
                ;
2644
              }
2645
              module.verbose('Replacing user suggestion with new value', $addition);
2646
            }
2647
            else {
2648
              $addition = module.create.userChoice(value);
2649
              $addition
2650
                .prependTo($menu)
2651
              ;
2652
              module.verbose('Adding item choice to menu corresponding with user choice addition', $addition);
2653
            }
2654
            if(!settings.hideAdditions || module.is.allFiltered()) {
2655
              $addition
2656
                .addClass(className.selected)
2657
                .siblings()
2658
                .removeClass(className.selected)
2659
              ;
2660
            }
2661
            module.refreshItems();
2662
          },
2663
          variables: function(message, term) {
2664
            var
2665
              hasCount    = (message.search('{count}') !== -1),
2666
              hasMaxCount = (message.search('{maxCount}') !== -1),
2667
              hasTerm     = (message.search('{term}') !== -1),
2668
              values,
0 ignored issues
show
Unused Code introduced by
The variable values seems to be never used. Consider removing it.
Loading history...
2669
              count,
2670
              query
2671
            ;
2672
            module.verbose('Adding templated variables to message', message);
2673
            if(hasCount) {
2674
              count  = module.get.selectionCount();
2675
              message = message.replace('{count}', count);
2676
            }
2677
            if(hasMaxCount) {
2678
              count  = module.get.selectionCount();
0 ignored issues
show
Unused Code introduced by
The assignment to variable count seems to be never used. Consider removing it.
Loading history...
2679
              message = message.replace('{maxCount}', settings.maxSelections);
2680
            }
2681
            if(hasTerm) {
2682
              query   = term || module.get.query();
2683
              message = message.replace('{term}', query);
2684
            }
2685
            return message;
2686
          },
2687
          value: function(addedValue, addedText, $selectedItem) {
2688
            var
2689
              currentValue = module.get.values(),
2690
              newValue
2691
            ;
2692
            if(addedValue === '') {
2693
              module.debug('Cannot select blank values from multiselect');
2694
              return;
2695
            }
2696
            // extend current array
2697
            if($.isArray(currentValue)) {
2698
              newValue = currentValue.concat([addedValue]);
2699
              newValue = module.get.uniqueArray(newValue);
2700
            }
2701
            else {
2702
              newValue = [addedValue];
2703
            }
2704
            // add values
2705
            if( module.has.selectInput() ) {
2706
              if(module.can.extendSelect()) {
2707
                module.debug('Adding value to select', addedValue, newValue, $input);
2708
                module.add.optionValue(addedValue);
2709
              }
2710
            }
2711
            else {
2712
              newValue = newValue.join(settings.delimiter);
2713
              module.debug('Setting hidden input to delimited value', newValue, $input);
2714
            }
2715
2716
            if(settings.fireOnInit === false && module.is.initialLoad()) {
2717
              module.verbose('Skipping onadd callback on initial load', settings.onAdd);
2718
            }
2719
            else {
2720
              settings.onAdd.call(element, addedValue, addedText, $selectedItem);
2721
            }
2722
            module.set.value(newValue, addedValue, addedText, $selectedItem);
2723
            module.check.maxSelections();
2724
          }
2725
        },
2726
2727
        remove: {
2728
          active: function() {
2729
            $module.removeClass(className.active);
2730
          },
2731
          activeLabel: function() {
2732
            $module.find(selector.label).removeClass(className.active);
2733
          },
2734
          empty: function() {
2735
            $module.removeClass(className.empty);
2736
          },
2737
          loading: function() {
2738
            $module.removeClass(className.loading);
2739
          },
2740
          initialLoad: function() {
2741
            initialLoad = false;
2742
          },
2743
          upward: function($currentMenu) {
2744
            var $element = $currentMenu || $module;
2745
            $element.removeClass(className.upward);
2746
          },
2747
          leftward: function($currentMenu) {
2748
            var $element = $currentMenu || $menu;
2749
            $element.removeClass(className.leftward);
2750
          },
2751
          visible: function() {
2752
            $module.removeClass(className.visible);
2753
          },
2754
          activeItem: function() {
2755
            $item.removeClass(className.active);
2756
          },
2757
          filteredItem: function() {
2758
            if(settings.useLabels && module.has.maxSelections() ) {
2759
              return;
2760
            }
2761
            if(settings.useLabels && module.is.multiple()) {
2762
              $item.not('.' + className.active).removeClass(className.filtered);
2763
            }
2764
            else {
2765
              $item.removeClass(className.filtered);
2766
            }
2767
            module.remove.empty();
2768
          },
2769
          optionValue: function(value) {
2770
            var
2771
              escapedValue = module.escape.value(value),
2772
              $option      = $input.find('option[value="' + module.escape.string(escapedValue) + '"]'),
2773
              hasOption    = ($option.length > 0)
2774
            ;
2775
            if(!hasOption || !$option.hasClass(className.addition)) {
2776
              return;
2777
            }
2778
            // temporarily disconnect observer
2779
            if(selectObserver) {
2780
              selectObserver.disconnect();
2781
              module.verbose('Temporarily disconnecting mutation observer');
2782
            }
2783
            $option.remove();
2784
            module.verbose('Removing user addition as an <option>', escapedValue);
2785
            if(selectObserver) {
2786
              selectObserver.observe($input[0], {
2787
                childList : true,
2788
                subtree   : true
2789
              });
2790
            }
2791
          },
2792
          message: function() {
2793
            $menu.children(selector.message).remove();
2794
          },
2795
          searchWidth: function() {
2796
            $search.css('width', '');
2797
          },
2798
          searchTerm: function() {
2799
            module.verbose('Cleared search term');
2800
            $search.val('');
2801
            module.set.filtered();
2802
          },
2803
          userAddition: function() {
2804
            $item.filter(selector.addition).remove();
2805
          },
2806
          selected: function(value, $selectedItem) {
2807
            $selectedItem = (settings.allowAdditions)
2808
              ? $selectedItem || module.get.itemWithAdditions(value)
2809
              : $selectedItem || module.get.item(value)
2810
            ;
2811
2812
            if(!$selectedItem) {
2813
              return false;
2814
            }
2815
2816
            $selectedItem
2817
              .each(function() {
2818
                var
2819
                  $selected     = $(this),
2820
                  selectedText  = module.get.choiceText($selected),
2821
                  selectedValue = module.get.choiceValue($selected, selectedText)
2822
                ;
2823
                if(module.is.multiple()) {
2824
                  if(settings.useLabels) {
2825
                    module.remove.value(selectedValue, selectedText, $selected);
2826
                    module.remove.label(selectedValue);
2827
                  }
2828
                  else {
2829
                    module.remove.value(selectedValue, selectedText, $selected);
2830
                    if(module.get.selectionCount() === 0) {
2831
                      module.set.placeholderText();
2832
                    }
2833
                    else {
2834
                      module.set.text(module.add.variables(message.count));
2835
                    }
2836
                  }
2837
                }
2838
                else {
2839
                  module.remove.value(selectedValue, selectedText, $selected);
2840
                }
2841
                $selected
2842
                  .removeClass(className.filtered)
2843
                  .removeClass(className.active)
2844
                ;
2845
                if(settings.useLabels) {
2846
                  $selected.removeClass(className.selected);
2847
                }
2848
              })
2849
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
2850
          },
2851
          selectedItem: function() {
2852
            $item.removeClass(className.selected);
2853
          },
2854
          value: function(removedValue, removedText, $removedItem) {
2855
            var
2856
              values = module.get.values(),
2857
              newValue
2858
            ;
2859
            if( module.has.selectInput() ) {
2860
              module.verbose('Input is <select> removing selected option', removedValue);
2861
              newValue = module.remove.arrayValue(removedValue, values);
2862
              module.remove.optionValue(removedValue);
2863
            }
2864
            else {
2865
              module.verbose('Removing from delimited values', removedValue);
2866
              newValue = module.remove.arrayValue(removedValue, values);
2867
              newValue = newValue.join(settings.delimiter);
2868
            }
2869
            if(settings.fireOnInit === false && module.is.initialLoad()) {
2870
              module.verbose('No callback on initial load', settings.onRemove);
2871
            }
2872
            else {
2873
              settings.onRemove.call(element, removedValue, removedText, $removedItem);
2874
            }
2875
            module.set.value(newValue, removedText, $removedItem);
2876
            module.check.maxSelections();
2877
          },
2878
          arrayValue: function(removedValue, values) {
2879
            if( !$.isArray(values) ) {
2880
              values = [values];
2881
            }
2882
            values = $.grep(values, function(value){
2883
              return (removedValue != value);
2884
            });
2885
            module.verbose('Removed value from delimited string', removedValue, values);
2886
            return values;
2887
          },
2888
          label: function(value, shouldAnimate) {
0 ignored issues
show
Unused Code introduced by
The parameter shouldAnimate is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
2889
            var
2890
              $labels       = $module.find(selector.label),
2891
              $removedLabel = $labels.filter('[data-' + metadata.value + '="' + module.escape.string(value) +'"]')
2892
            ;
2893
            module.verbose('Removing label', $removedLabel);
2894
            $removedLabel.remove();
2895
          },
2896
          activeLabels: function($activeLabels) {
2897
            $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active);
2898
            module.verbose('Removing active label selections', $activeLabels);
2899
            module.remove.labels($activeLabels);
2900
          },
2901
          labels: function($labels) {
2902
            $labels = $labels || $module.find(selector.label);
2903
            module.verbose('Removing labels', $labels);
2904
            $labels
2905
              .each(function(){
2906
                var
2907
                  $label      = $(this),
2908
                  value       = $label.data(metadata.value),
2909
                  stringValue = (value !== undefined)
2910
                    ? String(value)
2911
                    : value,
2912
                  isUserValue = module.is.userValue(stringValue)
2913
                ;
2914
                if(settings.onLabelRemove.call($label, value) === false) {
2915
                  module.debug('Label remove callback cancelled removal');
2916
                  return;
2917
                }
2918
                module.remove.message();
2919
                if(isUserValue) {
2920
                  module.remove.value(stringValue);
2921
                  module.remove.label(stringValue);
2922
                }
2923
                else {
2924
                  // selected will also remove label
2925
                  module.remove.selected(stringValue);
2926
                }
2927
              })
2928
            ;
2929
          },
2930
          tabbable: function() {
2931
            if( module.is.searchSelection() ) {
2932
              module.debug('Searchable dropdown initialized');
2933
              $search
2934
                .removeAttr('tabindex')
2935
              ;
2936
              $menu
2937
                .removeAttr('tabindex')
2938
              ;
2939
            }
2940
            else {
2941
              module.debug('Simple selection dropdown initialized');
2942
              $module
2943
                .removeAttr('tabindex')
2944
              ;
2945
              $menu
2946
                .removeAttr('tabindex')
2947
              ;
2948
            }
2949
          }
2950
        },
2951
2952
        has: {
2953
          menuSearch: function() {
2954
            return (module.has.search() && $search.closest($menu).length > 0);
2955
          },
2956
          search: function() {
2957
            return ($search.length > 0);
2958
          },
2959
          sizer: function() {
2960
            return ($sizer.length > 0);
2961
          },
2962
          selectInput: function() {
2963
            return ( $input.is('select') );
2964
          },
2965
          minCharacters: function(searchTerm) {
2966
            if(settings.minCharacters) {
2967
              searchTerm = (searchTerm !== undefined)
2968
                ? String(searchTerm)
2969
                : String(module.get.query())
2970
              ;
2971
              return (searchTerm.length >= settings.minCharacters);
2972
            }
2973
            return true;
2974
          },
2975
          firstLetter: function($item, letter) {
2976
            var
2977
              text,
2978
              firstLetter
2979
            ;
2980
            if(!$item || $item.length === 0 || typeof letter !== 'string') {
2981
              return false;
2982
            }
2983
            text        = module.get.choiceText($item, false);
2984
            letter      = letter.toLowerCase();
2985
            firstLetter = String(text).charAt(0).toLowerCase();
2986
            return (letter == firstLetter);
2987
          },
2988
          input: function() {
2989
            return ($input.length > 0);
2990
          },
2991
          items: function() {
2992
            return ($item.length > 0);
2993
          },
2994
          menu: function() {
2995
            return ($menu.length > 0);
2996
          },
2997
          message: function() {
2998
            return ($menu.children(selector.message).length !== 0);
2999
          },
3000
          label: function(value) {
3001
            var
3002
              escapedValue = module.escape.value(value),
3003
              $labels      = $module.find(selector.label)
3004
            ;
3005
            return ($labels.filter('[data-' + metadata.value + '="' + module.escape.string(escapedValue) +'"]').length > 0);
3006
          },
3007
          maxSelections: function() {
3008
            return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections);
3009
          },
3010
          allResultsFiltered: function() {
3011
            var
3012
              $normalResults = $item.not(selector.addition)
3013
            ;
3014
            return ($normalResults.filter(selector.unselectable).length === $normalResults.length);
3015
          },
3016
          userSuggestion: function() {
3017
            return ($menu.children(selector.addition).length > 0);
3018
          },
3019
          query: function() {
3020
            return (module.get.query() !== '');
3021
          },
3022
          value: function(value) {
3023
            var
3024
              values   = module.get.values(),
3025
              hasValue = $.isArray(values)
3026
               ? values && ($.inArray(value, values) !== -1)
3027
               : (values == value)
3028
            ;
3029
            return (hasValue)
3030
              ? true
3031
              : false
3032
            ;
3033
          }
3034
        },
3035
3036
        is: {
3037
          active: function() {
3038
            return $module.hasClass(className.active);
3039
          },
3040
          bubbledLabelClick: function(event) {
3041
            return $(event.target).is('select, input') && $module.closest('label').length > 0;
3042
          },
3043
          bubbledIconClick: function(event) {
3044
            return $(event.target).closest($icon).length > 0;
3045
          },
3046
          alreadySetup: function() {
3047
            return ($module.is('select') && $module.parent(selector.dropdown).length > 0  && $module.prev().length === 0);
3048
          },
3049
          animating: function($subMenu) {
3050
            return ($subMenu)
3051
              ? $subMenu.transition && $subMenu.transition('is animating')
3052
              : $menu.transition    && $menu.transition('is animating')
3053
            ;
3054
          },
3055
          leftward: function($subMenu) {
3056
            var $selectedMenu = $subMenu || $menu;
3057
            return $selectedMenu.hasClass(className.leftward);
3058
          },
3059
          disabled: function() {
3060
            return $module.hasClass(className.disabled);
3061
          },
3062
          focused: function() {
3063
            return (document.activeElement === $module[0]);
3064
          },
3065
          focusedOnSearch: function() {
3066
            return (document.activeElement === $search[0]);
3067
          },
3068
          allFiltered: function() {
3069
            return( (module.is.multiple() || module.has.search()) && !(settings.hideAdditions == false && module.has.userSuggestion()) && !module.has.message() && module.has.allResultsFiltered() );
0 ignored issues
show
Best Practice introduced by
Comparing settings.hideAdditions to false using the == operator is not safe. Consider using === instead.
Loading history...
3070
          },
3071
          hidden: function($subMenu) {
3072
            return !module.is.visible($subMenu);
3073
          },
3074
          initialLoad: function() {
3075
            return initialLoad;
3076
          },
3077
          inObject: function(needle, object) {
3078
            var
3079
              found = false
3080
            ;
3081
            $.each(object, function(index, property) {
3082
              if(property == needle) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if property == needle is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
3083
                found = true;
3084
                return true;
3085
              }
3086
            });
3087
            return found;
3088
          },
3089
          multiple: function() {
3090
            return $module.hasClass(className.multiple);
3091
          },
3092
          remote: function() {
3093
            return settings.apiSettings && module.can.useAPI();
3094
          },
3095
          single: function() {
3096
            return !module.is.multiple();
3097
          },
3098
          selectMutation: function(mutations) {
3099
            var
3100
              selectChanged = false
3101
            ;
3102
            $.each(mutations, function(index, mutation) {
3103
              if(mutation.target && $(mutation.target).is('select')) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if mutation.target && $(mut...on.target).is("select") is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
3104
                selectChanged = true;
3105
                return true;
3106
              }
3107
            });
3108
            return selectChanged;
3109
          },
3110
          search: function() {
3111
            return $module.hasClass(className.search);
3112
          },
3113
          searchSelection: function() {
3114
            return ( module.has.search() && $search.parent(selector.dropdown).length === 1 );
3115
          },
3116
          selection: function() {
3117
            return $module.hasClass(className.selection);
3118
          },
3119
          userValue: function(value) {
3120
            return ($.inArray(value, module.get.userValues()) !== -1);
3121
          },
3122
          upward: function($menu) {
3123
            var $element = $menu || $module;
3124
            return $element.hasClass(className.upward);
3125
          },
3126
          visible: function($subMenu) {
3127
            return ($subMenu)
3128
              ? $subMenu.hasClass(className.visible)
3129
              : $menu.hasClass(className.visible)
3130
            ;
3131
          },
3132
          verticallyScrollableContext: function() {
3133
            var
3134
              overflowY = ($context.get(0) !== window)
3135
                ? $context.css('overflow-y')
3136
                : false
3137
            ;
3138
            return (overflowY == 'auto' || overflowY == 'scroll');
3139
          },
3140
          horizontallyScrollableContext: function() {
3141
            var
3142
              overflowX = ($context.get(0) !== window)
3143
                ? $context.css('overflow-X')
3144
                : false
3145
            ;
3146
            return (overflowX == 'auto' || overflowX == 'scroll');
3147
          }
3148
        },
3149
3150
        can: {
3151
          activate: function($item) {
3152
            if(settings.useLabels) {
3153
              return true;
3154
            }
3155
            if(!module.has.maxSelections()) {
3156
              return true;
3157
            }
3158
            if(module.has.maxSelections() && $item.hasClass(className.active)) {
3159
              return true;
3160
            }
3161
            return false;
3162
          },
3163
          openDownward: function($subMenu) {
3164
            var
3165
              $currentMenu    = $subMenu || $menu,
3166
              canOpenDownward = true,
0 ignored issues
show
Unused Code introduced by
The assignment to variable canOpenDownward seems to be never used. Consider removing it.
Loading history...
3167
              onScreen        = {},
0 ignored issues
show
Unused Code introduced by
The assignment to variable onScreen seems to be never used. Consider removing it.
Loading history...
3168
              calculations
3169
            ;
3170
            $currentMenu
3171
              .addClass(className.loading)
3172
            ;
3173
            calculations = {
3174
              context: {
3175
                scrollTop : $context.scrollTop(),
3176
                height    : $context.outerHeight()
3177
              },
3178
              menu : {
3179
                offset: $currentMenu.offset(),
3180
                height: $currentMenu.outerHeight()
3181
              }
3182
            };
3183
            if(module.is.verticallyScrollableContext()) {
3184
              calculations.menu.offset.top += calculations.context.scrollTop;
3185
            }
3186
            onScreen = {
3187
              above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.menu.height,
3188
              below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top + calculations.menu.height
3189
            };
3190
            if(onScreen.below) {
3191
              module.verbose('Dropdown can fit in context downward', onScreen);
3192
              canOpenDownward = true;
3193
            }
3194
            else if(!onScreen.below && !onScreen.above) {
3195
              module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen);
3196
              canOpenDownward = true;
3197
            }
3198
            else {
3199
              module.verbose('Dropdown cannot fit below, opening upward', onScreen);
3200
              canOpenDownward = false;
3201
            }
3202
            $currentMenu.removeClass(className.loading);
3203
            return canOpenDownward;
3204
          },
3205
          openRightward: function($subMenu) {
3206
            var
3207
              $currentMenu     = $subMenu || $menu,
3208
              canOpenRightward = true,
3209
              isOffscreenRight = false,
0 ignored issues
show
Unused Code introduced by
The assignment to variable isOffscreenRight seems to be never used. Consider removing it.
Loading history...
3210
              calculations
3211
            ;
3212
            $currentMenu
3213
              .addClass(className.loading)
3214
            ;
3215
            calculations = {
3216
              context: {
3217
                scrollLeft : $context.scrollLeft(),
3218
                width      : $context.outerWidth()
3219
              },
3220
              menu: {
3221
                offset : $currentMenu.offset(),
3222
                width  : $currentMenu.outerWidth()
3223
              }
3224
            };
3225
            if(module.is.horizontallyScrollableContext()) {
3226
              calculations.menu.offset.left += calculations.context.scrollLeft;
3227
            }
3228
            isOffscreenRight = (calculations.menu.offset.left + calculations.menu.width >= calculations.context.scrollLeft + calculations.context.width);
3229
            if(isOffscreenRight) {
3230
              module.verbose('Dropdown cannot fit in context rightward', isOffscreenRight);
3231
              canOpenRightward = false;
3232
            }
3233
            $currentMenu.removeClass(className.loading);
3234
            return canOpenRightward;
3235
          },
3236
          click: function() {
3237
            return (hasTouch || settings.on == 'click');
3238
          },
3239
          extendSelect: function() {
3240
            return settings.allowAdditions || settings.apiSettings;
3241
          },
3242
          show: function() {
3243
            return !module.is.disabled() && (module.has.items() || module.has.message());
3244
          },
3245
          useAPI: function() {
3246
            return $.fn.api !== undefined;
3247
          }
3248
        },
3249
3250
        animate: {
3251
          show: function(callback, $subMenu) {
3252
            var
3253
              $currentMenu = $subMenu || $menu,
3254
              start = ($subMenu)
3255
                ? function() {}
3256
                : function() {
3257
                  module.hideSubMenus();
3258
                  module.hideOthers();
3259
                  module.set.active();
3260
                },
3261
              transition
3262
            ;
3263
            callback = $.isFunction(callback)
3264
              ? callback
3265
              : function(){}
3266
            ;
3267
            module.verbose('Doing menu show animation', $currentMenu);
3268
            module.set.direction($subMenu);
3269
            transition = module.get.transition($subMenu);
3270
            if( module.is.selection() ) {
3271
              module.set.scrollPosition(module.get.selectedItem(), true);
3272
            }
3273
            if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) {
3274
              if(transition == 'none') {
3275
                start();
3276
                $currentMenu.transition('show');
3277
                callback.call(element);
3278
              }
3279
              else if($.fn.transition !== undefined && $module.transition('is supported')) {
3280
                $currentMenu
3281
                  .transition({
3282
                    animation  : transition + ' in',
3283
                    debug      : settings.debug,
3284
                    verbose    : settings.verbose,
3285
                    duration   : settings.duration,
3286
                    queue      : true,
3287
                    onStart    : start,
3288
                    onComplete : function() {
3289
                      callback.call(element);
3290
                    }
3291
                  })
3292
                ;
3293
              }
3294
              else {
3295
                module.error(error.noTransition, transition);
3296
              }
3297
            }
3298
          },
3299
          hide: function(callback, $subMenu) {
3300
            var
3301
              $currentMenu = $subMenu || $menu,
3302
              duration = ($subMenu)
3303
                ? (settings.duration * 0.9)
3304
                : settings.duration,
3305
              start = ($subMenu)
3306
                ? function() {}
3307
                : function() {
3308
                  if( module.can.click() ) {
3309
                    module.unbind.intent();
3310
                  }
3311
                  module.remove.active();
3312
                },
3313
              transition = module.get.transition($subMenu)
3314
            ;
3315
            callback = $.isFunction(callback)
3316
              ? callback
3317
              : function(){}
3318
            ;
3319
            if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) {
3320
              module.verbose('Doing menu hide animation', $currentMenu);
3321
3322
              if(transition == 'none') {
3323
                start();
3324
                $currentMenu.transition('hide');
3325
                callback.call(element);
3326
              }
3327
              else if($.fn.transition !== undefined && $module.transition('is supported')) {
3328
                $currentMenu
3329
                  .transition({
3330
                    animation  : transition + ' out',
3331
                    duration   : settings.duration,
3332
                    debug      : settings.debug,
3333
                    verbose    : settings.verbose,
3334
                    queue      : true,
3335
                    onStart    : start,
3336
                    onComplete : function() {
3337
                      callback.call(element);
3338
                    }
3339
                  })
3340
                ;
3341
              }
3342
              else {
3343
                module.error(error.transition);
3344
              }
3345
            }
3346
          }
3347
        },
3348
3349
        hideAndClear: function() {
3350
          module.remove.searchTerm();
3351
          if( module.has.maxSelections() ) {
3352
            return;
3353
          }
3354
          if(module.has.search()) {
3355
            module.hide(function() {
3356
              module.remove.filteredItem();
3357
            });
3358
          }
3359
          else {
3360
            module.hide();
3361
          }
3362
        },
3363
3364
        delay: {
3365
          show: function() {
3366
            module.verbose('Delaying show event to ensure user intent');
3367
            clearTimeout(module.timer);
3368
            module.timer = setTimeout(module.show, settings.delay.show);
3369
          },
3370
          hide: function() {
3371
            module.verbose('Delaying hide event to ensure user intent');
3372
            clearTimeout(module.timer);
3373
            module.timer = setTimeout(module.hide, settings.delay.hide);
3374
          }
3375
        },
3376
3377
        escape: {
3378
          value: function(value) {
3379
            var
3380
              multipleValues = $.isArray(value),
3381
              stringValue    = (typeof value === 'string'),
3382
              isUnparsable   = (!stringValue && !multipleValues),
3383
              hasQuotes      = (stringValue && value.search(regExp.quote) !== -1),
3384
              values         = []
3385
            ;
3386
            if(isUnparsable || !hasQuotes) {
3387
              return value;
3388
            }
3389
            module.debug('Encoding quote values for use in select', value);
3390
            if(multipleValues) {
3391
              $.each(value, function(index, value){
3392
                values.push(value.replace(regExp.quote, '&quot;'));
3393
              });
3394
              return values;
3395
            }
3396
            return value.replace(regExp.quote, '&quot;');
3397
          },
3398
          string: function(text) {
3399
            text =  String(text);
3400
            return text.replace(regExp.escape, '\\$&');
3401
          }
3402
        },
3403
3404
        setting: function(name, value) {
3405
          module.debug('Changing setting', name, value);
3406
          if( $.isPlainObject(name) ) {
3407
            $.extend(true, settings, name);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
3408
          }
3409
          else if(value !== undefined) {
3410
            if($.isPlainObject(settings[name])) {
3411
              $.extend(true, settings[name], value);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
3412
            }
3413
            else {
3414
              settings[name] = value;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
3415
            }
3416
          }
3417
          else {
3418
            return settings[name];
3419
          }
3420
        },
3421
        internal: function(name, value) {
3422
          if( $.isPlainObject(name) ) {
3423
            $.extend(true, module, name);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
3424
          }
3425
          else if(value !== undefined) {
3426
            module[name] = value;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
3427
          }
3428
          else {
3429
            return module[name];
3430
          }
3431
        },
3432
        debug: function() {
3433
          if(!settings.silent && settings.debug) {
3434
            if(settings.performance) {
3435
              module.performance.log(arguments);
3436
            }
3437
            else {
3438
              module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
3439
              module.debug.apply(console, arguments);
3440
            }
3441
          }
3442
        },
3443
        verbose: function() {
3444
          if(!settings.silent && settings.verbose && settings.debug) {
3445
            if(settings.performance) {
3446
              module.performance.log(arguments);
3447
            }
3448
            else {
3449
              module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
3450
              module.verbose.apply(console, arguments);
3451
            }
3452
          }
3453
        },
3454
        error: function() {
3455
          if(!settings.silent) {
3456
            module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
3457
            module.error.apply(console, arguments);
3458
          }
3459
        },
3460
        performance: {
3461
          log: function(message) {
3462
            var
3463
              currentTime,
3464
              executionTime,
3465
              previousTime
3466
            ;
3467
            if(settings.performance) {
3468
              currentTime   = new Date().getTime();
3469
              previousTime  = time || currentTime;
3470
              executionTime = currentTime - previousTime;
3471
              time          = currentTime;
3472
              performance.push({
3473
                'Name'           : message[0],
3474
                'Arguments'      : [].slice.call(message, 1) || '',
3475
                'Element'        : element,
3476
                'Execution Time' : executionTime
3477
              });
3478
            }
3479
            clearTimeout(module.performance.timer);
3480
            module.performance.timer = setTimeout(module.performance.display, 500);
3481
          },
3482
          display: function() {
3483
            var
3484
              title = settings.name + ':',
3485
              totalTime = 0
3486
            ;
3487
            time = false;
3488
            clearTimeout(module.performance.timer);
3489
            $.each(performance, function(index, data) {
3490
              totalTime += data['Execution Time'];
3491
            });
3492
            title += ' ' + totalTime + 'ms';
3493
            if(moduleSelector) {
3494
              title += ' \'' + moduleSelector + '\'';
3495
            }
3496
            if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
3497
              console.groupCollapsed(title);
3498
              if(console.table) {
3499
                console.table(performance);
3500
              }
3501
              else {
3502
                $.each(performance, function(index, data) {
3503
                  console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
3504
                });
3505
              }
3506
              console.groupEnd();
3507
            }
3508
            performance = [];
3509
          }
3510
        },
3511
        invoke: function(query, passedArguments, context) {
3512
          var
3513
            object = instance,
3514
            maxDepth,
3515
            found,
3516
            response
3517
          ;
3518
          passedArguments = passedArguments || queryArguments;
3519
          context         = element         || context;
3520
          if(typeof query == 'string' && object !== undefined) {
3521
            query    = query.split(/[\. ]/);
3522
            maxDepth = query.length - 1;
3523
            $.each(query, function(depth, value) {
3524
              var camelCaseValue = (depth != maxDepth)
3525
                ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
3526
                : query
3527
              ;
3528
              if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
3529
                object = object[camelCaseValue];
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
3530
              }
3531
              else if( object[camelCaseValue] !== undefined ) {
3532
                found = object[camelCaseValue];
3533
                return false;
3534
              }
3535
              else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
3536
                object = object[value];
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
3537
              }
3538
              else if( object[value] !== undefined ) {
3539
                found = object[value];
3540
                return false;
3541
              }
3542
              else {
3543
                module.error(error.method, query);
3544
                return false;
3545
              }
3546
            });
3547
          }
3548
          if ( $.isFunction( found ) ) {
3549
            response = found.apply(context, passedArguments);
3550
          }
3551
          else if(found !== undefined) {
3552
            response = found;
3553
          }
3554
          if($.isArray(returnedValue)) {
3555
            returnedValue.push(response);
0 ignored issues
show
Bug introduced by
The variable response does not seem to be initialized in case found !== undefined on line 3551 is false. Are you sure the function push handles undefined variables?
Loading history...
3556
          }
3557
          else if(returnedValue !== undefined) {
3558
            returnedValue = [returnedValue, response];
3559
          }
3560
          else if(response !== undefined) {
3561
            returnedValue = response;
3562
          }
3563
          return found;
3564
        }
3565
      };
3566
3567
      if(methodInvoked) {
3568
        if(instance === undefined) {
3569
          module.initialize();
3570
        }
3571
        module.invoke(query);
3572
      }
3573
      else {
3574
        if(instance !== undefined) {
3575
          instance.invoke('destroy');
3576
        }
3577
        module.initialize();
3578
      }
3579
    })
3580
  ;
3581
  return (returnedValue !== undefined)
3582
    ? returnedValue
3583
    : $allModules
3584
  ;
3585
};
3586
3587
$.fn.dropdown.settings = {
3588
3589
  silent                 : false,
3590
  debug                  : false,
3591
  verbose                : false,
3592
  performance            : true,
3593
3594
  on                     : 'click',    // what event should show menu action on item selection
3595
  action                 : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){})
3596
3597
3598
  apiSettings            : false,
3599
  selectOnKeydown        : true,       // Whether selection should occur automatically when keyboard shortcuts used
3600
  minCharacters          : 0,          // Minimum characters required to trigger API call
3601
3602
  filterRemoteData       : false,      // Whether API results should be filtered after being returned for query term
3603
  saveRemoteData         : true,       // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh
3604
3605
  throttle               : 200,        // How long to wait after last user input to search remotely
3606
3607
  context                : window,     // Context to use when determining if on screen
3608
  direction              : 'auto',     // Whether dropdown should always open in one direction
3609
  keepOnScreen           : true,       // Whether dropdown should check whether it is on screen before showing
3610
3611
  match                  : 'both',     // what to match against with search selection (both, text, or label)
3612
  fullTextSearch         : false,      // search anywhere in value (set to 'exact' to require exact matches)
3613
3614
  placeholder            : 'auto',     // whether to convert blank <select> values to placeholder text
3615
  preserveHTML           : true,       // preserve html when selecting value
3616
  sortSelect             : false,      // sort selection on init
3617
3618
  forceSelection         : true,       // force a choice on blur with search selection
3619
3620
  allowAdditions         : false,      // whether multiple select should allow user added values
3621
  hideAdditions          : true,      // whether or not to hide special message prompting a user they can enter a value
3622
3623
  maxSelections          : false,      // When set to a number limits the number of selections to this count
3624
  useLabels              : true,       // whether multiple select should filter currently active selections from choices
3625
  delimiter              : ',',        // when multiselect uses normal <input> the values will be delimited with this character
3626
3627
  showOnFocus            : true,       // show menu on focus
3628
  allowReselection       : false,      // whether current value should trigger callbacks when reselected
3629
  allowTab               : true,       // add tabindex to element
3630
  allowCategorySelection : false,      // allow elements with sub-menus to be selected
3631
3632
  fireOnInit             : false,      // Whether callbacks should fire when initializing dropdown values
3633
3634
  transition             : 'auto',     // auto transition will slide down or up based on direction
3635
  duration               : 200,        // duration of transition
3636
3637
  glyphWidth             : 1.037,      // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width
3638
3639
  // label settings on multi-select
3640
  label: {
3641
    transition : 'scale',
3642
    duration   : 200,
3643
    variation  : false
3644
  },
3645
3646
  // delay before event
3647
  delay : {
3648
    hide   : 300,
3649
    show   : 200,
3650
    search : 20,
3651
    touch  : 50
3652
  },
3653
3654
  /* Callbacks */
3655
  onChange      : function(value, text, $selected){},
0 ignored issues
show
Unused Code introduced by
The parameter text is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter value is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter $selected is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
3656
  onAdd         : function(value, text, $selected){},
0 ignored issues
show
Unused Code introduced by
The parameter text is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter $selected is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter value is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
3657
  onRemove      : function(value, text, $selected){},
0 ignored issues
show
Unused Code introduced by
The parameter value is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter text is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter $selected is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
3658
3659
  onLabelSelect : function($selectedLabels){},
0 ignored issues
show
Unused Code introduced by
The parameter $selectedLabels is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
3660
  onLabelCreate : function(value, text) { return $(this); },
0 ignored issues
show
Unused Code introduced by
The parameter value is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter text is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
3661
  onLabelRemove : function(value) { return true; },
0 ignored issues
show
Unused Code introduced by
The parameter value is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
3662
  onNoResults   : function(searchTerm) { return true; },
0 ignored issues
show
Unused Code introduced by
The parameter searchTerm is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
3663
  onShow        : function(){},
3664
  onHide        : function(){},
3665
3666
  /* Component */
3667
  name           : 'Dropdown',
3668
  namespace      : 'dropdown',
3669
3670
  message: {
3671
    addResult     : 'Add <b>{term}</b>',
3672
    count         : '{count} selected',
3673
    maxSelections : 'Max {maxCount} selections',
3674
    noResults     : 'No results found.',
3675
    serverError   : 'There was an error contacting the server'
3676
  },
3677
3678
  error : {
3679
    action          : 'You called a dropdown action that was not defined',
3680
    alreadySetup    : 'Once a select has been initialized behaviors must be called on the created ui dropdown',
3681
    labels          : 'Allowing user additions currently requires the use of labels.',
3682
    missingMultiple : '<select> requires multiple property to be set to correctly preserve multiple values',
3683
    method          : 'The method you called is not defined.',
3684
    noAPI           : 'The API module is required to load resources remotely',
3685
    noStorage       : 'Saving remote data requires session storage',
3686
    noTransition    : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>'
3687
  },
3688
3689
  regExp : {
3690
    escape   : /[-[\]{}()*+?.,\\^$|#\s]/g,
3691
    quote    : /"/g
3692
  },
3693
3694
  metadata : {
3695
    defaultText     : 'defaultText',
3696
    defaultValue    : 'defaultValue',
3697
    placeholderText : 'placeholder',
3698
    text            : 'text',
3699
    value           : 'value'
3700
  },
3701
3702
  // property names for remote query
3703
  fields: {
3704
    remoteValues : 'results',  // grouping for api results
3705
    values       : 'values',   // grouping for all dropdown values
3706
    disabled     : 'disabled', // whether value should be disabled
3707
    name         : 'name',     // displayed dropdown text
3708
    value        : 'value',    // actual dropdown value
3709
    text         : 'text'      // displayed text when selected
3710
  },
3711
3712
  keys : {
3713
    backspace  : 8,
3714
    delimiter  : 188, // comma
3715
    deleteKey  : 46,
3716
    enter      : 13,
3717
    escape     : 27,
3718
    pageUp     : 33,
3719
    pageDown   : 34,
3720
    leftArrow  : 37,
3721
    upArrow    : 38,
3722
    rightArrow : 39,
3723
    downArrow  : 40
3724
  },
3725
3726
  selector : {
3727
    addition     : '.addition',
3728
    dropdown     : '.ui.dropdown',
3729
    hidden       : '.hidden',
3730
    icon         : '> .dropdown.icon',
3731
    input        : '> input[type="hidden"], > select',
3732
    item         : '.item',
3733
    label        : '> .label',
3734
    remove       : '> .label > .delete.icon',
3735
    siblingLabel : '.label',
3736
    menu         : '.menu',
3737
    message      : '.message',
3738
    menuIcon     : '.dropdown.icon',
3739
    search       : 'input.search, .menu > .search > input, .menu input.search',
3740
    sizer        : '> input.sizer',
3741
    text         : '> .text:not(.icon)',
3742
    unselectable : '.disabled, .filtered'
3743
  },
3744
3745
  className : {
3746
    active      : 'active',
3747
    addition    : 'addition',
3748
    animating   : 'animating',
3749
    disabled    : 'disabled',
3750
    empty       : 'empty',
3751
    dropdown    : 'ui dropdown',
3752
    filtered    : 'filtered',
3753
    hidden      : 'hidden transition',
3754
    item        : 'item',
3755
    label       : 'ui label',
3756
    loading     : 'loading',
3757
    menu        : 'menu',
3758
    message     : 'message',
3759
    multiple    : 'multiple',
3760
    placeholder : 'default',
3761
    sizer       : 'sizer',
3762
    search      : 'search',
3763
    selected    : 'selected',
3764
    selection   : 'selection',
3765
    upward      : 'upward',
3766
    leftward    : 'left',
3767
    visible     : 'visible'
3768
  }
3769
3770
};
3771
3772
/* Templates */
3773
$.fn.dropdown.settings.templates = {
3774
3775
  // generates dropdown from select values
3776
  dropdown: function(select) {
3777
    var
3778
      placeholder = select.placeholder || false,
3779
      values      = select.values || {},
3780
      html        = ''
3781
    ;
3782
    html +=  '<i class="dropdown icon"></i>';
3783
    if(select.placeholder) {
3784
      html += '<div class="default text">' + placeholder + '</div>';
3785
    }
3786
    else {
3787
      html += '<div class="text"></div>';
3788
    }
3789
    html += '<div class="menu">';
3790
    $.each(select.values, function(index, option) {
3791
      html += (option.disabled)
3792
        ? '<div class="disabled item" data-value="' + option.value + '">' + option.name + '</div>'
3793
        : '<div class="item" data-value="' + option.value + '">' + option.name + '</div>'
3794
      ;
3795
    });
3796
    html += '</div>';
3797
    return html;
3798
  },
3799
3800
  // generates just menu from select
3801
  menu: function(response, fields) {
3802
    var
3803
      values = response[fields.values] || {},
3804
      html   = ''
3805
    ;
3806
    $.each(values, function(index, option) {
3807
      var
3808
        maybeText = (option[fields.text])
3809
          ? 'data-text="' + option[fields.text] + '"'
3810
          : '',
3811
        maybeDisabled = (option[fields.disabled])
3812
          ? 'disabled '
3813
          : ''
3814
      ;
3815
      html += '<div class="'+ maybeDisabled +'item" data-value="' + option[fields.value] + '"' + maybeText + '>'
3816
      html +=   option[fields.name];
3817
      html += '</div>';
3818
    });
3819
    return html;
3820
  },
3821
3822
  // generates label for multiselect
3823
  label: function(value, text) {
3824
    return text + '<i class="delete icon"></i>';
3825
  },
3826
3827
3828
  // generates messages like "No results"
3829
  message: function(message) {
3830
    return message;
3831
  },
3832
3833
  // generates user addition to selection menu
3834
  addition: function(choice) {
3835
    return choice;
3836
  }
3837
3838
};
3839
3840
})( jQuery, window, document );
3841