semantic-ui-sass 1.12.3.0 → 2.0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/app/assets/javascripts/semantic-ui.js +1 -0
  4. data/app/assets/javascripts/semantic-ui/accordion.js +67 -53
  5. data/app/assets/javascripts/semantic-ui/api.js +395 -189
  6. data/app/assets/javascripts/semantic-ui/checkbox.js +322 -114
  7. data/app/assets/javascripts/semantic-ui/colorize.js +4 -2
  8. data/app/assets/javascripts/semantic-ui/dimmer.js +74 -50
  9. data/app/assets/javascripts/semantic-ui/dropdown.js +2046 -584
  10. data/app/assets/javascripts/semantic-ui/embed.js +662 -0
  11. data/app/assets/javascripts/semantic-ui/form.js +345 -163
  12. data/app/assets/javascripts/semantic-ui/modal.js +119 -90
  13. data/app/assets/javascripts/semantic-ui/nag.js +8 -9
  14. data/app/assets/javascripts/semantic-ui/popup.js +390 -228
  15. data/app/assets/javascripts/semantic-ui/progress.js +112 -103
  16. data/app/assets/javascripts/semantic-ui/rating.js +79 -55
  17. data/app/assets/javascripts/semantic-ui/search.js +305 -123
  18. data/app/assets/javascripts/semantic-ui/shape.js +94 -48
  19. data/app/assets/javascripts/semantic-ui/sidebar.js +84 -151
  20. data/app/assets/javascripts/semantic-ui/site.js +5 -5
  21. data/app/assets/javascripts/semantic-ui/state.js +4 -4
  22. data/app/assets/javascripts/semantic-ui/sticky.js +108 -35
  23. data/app/assets/javascripts/semantic-ui/tab.js +220 -125
  24. data/app/assets/javascripts/semantic-ui/transition.js +205 -171
  25. data/app/assets/javascripts/semantic-ui/visibility.js +220 -100
  26. data/app/assets/javascripts/semantic-ui/visit.js +6 -4
  27. data/app/assets/stylesheets/semantic-ui/collections/_breadcrumb.scss +17 -16
  28. data/app/assets/stylesheets/semantic-ui/collections/_form.scss +223 -121
  29. data/app/assets/stylesheets/semantic-ui/collections/_grid.scss +462 -448
  30. data/app/assets/stylesheets/semantic-ui/collections/_menu.scss +949 -665
  31. data/app/assets/stylesheets/semantic-ui/collections/_message.scss +134 -92
  32. data/app/assets/stylesheets/semantic-ui/collections/_table.scss +270 -208
  33. data/app/assets/stylesheets/semantic-ui/elements/_all.scss +1 -0
  34. data/app/assets/stylesheets/semantic-ui/elements/_button.scss +1357 -521
  35. data/app/assets/stylesheets/semantic-ui/elements/_container.scss +125 -0
  36. data/app/assets/stylesheets/semantic-ui/elements/_divider.scss +51 -31
  37. data/app/assets/stylesheets/semantic-ui/elements/_flag.scss +3 -3
  38. data/app/assets/stylesheets/semantic-ui/elements/_header.scss +270 -144
  39. data/app/assets/stylesheets/semantic-ui/elements/_icon.scss +241 -110
  40. data/app/assets/stylesheets/semantic-ui/elements/_image.scss +30 -16
  41. data/app/assets/stylesheets/semantic-ui/elements/_input.scss +88 -53
  42. data/app/assets/stylesheets/semantic-ui/elements/_label.scss +432 -281
  43. data/app/assets/stylesheets/semantic-ui/elements/_list.scss +172 -128
  44. data/app/assets/stylesheets/semantic-ui/elements/_loader.scss +16 -14
  45. data/app/assets/stylesheets/semantic-ui/elements/_rail.scss +15 -7
  46. data/app/assets/stylesheets/semantic-ui/elements/_reveal.scss +32 -13
  47. data/app/assets/stylesheets/semantic-ui/elements/_segment.scss +329 -212
  48. data/app/assets/stylesheets/semantic-ui/elements/_step.scss +291 -99
  49. data/app/assets/stylesheets/semantic-ui/globals/_reset.scss +2 -2
  50. data/app/assets/stylesheets/semantic-ui/globals/_site.scss +19 -18
  51. data/app/assets/stylesheets/semantic-ui/modules/_accordion.scss +17 -18
  52. data/app/assets/stylesheets/semantic-ui/modules/_all.scss +1 -0
  53. data/app/assets/stylesheets/semantic-ui/modules/_checkbox.scss +265 -161
  54. data/app/assets/stylesheets/semantic-ui/modules/_dimmer.scss +29 -15
  55. data/app/assets/stylesheets/semantic-ui/modules/_dropdown.scss +441 -156
  56. data/app/assets/stylesheets/semantic-ui/modules/_embed.scss +168 -0
  57. data/app/assets/stylesheets/semantic-ui/modules/_modal.scss +163 -85
  58. data/app/assets/stylesheets/semantic-ui/modules/_nag.scss +8 -8
  59. data/app/assets/stylesheets/semantic-ui/modules/_popup.scss +88 -23
  60. data/app/assets/stylesheets/semantic-ui/modules/_progress.scss +185 -129
  61. data/app/assets/stylesheets/semantic-ui/modules/_rating.scss +75 -60
  62. data/app/assets/stylesheets/semantic-ui/modules/_search.scss +99 -52
  63. data/app/assets/stylesheets/semantic-ui/modules/_shape.scss +11 -11
  64. data/app/assets/stylesheets/semantic-ui/modules/_sidebar.scss +16 -12
  65. data/app/assets/stylesheets/semantic-ui/modules/_sticky.scss +4 -4
  66. data/app/assets/stylesheets/semantic-ui/modules/_tab.scss +3 -3
  67. data/app/assets/stylesheets/semantic-ui/modules/_transition.scss +31 -39
  68. data/app/assets/stylesheets/semantic-ui/views/_ad.scss +3 -3
  69. data/app/assets/stylesheets/semantic-ui/views/_all.scss +1 -0
  70. data/app/assets/stylesheets/semantic-ui/views/_card.scss +204 -162
  71. data/app/assets/stylesheets/semantic-ui/views/_comment.scss +6 -6
  72. data/app/assets/stylesheets/semantic-ui/views/_feed.scss +51 -26
  73. data/app/assets/stylesheets/semantic-ui/views/_item.scss +62 -36
  74. data/app/assets/stylesheets/semantic-ui/views/_statistic.scss +265 -90
  75. data/lib/semantic/ui/sass/version.rb +2 -2
  76. data/semantic-ui-sass.gemspec +2 -2
  77. metadata +9 -6
@@ -3,7 +3,7 @@
3
3
  * http://github.com/semantic-org/semantic-ui/
4
4
  *
5
5
  *
6
- * Copyright 2014 Contributors
6
+ * Copyright 2015 Contributors
7
7
  * Released under the MIT license
8
8
  * http://opensource.org/licenses/MIT
9
9
  *
@@ -15,7 +15,9 @@
15
15
 
16
16
  $.fn.colorize = function(parameters) {
17
17
  var
18
- settings = $.extend(true, {}, $.fn.colorize.settings, parameters),
18
+ settings = ( $.isPlainObject(parameters) )
19
+ ? $.extend(true, {}, $.fn.colorize.settings, parameters)
20
+ : $.extend({}, $.fn.colorize.settings),
19
21
  // hoist arguments
20
22
  moduleArguments = arguments || false
21
23
  ;
@@ -1,9 +1,9 @@
1
1
  /*!
2
- * # Semantic UI 1.12.3 - Dimmer
2
+ * # Semantic UI - Dimmer
3
3
  * http://github.com/semantic-org/semantic-ui/
4
4
  *
5
5
  *
6
- * Copyright 2014 Contributors
6
+ * Copyright 2015 Contributors
7
7
  * Released under the MIT license
8
8
  * http://opensource.org/licenses/MIT
9
9
  *
@@ -60,6 +60,7 @@ $.fn.dimmer = function(parameters) {
60
60
 
61
61
  preinitialize: function() {
62
62
  if( module.is.dimmer() ) {
63
+
63
64
  $dimmable = $module.parent();
64
65
  $dimmer = $module;
65
66
  }
@@ -67,10 +68,10 @@ $.fn.dimmer = function(parameters) {
67
68
  $dimmable = $module;
68
69
  if( module.has.dimmer() ) {
69
70
  if(settings.dimmerName) {
70
- $dimmer = $dimmable.children(selector.dimmer).filter('.' + settings.dimmerName);
71
+ $dimmer = $dimmable.find(selector.dimmer).filter('.' + settings.dimmerName);
71
72
  }
72
73
  else {
73
- $dimmer = $dimmable.children(selector.dimmer);
74
+ $dimmer = $dimmable.find(selector.dimmer);
74
75
  }
75
76
  }
76
77
  else {
@@ -81,28 +82,8 @@ $.fn.dimmer = function(parameters) {
81
82
 
82
83
  initialize: function() {
83
84
  module.debug('Initializing dimmer', settings);
84
- if(settings.on == 'hover') {
85
- $dimmable
86
- .on('mouseenter' + eventNamespace, module.show)
87
- .on('mouseleave' + eventNamespace, module.hide)
88
- ;
89
- }
90
- else if(settings.on == 'click') {
91
- $dimmable
92
- .on(clickEvent + eventNamespace, module.toggle)
93
- ;
94
- }
95
- if( module.is.page() ) {
96
- module.debug('Setting as a page dimmer', $dimmable);
97
- module.set.pageDimmer();
98
- }
99
85
 
100
- if( module.is.closable() ) {
101
- module.verbose('Adding dimmer close event', $dimmer);
102
- $dimmer
103
- .on(clickEvent + eventNamespace, module.event.click)
104
- ;
105
- }
86
+ module.bind.events();
106
87
  module.set.dimmable();
107
88
  module.instantiate();
108
89
  },
@@ -117,15 +98,46 @@ $.fn.dimmer = function(parameters) {
117
98
 
118
99
  destroy: function() {
119
100
  module.verbose('Destroying previous module', $dimmer);
120
- $module
121
- .removeData(moduleNamespace)
122
- ;
101
+ module.unbind.events();
102
+ module.remove.variation();
123
103
  $dimmable
124
104
  .off(eventNamespace)
125
105
  ;
126
- $dimmer
127
- .off(eventNamespace)
128
- ;
106
+ },
107
+
108
+ bind: {
109
+ events: function() {
110
+ if(settings.on == 'hover') {
111
+ $dimmable
112
+ .on('mouseenter' + eventNamespace, module.show)
113
+ .on('mouseleave' + eventNamespace, module.hide)
114
+ ;
115
+ }
116
+ else if(settings.on == 'click') {
117
+ $dimmable
118
+ .on(clickEvent + eventNamespace, module.toggle)
119
+ ;
120
+ }
121
+ if( module.is.page() ) {
122
+ module.debug('Setting as a page dimmer', $dimmable);
123
+ module.set.pageDimmer();
124
+ }
125
+
126
+ if( module.is.closable() ) {
127
+ module.verbose('Adding dimmer close event', $dimmer);
128
+ $dimmable
129
+ .on(clickEvent + eventNamespace, selector.dimmer, module.event.click)
130
+ ;
131
+ }
132
+ }
133
+ },
134
+
135
+ unbind: {
136
+ events: function() {
137
+ $module
138
+ .removeData(moduleNamespace)
139
+ ;
140
+ }
129
141
  },
130
142
 
131
143
  event: {
@@ -313,10 +325,10 @@ $.fn.dimmer = function(parameters) {
313
325
  has: {
314
326
  dimmer: function() {
315
327
  if(settings.dimmerName) {
316
- return ($module.children(selector.dimmer).filter('.' + settings.dimmerName).length > 0);
328
+ return ($module.find(selector.dimmer).filter('.' + settings.dimmerName).length > 0);
317
329
  }
318
330
  else {
319
- return ( $module.children(selector.dimmer).length > 0 );
331
+ return ( $module.find(selector.dimmer).length > 0 );
320
332
  }
321
333
  }
322
334
  },
@@ -338,10 +350,10 @@ $.fn.dimmer = function(parameters) {
338
350
  return settings.closable;
339
351
  },
340
352
  dimmer: function() {
341
- return $module.is(selector.dimmer);
353
+ return $module.hasClass(className.dimmer);
342
354
  },
343
355
  dimmable: function() {
344
- return $module.is(selector.dimmable);
356
+ return $module.hasClass(className.dimmable);
345
357
  },
346
358
  dimmed: function() {
347
359
  return $dimmable.hasClass(className.dimmed);
@@ -369,11 +381,11 @@ $.fn.dimmer = function(parameters) {
369
381
  set: {
370
382
  opacity: function(opacity) {
371
383
  var
372
- opacity = settings.opacity || opacity,
373
384
  color = $dimmer.css('background-color'),
374
385
  colorArray = color.split(','),
375
386
  isRGBA = (colorArray && colorArray.length == 4)
376
387
  ;
388
+ opacity = settings.opacity || opacity;
377
389
  if(isRGBA) {
378
390
  colorArray[3] = opacity + ')';
379
391
  color = colorArray.join(',');
@@ -398,6 +410,12 @@ $.fn.dimmer = function(parameters) {
398
410
  },
399
411
  disabled: function() {
400
412
  $dimmer.addClass(className.disabled);
413
+ },
414
+ variation: function(variation) {
415
+ variation = variation || settings.variation;
416
+ if(variation) {
417
+ $dimmer.addClass(variation);
418
+ }
401
419
  }
402
420
  },
403
421
 
@@ -412,6 +430,12 @@ $.fn.dimmer = function(parameters) {
412
430
  },
413
431
  disabled: function() {
414
432
  $dimmer.removeClass(className.disabled);
433
+ },
434
+ variation: function(variation) {
435
+ variation = variation || settings.variation;
436
+ if(variation) {
437
+ $dimmer.removeClass(variation);
438
+ }
415
439
  }
416
440
  },
417
441
 
@@ -484,7 +508,7 @@ $.fn.dimmer = function(parameters) {
484
508
  });
485
509
  }
486
510
  clearTimeout(module.performance.timer);
487
- module.performance.timer = setTimeout(module.performance.display, 100);
511
+ module.performance.timer = setTimeout(module.performance.display, 500);
488
512
  },
489
513
  display: function() {
490
514
  var
@@ -603,7 +627,7 @@ $.fn.dimmer.settings = {
603
627
  namespace : 'dimmer',
604
628
 
605
629
  debug : false,
606
- verbose : true,
630
+ verbose : false,
607
631
  performance : true,
608
632
 
609
633
  // name to distinguish between multiple dimmers in context
@@ -641,27 +665,27 @@ $.fn.dimmer.settings = {
641
665
  method : 'The method you called is not defined.'
642
666
  },
643
667
 
644
- selector: {
645
- dimmable : '.dimmable',
646
- dimmer : '.ui.dimmer',
647
- content : '.ui.dimmer > .content, .ui.dimmer > .content > .center'
648
- },
649
-
650
- template: {
651
- dimmer: function() {
652
- return $('<div />').attr('class', 'ui dimmer');
653
- }
654
- },
655
-
656
668
  className : {
657
669
  active : 'active',
658
670
  animating : 'animating',
659
671
  dimmable : 'dimmable',
660
672
  dimmed : 'dimmed',
673
+ dimmer : 'dimmer',
661
674
  disabled : 'disabled',
662
675
  hide : 'hide',
663
676
  pageDimmer : 'page',
664
677
  show : 'show'
678
+ },
679
+
680
+ selector: {
681
+ dimmer : '> .ui.dimmer',
682
+ content : '.ui.dimmer > .content, .ui.dimmer > .content > .center'
683
+ },
684
+
685
+ template: {
686
+ dimmer: function() {
687
+ return $('<div />').attr('class', 'ui dimmer');
688
+ }
665
689
  }
666
690
 
667
691
  };
@@ -1,9 +1,9 @@
1
1
  /*!
2
- * # Semantic UI 1.12.3 - Dropdown
2
+ * # Semantic UI - Dropdown
3
3
  * http://github.com/semantic-org/semantic-ui/
4
4
  *
5
5
  *
6
- * Copyright 2014 Contributors
6
+ * Copyright 2015 Contributors
7
7
  * Released under the MIT license
8
8
  * http://opensource.org/licenses/MIT
9
9
  *
@@ -31,25 +31,30 @@ $.fn.dropdown = function(parameters) {
31
31
  ;
32
32
 
33
33
  $allModules
34
- .each(function() {
34
+ .each(function(elementIndex) {
35
35
  var
36
36
  settings = ( $.isPlainObject(parameters) )
37
37
  ? $.extend(true, {}, $.fn.dropdown.settings, parameters)
38
38
  : $.extend({}, $.fn.dropdown.settings),
39
39
 
40
40
  className = settings.className,
41
+ message = settings.message,
41
42
  metadata = settings.metadata,
42
43
  namespace = settings.namespace,
44
+ regExp = settings.regExp,
43
45
  selector = settings.selector,
44
46
  error = settings.error,
47
+ templates = settings.templates,
45
48
 
46
49
  eventNamespace = '.' + namespace,
47
50
  moduleNamespace = 'module-' + namespace,
48
51
 
49
52
  $module = $(this),
53
+ $context = $(settings.context),
50
54
  $text = $module.find(selector.text),
51
55
  $search = $module.find(selector.search),
52
56
  $input = $module.find(selector.input),
57
+ $icon = $module.find(selector.icon),
53
58
 
54
59
  $combo = ($module.prev().find(selector.text).length > 0)
55
60
  ? $module.prev().find(selector.text)
@@ -63,9 +68,12 @@ $.fn.dropdown = function(parameters) {
63
68
  element = this,
64
69
  instance = $module.data(moduleNamespace),
65
70
 
71
+ initialLoad,
72
+ pageLostFocus,
66
73
  elementNamespace,
67
74
  id,
68
- observer,
75
+ selectObserver,
76
+ menuObserver,
69
77
  module
70
78
  ;
71
79
 
@@ -79,12 +87,12 @@ $.fn.dropdown = function(parameters) {
79
87
  }
80
88
  else {
81
89
  module.setup.layout();
90
+ module.refreshData();
82
91
 
83
92
  module.save.defaults();
84
- module.set.selected();
93
+ module.restore.selected();
85
94
 
86
95
  module.create.id();
87
-
88
96
  if(hasTouch) {
89
97
  module.bind.touchEvents();
90
98
  }
@@ -94,6 +102,7 @@ $.fn.dropdown = function(parameters) {
94
102
  module.observeChanges();
95
103
  module.instantiate();
96
104
  }
105
+
97
106
  },
98
107
 
99
108
  instantiate: function() {
@@ -105,7 +114,7 @@ $.fn.dropdown = function(parameters) {
105
114
  },
106
115
 
107
116
  destroy: function() {
108
- module.verbose('Destroying previous dropdown for', $module);
117
+ module.verbose('Destroying previous dropdown', $module);
109
118
  module.remove.tabbable();
110
119
  $module
111
120
  .off(eventNamespace)
@@ -117,55 +126,150 @@ $.fn.dropdown = function(parameters) {
117
126
  $document
118
127
  .off(elementNamespace)
119
128
  ;
129
+ if(selectObserver) {
130
+ selectObserver.disconnect();
131
+ }
132
+ if(menuObserver) {
133
+ menuObserver.disconnect();
134
+ }
120
135
  },
121
136
 
122
137
  observeChanges: function() {
123
138
  if('MutationObserver' in window) {
124
- observer = new MutationObserver(function(mutations) {
125
- if( module.is.selectMutation(mutations) ) {
126
- module.debug('<select> modified, recreating menu');
127
- module.setup.select();
128
- }
129
- else {
130
- module.debug('DOM tree modified, updating selector cache');
131
- module.refresh();
132
- }
139
+ selectObserver = new MutationObserver(function(mutations) {
140
+ module.debug('<select> modified, recreating menu');
141
+ module.setup.select();
133
142
  });
134
- observer.observe(element, {
135
- childList : true,
136
- subtree : true
143
+ menuObserver = new MutationObserver(function(mutations) {
144
+ module.debug('Menu modified, updating selector cache');
145
+ module.refresh();
137
146
  });
138
- module.debug('Setting up mutation observer', observer);
147
+ if(module.has.input()) {
148
+ selectObserver.observe($input[0], {
149
+ childList : true,
150
+ subtree : true
151
+ });
152
+ }
153
+ if(module.has.menu()) {
154
+ menuObserver.observe($menu[0], {
155
+ childList : true,
156
+ subtree : true
157
+ });
158
+ }
159
+ module.debug('Setting up mutation observer', selectObserver, menuObserver);
139
160
  }
140
161
  },
141
162
 
142
163
  create: {
143
164
  id: function() {
144
- id = (Math.random().toString(16) + '000000000').substr(2,8);
165
+ id = (Math.random().toString(16) + '000000000').substr(2, 8);
145
166
  elementNamespace = '.' + id;
146
167
  module.verbose('Creating unique id for element', id);
147
- }
168
+ },
169
+ userChoice: function(values) {
170
+ var
171
+ $userChoices,
172
+ $userChoice,
173
+ isUserValue,
174
+ html
175
+ ;
176
+ values = values || module.get.userValues();
177
+ if(!values) {
178
+ return false;
179
+ }
180
+ values = $.isArray(values)
181
+ ? values
182
+ : [values]
183
+ ;
184
+ $.each(values, function(index, value) {
185
+ if(module.get.item(value) === false) {
186
+ html = settings.templates.addition(value);
187
+ $userChoice = $('<div />')
188
+ .html(html)
189
+ .data(metadata.value, value)
190
+ .addClass(className.addition)
191
+ .addClass(className.item)
192
+ ;
193
+ $userChoices = ($userChoices === undefined)
194
+ ? $userChoice
195
+ : $userChoices.add($userChoice)
196
+ ;
197
+ module.verbose('Creating user choices for value', value, $userChoice);
198
+ }
199
+ });
200
+ return $userChoices;
201
+ },
202
+ userLabels: function(value) {
203
+ var
204
+ userValues = module.get.userValues()
205
+ ;
206
+ if(userValues) {
207
+ module.debug('Adding user labels', userValues);
208
+ $.each(userValues, function(index, value) {
209
+ module.verbose('Adding custom user value');
210
+ module.add.label(value, value);
211
+ });
212
+ }
213
+ },
148
214
  },
149
215
 
150
- search: function() {
151
- var
152
- query
216
+ search: function(query) {
217
+ query = (query !== undefined)
218
+ ? query
219
+ : module.get.query()
153
220
  ;
154
- query = $search.val();
155
-
156
221
  module.verbose('Searching for query', query);
157
222
  module.filter(query);
158
- if(module.is.searchSelection() && module.can.show() ) {
159
- module.show();
223
+ },
224
+
225
+ select: {
226
+ firstUnfiltered: function() {
227
+ module.verbose('Selecting first non-filtered element');
228
+ module.remove.selectedItem();
229
+ $item
230
+ .not(selector.unselectable)
231
+ .eq(0)
232
+ .addClass(className.selected)
233
+ ;
234
+ },
235
+ nextAvailable: function($selected) {
236
+ $selected = $selected.eq(0);
237
+ var
238
+ $nextAvailable = $selected.nextAll(selector.item).not(selector.unselectable).eq(0),
239
+ $prevAvailable = $selected.prevAll(selector.item).not(selector.unselectable).eq(0),
240
+ hasNext = ($nextAvailable.length > 0)
241
+ ;
242
+ if(hasNext) {
243
+ module.verbose('Moving selection to', $nextAvailable);
244
+ $nextAvailable.addClass(className.selected);
245
+ }
246
+ else {
247
+ module.verbose('Moving selection to', $prevAvailable);
248
+ $prevAvailable.addClass(className.selected);
249
+ }
160
250
  }
161
251
  },
162
252
 
163
253
  setup: {
254
+ api: function() {
255
+ var
256
+ apiSettings = {
257
+ debug : settings.debug,
258
+ on : false
259
+ }
260
+ ;
261
+ module.verbose('First request, initializing API');
262
+ $module
263
+ .api(apiSettings)
264
+ ;
265
+ },
164
266
  layout: function() {
165
267
  if( $module.is('select') ) {
166
268
  module.setup.select();
269
+ module.setup.returnedObject();
167
270
  }
168
- if( module.is.search() && !module.is.searchable() ) {
271
+ if( module.is.search() && !module.has.search() ) {
272
+ module.verbose('Adding search input');
169
273
  $search = $('<input />')
170
274
  .addClass(className.search)
171
275
  .insertBefore($text)
@@ -174,6 +278,12 @@ $.fn.dropdown = function(parameters) {
174
278
  if(settings.allowTab) {
175
279
  module.set.tabbable();
176
280
  }
281
+ if($menu.length === 0) {
282
+ $menu = $('<div />')
283
+ .addClass(className.menu)
284
+ .appendTo($module)
285
+ ;
286
+ }
177
287
  },
178
288
  select: function() {
179
289
  var
@@ -188,13 +298,7 @@ $.fn.dropdown = function(parameters) {
188
298
  module.debug('UI dropdown already exists. Creating dropdown menu only');
189
299
  $module = $input.closest(selector.dropdown);
190
300
  $menu = $module.children(selector.menu);
191
- if($menu.length === 0) {
192
- $menu = $('<div />')
193
- .addClass(className.menu)
194
- .appendTo($module)
195
- ;
196
- }
197
- $menu.html( settings.templates.menu( selectValues ));
301
+ module.setup.menu(selectValues);
198
302
  }
199
303
  else {
200
304
  module.debug('Creating entire dropdown from select');
@@ -202,51 +306,79 @@ $.fn.dropdown = function(parameters) {
202
306
  .attr('class', $input.attr('class') )
203
307
  .addClass(className.selection)
204
308
  .addClass(className.dropdown)
205
- .html( settings.templates.dropdown(selectValues) )
309
+ .html( templates.dropdown(selectValues) )
206
310
  .insertBefore($input)
207
311
  ;
208
312
  $input
209
313
  .removeAttr('class')
314
+ .detach()
210
315
  .prependTo($module)
211
316
  ;
212
317
  }
318
+ if($input.is('[multiple]')) {
319
+ module.set.multiple();
320
+ }
213
321
  module.refresh();
214
322
  },
323
+ menu: function(values) {
324
+ $menu.html( templates.menu( values ));
325
+ $item = $menu.find(selector.item);
326
+ },
215
327
  reference: function() {
216
- var
217
- index = $allModules.index($module),
218
- $firstModules,
219
- $lastModules
220
- ;
221
328
  module.debug('Dropdown behavior was called on select, replacing with closest dropdown');
222
329
  // replace module reference
223
330
  $module = $module.parent(selector.dropdown);
224
331
  module.refresh();
225
- // adjust all modules
226
- $firstModules = $allModules.slice(0, index);
227
- $lastModules = $allModules.slice(index + 1);
228
- $allModules = $firstModules.add($module).add($lastModules);
332
+ module.setup.returnedObject();
229
333
  // invoke method in context of current instance
230
334
  if(methodInvoked) {
231
335
  instance = module;
232
336
  module.invoke(query);
233
337
  }
338
+ },
339
+ returnedObject: function() {
340
+ var
341
+ $firstModules = $allModules.slice(0, elementIndex),
342
+ $lastModules = $allModules.slice(elementIndex + 1)
343
+ ;
344
+ // adjust all modules to use correct reference
345
+ $allModules = $firstModules.add($module).add($lastModules);
234
346
  }
235
347
  },
236
348
 
237
349
  refresh: function() {
350
+ module.refreshSelectors();
351
+ module.refreshData();
352
+ },
353
+
354
+ refreshSelectors: function() {
238
355
  module.verbose('Refreshing selector cache');
239
356
  $text = $module.find(selector.text);
240
357
  $search = $module.find(selector.search);
241
358
  $input = $module.find(selector.input);
359
+ $icon = $module.find(selector.icon);
242
360
  $combo = ($module.prev().find(selector.text).length > 0)
243
361
  ? $module.prev().find(selector.text)
244
362
  : $module.prev()
245
363
  ;
246
- $menu = $module.children(selector.menu);
247
- $item = $menu.find(selector.item);
364
+ $menu = $module.children(selector.menu);
365
+ $item = $menu.find(selector.item);
366
+ },
367
+
368
+ refreshData: function() {
369
+ module.verbose('Refreshing cached metadata');
370
+ $item
371
+ .removeData(metadata.text)
372
+ .removeData(metadata.value)
373
+ ;
374
+ $module
375
+ .removeData(metadata.defaultText)
376
+ .removeData(metadata.defaultValue)
377
+ .removeData(metadata.placeholderText)
378
+ ;
248
379
  },
249
380
 
381
+
250
382
  toggle: function() {
251
383
  module.verbose('Toggling menu visibility');
252
384
  if( !module.is.active() ) {
@@ -262,11 +394,13 @@ $.fn.dropdown = function(parameters) {
262
394
  ? callback
263
395
  : function(){}
264
396
  ;
265
- if( module.is.searchSelection() && module.is.allFiltered() ) {
266
- return;
267
- }
268
397
  if( module.can.show() && !module.is.active() ) {
269
398
  module.debug('Showing dropdown');
399
+ if(module.is.multiple()) {
400
+ if(!module.has.search() && module.is.allFiltered()) {
401
+ return true;
402
+ }
403
+ }
270
404
  module.animate.show(function() {
271
405
  if( module.can.click() ) {
272
406
  module.bind.intent();
@@ -297,15 +431,23 @@ $.fn.dropdown = function(parameters) {
297
431
  module.verbose('Finding other dropdowns to hide');
298
432
  $allModules
299
433
  .not($module)
300
- .has(selector.menu + ':visible:not(.' + className.animating + ')')
434
+ .has(selector.menu + '.' + className.visible)
301
435
  .dropdown('hide')
302
436
  ;
303
437
  },
304
438
 
439
+ hideMenu: function() {
440
+ module.verbose('Hiding menu instantaneously');
441
+ module.remove.active();
442
+ module.remove.visible();
443
+ $menu.transition('hide');
444
+ },
445
+
305
446
  hideSubMenus: function() {
306
447
  var
307
- $subMenus = $menu.find(selector.menu)
448
+ $subMenus = $menu.children(selector.item).find(selector.menu)
308
449
  ;
450
+ module.verbose('Hiding sub menus', $subMenus);
309
451
  $subMenus.transition('hide');
310
452
  },
311
453
 
@@ -315,9 +457,14 @@ $.fn.dropdown = function(parameters) {
315
457
  $module
316
458
  .on('keydown' + eventNamespace, module.event.keydown)
317
459
  ;
318
- if( module.is.searchable() ) {
460
+ if( module.has.search() ) {
319
461
  $module
320
- .on(module.get.inputEvent(), selector.search, module.event.input)
462
+ .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input)
463
+ ;
464
+ }
465
+ if( module.is.multiple() ) {
466
+ $document
467
+ .on('keydown' + elementNamespace, module.event.document.keydown)
321
468
  ;
322
469
  }
323
470
  },
@@ -326,7 +473,7 @@ $.fn.dropdown = function(parameters) {
326
473
  if( module.is.searchSelection() ) {
327
474
  // do nothing special yet
328
475
  }
329
- else {
476
+ else if( module.is.single() ) {
330
477
  $module
331
478
  .on('touchstart' + eventNamespace, module.event.test.toggle)
332
479
  ;
@@ -336,20 +483,33 @@ $.fn.dropdown = function(parameters) {
336
483
  ;
337
484
  },
338
485
  mouseEvents: function() {
339
- module.verbose('Mouse detected binding mouse events');
486
+ module.debug('Mouse detected binding mouse events');
487
+ if(module.is.multiple()) {
488
+ $module
489
+ .on('click' + eventNamespace, selector.label, module.event.label.click)
490
+ .on('click' + eventNamespace, selector.remove, module.event.remove.click)
491
+ ;
492
+ }
340
493
  if( module.is.searchSelection() ) {
341
494
  $module
342
- .on('mousedown' + eventNamespace, selector.menu, module.event.menu.activate)
343
- .on('mouseup' + eventNamespace, selector.menu, module.event.menu.deactivate)
495
+ .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown)
496
+ .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup)
497
+ .on('click' + eventNamespace, selector.icon, module.event.icon.click)
344
498
  .on('click' + eventNamespace, selector.search, module.show)
345
- .on('focus' + eventNamespace, selector.search, module.event.searchFocus)
346
- .on('blur' + eventNamespace, selector.search, module.event.searchBlur)
347
- .on('click' + eventNamespace, selector.text, module.event.searchTextFocus)
499
+ .on('focus' + eventNamespace, selector.search, module.event.search.focus)
500
+ .on('blur' + eventNamespace, selector.search, module.event.search.blur)
501
+ .on('click' + eventNamespace, selector.text, module.event.text.focus)
348
502
  ;
503
+ if(module.is.multiple()) {
504
+ $module
505
+ .on('click' + eventNamespace, module.event.click)
506
+ ;
507
+ }
349
508
  }
350
509
  else {
351
510
  if(settings.on == 'click') {
352
511
  $module
512
+ .on('click' + eventNamespace, selector.icon, module.event.icon.click)
353
513
  .on('click' + eventNamespace, module.event.test.toggle)
354
514
  ;
355
515
  }
@@ -406,76 +566,204 @@ $.fn.dropdown = function(parameters) {
406
566
  }
407
567
  },
408
568
 
409
- filter: function(searchTerm) {
569
+ filter: function(query) {
410
570
  var
411
- $results = $(),
412
- escapedTerm = module.escape.regExp(searchTerm),
413
- exactRegExp = new RegExp('^' + escapedTerm, 'igm'),
414
- fullTextRegExp = new RegExp(escapedTerm, 'ig'),
415
- allItemsFiltered
416
- ;
417
- module.verbose('Searching for matching values');
418
- $item
419
- .each(function(){
420
- var
421
- $choice = $(this),
422
- text = String(module.get.choiceText($choice, false)),
423
- value = String(module.get.choiceValue($choice, text))
424
- ;
425
- if( text.match(exactRegExp) || value.match(exactRegExp) ) {
426
- $results = $results.add($choice);
571
+ searchTerm = (query !== undefined)
572
+ ? query
573
+ : module.get.query(),
574
+ afterFiltered = function() {
575
+ if(module.is.multiple()) {
576
+ module.filterActive();
427
577
  }
428
- else if(settings.fullTextSearch) {
429
- if( text.match(fullTextRegExp) || value.match(fullTextRegExp) ) {
430
- $results = $results.add($choice);
578
+ module.select.firstUnfiltered();
579
+ if( module.has.allResultsFiltered() ) {
580
+ if( settings.onNoResults.call(element, searchTerm) ) {
581
+ if(!settings.allowAdditions) {
582
+ module.verbose('All items filtered, showing message', searchTerm);
583
+ module.add.message(message.noResults);
584
+ }
585
+ }
586
+ else {
587
+ module.verbose('All items filtered, hiding dropdown', searchTerm);
588
+ module.hideMenu();
431
589
  }
432
590
  }
433
- })
591
+ else {
592
+ module.remove.message();
593
+ }
594
+ if(settings.allowAdditions) {
595
+ module.add.userSuggestion(query);
596
+ }
597
+ if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) {
598
+ module.show();
599
+ }
600
+ }
601
+ ;
602
+ if(module.has.maxSelections()) {
603
+ return;
604
+ }
605
+ if(settings.apiSettings) {
606
+ if( module.can.useAPI() ) {
607
+ module.queryRemote(searchTerm, function() {
608
+ afterFiltered();
609
+ });
610
+ }
611
+ else {
612
+ module.error(error.noAPI);
613
+ }
614
+ }
615
+ else {
616
+ module.filterItems(searchTerm);
617
+ afterFiltered();
618
+ }
619
+ },
620
+
621
+ queryRemote: function(query, callback) {
622
+ var
623
+ apiSettings = {
624
+ errorDuration : false,
625
+ throttle : settings.throttle,
626
+ cache : 'local',
627
+ urlData : {
628
+ query: query
629
+ },
630
+ onError: function() {
631
+ module.add.message(message.serverError);
632
+ callback();
633
+ },
634
+ onFailure: function() {
635
+ module.add.message(message.serverError);
636
+ callback();
637
+ },
638
+ onSuccess : function(response) {
639
+ module.remove.message();
640
+ module.setup.menu({
641
+ values: response.results
642
+ });
643
+ callback();
644
+ }
645
+ }
434
646
  ;
647
+ if( !$module.api('get request') ) {
648
+ module.setup.api();
649
+ }
650
+ apiSettings = $.extend(true, {}, apiSettings, settings.apiSettings);
651
+ $module
652
+ .api('setting', apiSettings)
653
+ .api('query')
654
+ ;
655
+ },
656
+
657
+ filterItems: function(query) {
658
+ var
659
+ searchTerm = (query !== undefined)
660
+ ? query
661
+ : module.get.query(),
662
+ $results = $(),
663
+ escapedTerm = module.escape.regExp(searchTerm),
664
+ beginsWithRegExp = new RegExp('^' + escapedTerm, 'igm')
665
+ ;
666
+ // avoid loop if we're matching nothing
667
+ if(searchTerm === '') {
668
+ $results = $item;
669
+ }
670
+ else {
671
+ module.verbose('Searching for matching values', searchTerm);
672
+ $item
673
+ .each(function(){
674
+ var
675
+ $choice = $(this),
676
+ text,
677
+ value
678
+ ;
679
+ if(settings.match == 'both' || settings.match == 'text') {
680
+ text = String(module.get.choiceText($choice, false));
681
+ if(text.search(beginsWithRegExp) !== -1) {
682
+ $results = $results.add($choice);
683
+ return true;
684
+ }
685
+ else if(settings.fullTextSearch && module.fuzzySearch(searchTerm, text)) {
686
+ $results = $results.add($choice);
687
+ return true;
688
+ }
689
+ }
690
+ if(settings.match == 'both' || settings.match == 'value') {
691
+ value = String(module.get.choiceValue($choice, text));
692
+
693
+ if(value.search(beginsWithRegExp) !== -1) {
694
+ $results = $results.add($choice);
695
+ return true;
696
+ }
697
+ else if(settings.fullTextSearch && module.fuzzySearch(searchTerm, value)) {
698
+ $results = $results.add($choice);
699
+ return true;
700
+ }
701
+ }
702
+ })
703
+ ;
704
+ }
435
705
 
436
- module.debug('Setting filter', searchTerm);
706
+ module.debug('Showing only matched items', searchTerm);
437
707
  module.remove.filteredItem();
438
708
  $item
439
709
  .not($results)
440
710
  .addClass(className.filtered)
441
711
  ;
712
+ },
442
713
 
443
- module.verbose('Selecting first non-filtered element');
444
- module.remove.selectedItem();
445
- $item
446
- .not('.' + className.filtered)
447
- .eq(0)
448
- .addClass(className.selected)
714
+ fuzzySearch: function(query, term) {
715
+ var
716
+ termLength = term.length,
717
+ queryLength = query.length
449
718
  ;
450
- if( module.is.allFiltered() ) {
451
- module.debug('All items filtered, hiding dropdown', searchTerm);
452
- if(module.is.searchSelection()) {
453
- module.hide();
719
+ query = query.toLowerCase();
720
+ term = term.toLowerCase();
721
+ if(queryLength > termLength) {
722
+ return false;
723
+ }
724
+ if(queryLength === termLength) {
725
+ return (query === term);
726
+ }
727
+ search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) {
728
+ var
729
+ queryCharacter = query.charCodeAt(characterIndex)
730
+ ;
731
+ while(nextCharacterIndex < termLength) {
732
+ if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) {
733
+ continue search;
734
+ }
454
735
  }
455
- settings.onNoResults.call(element, searchTerm);
736
+ return false;
456
737
  }
738
+ return true;
457
739
  },
458
740
 
459
- focusSearch: function() {
460
- if( module.is.search() ) {
461
- $search
462
- .focus()
741
+ filterActive: function() {
742
+ if(settings.useLabels) {
743
+ $item.filter('.' + className.active)
744
+ .addClass(className.filtered)
463
745
  ;
464
746
  }
465
747
  },
466
748
 
749
+ focusSearch: function() {
750
+ if( module.is.search() && !module.is.focusedOnSearch() ) {
751
+ $search[0].focus();
752
+ }
753
+ },
754
+
467
755
  forceSelection: function() {
468
756
  var
469
757
  $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0),
470
- $activeItem = $item.filter('.' + className.active).eq(0),
758
+ $activeItem = $item.not(className.filtered).filter('.' + className.active).eq(0),
471
759
  $selectedItem = ($currentlySelected.length > 0)
472
760
  ? $currentlySelected
473
761
  : $activeItem,
474
762
  hasSelected = ($selectedItem.size() > 0)
475
763
  ;
476
764
  if(hasSelected) {
765
+ module.debug('Forcing partial selection to selected item', $selectedItem);
477
766
  module.event.item.click.call($selectedItem);
478
- module.remove.filteredItem();
479
767
  }
480
768
  else {
481
769
  module.hide();
@@ -483,197 +771,133 @@ $.fn.dropdown = function(parameters) {
483
771
  },
484
772
 
485
773
  event: {
486
- // prevents focus callback from occuring on mousedown
487
- mousedown: function() {
488
- activated = true;
489
- },
490
- mouseup: function() {
491
- activated = false;
492
- },
493
774
  focus: function() {
494
- if(!activated && module.is.hidden()) {
775
+ if(settings.showOnFocus && !activated && module.is.hidden() && !pageLostFocus) {
495
776
  module.show();
496
777
  }
497
778
  },
498
- blur: function(event) {
779
+ click: function(event) {
499
780
  var
500
- pageLostFocus = (document.activeElement === this)
781
+ $target = $(event.target)
501
782
  ;
783
+ // focus search
784
+ if($target.is($module) && !module.is.focusedOnSearch()) {
785
+ module.focusSearch();
786
+ }
787
+ },
788
+ blur: function(event) {
789
+ pageLostFocus = (document.activeElement === this);
502
790
  if(!activated && !pageLostFocus) {
791
+ module.remove.activeLabel();
503
792
  module.hide();
504
793
  }
505
794
  },
506
- searchFocus: function() {
795
+ // prevents focus callback from occuring on mousedown
796
+ mousedown: function() {
507
797
  activated = true;
508
- module.show();
509
798
  },
510
- searchBlur: function(event) {
511
- var
512
- pageLostFocus = (document.activeElement === this)
513
- ;
514
- if(!itemActivated && !pageLostFocus) {
515
- if(settings.forceSelection) {
516
- module.forceSelection();
799
+ mouseup: function() {
800
+ activated = false;
801
+ },
802
+ search: {
803
+ focus: function() {
804
+ activated = true;
805
+ if(module.is.multiple()) {
806
+ module.remove.activeLabel();
517
807
  }
518
- else {
519
- module.hide();
808
+ if(settings.showOnFocus) {
809
+ module.show();
810
+ }
811
+ },
812
+ blur: function(event) {
813
+ pageLostFocus = (document.activeElement === this);
814
+ if(!itemActivated && !pageLostFocus) {
815
+ if(module.is.multiple()) {
816
+ module.remove.activeLabel();
817
+ module.hide();
818
+ }
819
+ else if(settings.forceSelection) {
820
+ module.forceSelection();
821
+ }
822
+ else {
823
+ module.hide();
824
+ }
520
825
  }
521
826
  }
522
827
  },
523
- searchTextFocus: function(event) {
524
- activated = true;
525
- $search.focus();
828
+ icon: {
829
+ click: function(event) {
830
+ module.toggle();
831
+ event.stopPropagation();
832
+ }
833
+ },
834
+ text: {
835
+ focus: function(event) {
836
+ activated = true;
837
+ module.focusSearch();
838
+ }
526
839
  },
527
840
  input: function(event) {
528
- if(module.is.searchSelection()) {
841
+ if(module.is.multiple() || module.is.searchSelection()) {
529
842
  module.set.filtered();
530
843
  }
531
844
  clearTimeout(module.timer);
532
845
  module.timer = setTimeout(module.search, settings.delay.search);
533
846
  },
534
- keydown: function(event) {
535
- var
536
- $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0),
537
- $activeItem = $menu.children('.' + className.active).eq(0),
538
- $selectedItem = ($currentlySelected.length > 0)
539
- ? $currentlySelected
540
- : $activeItem,
541
- $visibleItems = ($selectedItem.length > 0)
542
- ? $selectedItem.siblings(':not(.' + className.filtered +')').andSelf()
543
- : $menu.children(':not(.' + className.filtered +')'),
544
- $subMenu = $selectedItem.children(selector.menu),
545
- $parentMenu = $selectedItem.closest(selector.menu),
546
- isSubMenuItem = $parentMenu[0] !== $menu[0],
547
- inVisibleMenu = $parentMenu.is(':visible'),
548
- pressedKey = event.which,
549
- keys = {
550
- enter : 13,
551
- escape : 27,
552
- leftArrow : 37,
553
- upArrow : 38,
554
- rightArrow : 39,
555
- downArrow : 40
556
- },
557
- hasSubMenu = ($subMenu.length> 0),
558
- hasSelectedItem = ($selectedItem.length > 0),
559
- lastVisibleIndex = ($visibleItems.size() - 1),
560
- $nextItem,
561
- newIndex
562
- ;
563
- // visible menu keyboard shortcuts
564
- if(module.is.visible()) {
565
- // enter (select or sub-menu)
566
- if(pressedKey == keys.enter && hasSelectedItem) {
567
- if(hasSubMenu && !settings.allowCategorySelection) {
568
- module.verbose('Pressed enter on unselectable category, opening sub menu');
569
- pressedKey = keys.rightArrow;
570
- }
571
- else {
572
- module.verbose('Enter key pressed, choosing selected item');
573
- module.event.item.click.call($selectedItem, event);
574
- }
575
- }
576
- // left arrow (hide sub-menu)
577
- if(pressedKey == keys.leftArrow) {
578
- if(isSubMenuItem) {
579
- module.verbose('Left key pressed, closing sub-menu');
580
- module.animate.hide(false, $parentMenu);
581
- $selectedItem
582
- .removeClass(className.selected)
583
- ;
584
- $parentMenu
585
- .closest(selector.item)
586
- .addClass(className.selected)
587
- ;
588
- event.preventDefault();
589
- }
590
- }
591
- // right arrow (show sub-menu)
592
- if(pressedKey == keys.rightArrow) {
593
- if(hasSubMenu) {
594
- module.verbose('Right key pressed, opening sub-menu');
595
- module.animate.show(false, $subMenu);
596
- $selectedItem
597
- .removeClass(className.selected)
598
- ;
599
- $subMenu
600
- .find(selector.item).eq(0)
601
- .addClass(className.selected)
602
- ;
603
- event.preventDefault();
604
- }
847
+ label: {
848
+ click: function(event) {
849
+ var
850
+ $label = $(this),
851
+ $labels = $module.find(selector.label),
852
+ $activeLabels = $labels.filter('.' + className.active),
853
+ $nextActive = $label.nextAll('.' + className.active),
854
+ $prevActive = $label.prevAll('.' + className.active),
855
+ $range = ($nextActive.length > 0)
856
+ ? $label.nextUntil($nextActive).add($activeLabels).add($label)
857
+ : $label.prevUntil($prevActive).add($activeLabels).add($label)
858
+ ;
859
+ if(event.shiftKey) {
860
+ $activeLabels.removeClass(className.active);
861
+ $range.addClass(className.active);
605
862
  }
606
- // up arrow (traverse menu up)
607
- if(pressedKey == keys.upArrow) {
608
- $nextItem = (hasSelectedItem && inVisibleMenu)
609
- ? $selectedItem.prevAll(selector.item + ':not(.' + className.filtered + ')').eq(0)
610
- : $item.eq(0)
611
- ;
612
- if($visibleItems.index( $nextItem ) < 0) {
613
- module.verbose('Up key pressed but reached top of current menu');
614
- return;
615
- }
616
- else {
617
- module.verbose('Up key pressed, changing active item');
618
- $selectedItem
619
- .removeClass(className.selected)
620
- ;
621
- $nextItem
622
- .addClass(className.selected)
623
- ;
624
- module.set.scrollPosition($nextItem);
625
- }
626
- event.preventDefault();
863
+ else if(event.ctrlKey) {
864
+ $label.toggleClass(className.active);
627
865
  }
628
- // down arrow (traverse menu down)
629
- if(pressedKey == keys.downArrow) {
630
- $nextItem = (hasSelectedItem && inVisibleMenu)
631
- ? $nextItem = $selectedItem.nextAll(selector.item + ':not(.' + className.filtered + ')').eq(0)
632
- : $item.eq(0)
633
- ;
634
- if($nextItem.length === 0) {
635
- module.verbose('Down key pressed but reached bottom of current menu');
636
- return;
637
- }
638
- else {
639
- module.verbose('Down key pressed, changing active item');
640
- $item
641
- .removeClass(className.selected)
642
- ;
643
- $nextItem
644
- .addClass(className.selected)
645
- ;
646
- module.set.scrollPosition($nextItem);
647
- }
648
- event.preventDefault();
866
+ else {
867
+ $activeLabels.removeClass(className.active);
868
+ $label.addClass(className.active);
649
869
  }
870
+ settings.onLabelSelect.apply(this, $labels.filter('.' + className.active));
650
871
  }
651
- else {
652
- // enter (open menu)
653
- if(pressedKey == keys.enter) {
654
- module.verbose('Enter key pressed, showing dropdown');
655
- module.show();
656
- }
657
- // escape (close menu)
658
- if(pressedKey == keys.escape) {
659
- module.verbose('Escape key pressed, closing dropdown');
660
- module.hide();
872
+ },
873
+ remove: {
874
+ click: function() {
875
+ var
876
+ $label = $(this).parent()
877
+ ;
878
+ if( $label.hasClass(className.active) ) {
879
+ // remove all selected labels
880
+ module.remove.activeLabels();
661
881
  }
662
- // down arrow (open menu)
663
- if(pressedKey == keys.downArrow) {
664
- module.verbose('Down key pressed, showing dropdown');
665
- module.show();
882
+ else {
883
+ // remove this label only
884
+ module.remove.activeLabels( $label );
666
885
  }
667
886
  }
668
887
  },
669
888
  test: {
670
889
  toggle: function(event) {
671
- if( module.determine.eventInMenu(event, module.toggle) ) {
890
+ var
891
+ toggleBehavior = (module.is.multiple())
892
+ ? module.show
893
+ : module.toggle
894
+ ;
895
+ if( module.determine.eventOnElement(event, toggleBehavior) ) {
672
896
  event.preventDefault();
673
897
  }
674
898
  },
675
899
  touch: function(event) {
676
- module.determine.eventInMenu(event, function() {
900
+ module.determine.eventOnElement(event, function() {
677
901
  if(event.type == 'touchstart') {
678
902
  module.timer = setTimeout(module.hide, settings.delay.touch);
679
903
  }
@@ -687,12 +911,11 @@ $.fn.dropdown = function(parameters) {
687
911
  module.determine.eventInModule(event, module.hide);
688
912
  }
689
913
  },
690
-
691
914
  menu: {
692
- activate: function() {
915
+ mousedown: function() {
693
916
  itemActivated = true;
694
917
  },
695
- deactivate: function() {
918
+ mouseup: function() {
696
919
  itemActivated = false;
697
920
  }
698
921
  },
@@ -735,44 +958,329 @@ $.fn.dropdown = function(parameters) {
735
958
  $subMenu = $choice.find(selector.menu),
736
959
  text = module.get.choiceText($choice),
737
960
  value = module.get.choiceValue($choice, text),
738
- callback = function() {
739
- module.remove.searchTerm();
740
- module.determine.selectAction(text, value);
741
- },
742
961
  hasSubMenu = ($subMenu.length > 0),
743
962
  isBubbledEvent = ($subMenu.find($target).length > 0)
744
963
  ;
745
964
  if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) {
746
- callback();
965
+ if(!settings.useLabels) {
966
+ module.remove.filteredItem();
967
+ module.remove.searchTerm();
968
+ module.set.scrollPosition($choice);
969
+ }
970
+ module.determine.selectAction.call(this, text, value);
747
971
  }
748
972
  }
749
973
  },
750
- resetStyle: function() {
751
- $(this).removeAttr('style');
752
- }
753
- },
754
974
 
755
- determine: {
756
- selectAction: function(text, value) {
757
- module.verbose('Determining action', settings.action);
758
- if( $.isFunction( module.action[settings.action] ) ) {
759
- module.verbose('Triggering preset action', settings.action, text, value);
760
- module.action[ settings.action ](text, value);
761
- }
762
- else if( $.isFunction(settings.action) ) {
763
- module.verbose('Triggering user action', settings.action, text, value);
764
- settings.action(text, value);
765
- }
975
+ document: {
976
+ // label selection should occur even when element has no focus
977
+ keydown: function(event) {
978
+ var
979
+ pressedKey = event.which,
980
+ keys = module.get.shortcutKeys(),
981
+ isShortcutKey = module.is.inObject(pressedKey, keys)
982
+ ;
983
+ if(isShortcutKey) {
984
+ var
985
+ $label = $module.find(selector.label),
986
+ $activeLabel = $label.filter('.' + className.active),
987
+ activeValue = $activeLabel.data('value'),
988
+ labelIndex = $label.index($activeLabel),
989
+ labelCount = $label.length,
990
+ hasActiveLabel = ($activeLabel.length > 0),
991
+ hasMultipleActive = ($activeLabel.length > 1),
992
+ isFirstLabel = (labelIndex === 0),
993
+ isLastLabel = (labelIndex + 1 == labelCount),
994
+ isSearch = module.is.searchSelection(),
995
+ isFocusedOnSearch = module.is.focusedOnSearch(),
996
+ isFocused = module.is.focused(),
997
+ caretAtStart = (isFocusedOnSearch && module.get.caretPosition() === 0),
998
+ $nextLabel
999
+ ;
1000
+ if(isSearch && !hasActiveLabel && !isFocusedOnSearch) {
1001
+ return;
1002
+ }
1003
+
1004
+ if(pressedKey == keys.leftArrow) {
1005
+ // activate previous label
1006
+ if((isFocused || caretAtStart) && !hasActiveLabel) {
1007
+ module.verbose('Selecting previous label');
1008
+ $label.last().addClass(className.active);
1009
+ }
1010
+ else if(hasActiveLabel) {
1011
+ if(!event.shiftKey) {
1012
+ module.verbose('Selecting previous label');
1013
+ $label.removeClass(className.active);
1014
+ }
1015
+ else {
1016
+ module.verbose('Adding previous label to selection');
1017
+ }
1018
+ if(isFirstLabel && !hasMultipleActive) {
1019
+ $activeLabel.addClass(className.active);
1020
+ }
1021
+ else {
1022
+ $activeLabel.prev(selector.siblingLabel)
1023
+ .addClass(className.active)
1024
+ .end()
1025
+ ;
1026
+ }
1027
+ event.preventDefault();
1028
+ }
1029
+ }
1030
+ else if(pressedKey == keys.rightArrow) {
1031
+ // activate first label
1032
+ if(isFocused && !hasActiveLabel) {
1033
+ $label.first().addClass(className.active);
1034
+ }
1035
+ // activate next label
1036
+ if(hasActiveLabel) {
1037
+ if(!event.shiftKey) {
1038
+ module.verbose('Selecting next label');
1039
+ $label.removeClass(className.active);
1040
+ }
1041
+ else {
1042
+ module.verbose('Adding next label to selection');
1043
+ }
1044
+ if(isLastLabel) {
1045
+ if(isSearch) {
1046
+ if(!isFocusedOnSearch) {
1047
+ module.focusSearch();
1048
+ }
1049
+ else {
1050
+ $label.removeClass(className.active);
1051
+ }
1052
+ }
1053
+ else if(hasMultipleActive) {
1054
+ $activeLabel.next(selector.siblingLabel).addClass(className.active);
1055
+ }
1056
+ else {
1057
+ $activeLabel.addClass(className.active);
1058
+ }
1059
+ }
1060
+ else {
1061
+ $activeLabel.next(selector.siblingLabel).addClass(className.active);
1062
+ }
1063
+ event.preventDefault();
1064
+ }
1065
+ }
1066
+ else if(pressedKey == keys.deleteKey || pressedKey == keys.backspace) {
1067
+ if(hasActiveLabel) {
1068
+ module.verbose('Removing active labels');
1069
+ if(isLastLabel) {
1070
+ if(isSearch && !isFocusedOnSearch) {
1071
+ module.focusSearch();
1072
+ }
1073
+ }
1074
+ $activeLabel.last().next(selector.siblingLabel).addClass(className.active);
1075
+ module.remove.activeLabels($activeLabel);
1076
+ event.preventDefault();
1077
+ }
1078
+ else if(caretAtStart && !hasActiveLabel && pressedKey == keys.backspace) {
1079
+ module.verbose('Removing last label on input backspace');
1080
+ $activeLabel = $label.last().addClass(className.active);
1081
+ module.remove.activeLabels($activeLabel);
1082
+ }
1083
+ }
1084
+ else {
1085
+ $activeLabel.removeClass(className.active);
1086
+ }
1087
+ }
1088
+ }
1089
+ },
1090
+
1091
+ keydown: function(event) {
1092
+ var
1093
+ pressedKey = event.which,
1094
+ keys = module.get.shortcutKeys(),
1095
+ isShortcutKey = module.is.inObject(pressedKey, keys)
1096
+ ;
1097
+ if(isShortcutKey) {
1098
+ var
1099
+ $currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0),
1100
+ $activeItem = $menu.children('.' + className.active).eq(0),
1101
+ $selectedItem = ($currentlySelected.length > 0)
1102
+ ? $currentlySelected
1103
+ : $activeItem,
1104
+ $visibleItems = ($selectedItem.length > 0)
1105
+ ? $selectedItem.siblings(':not(.' + className.filtered +')').andSelf()
1106
+ : $menu.children(':not(.' + className.filtered +')'),
1107
+ $subMenu = $selectedItem.children(selector.menu),
1108
+ $parentMenu = $selectedItem.closest(selector.menu),
1109
+ inVisibleMenu = ($parentMenu.hasClass(className.visible) || $parentMenu.hasClass(className.animating) || $parentMenu.parent(selector.menu).length > 0),
1110
+ hasSubMenu = ($subMenu.length> 0),
1111
+ hasSelectedItem = ($selectedItem.length > 0),
1112
+ selectedIsSelectable = ($selectedItem.not(selector.unselectable).length > 0),
1113
+ $nextItem,
1114
+ isSubMenuItem,
1115
+ newIndex
1116
+ ;
1117
+
1118
+ // visible menu keyboard shortcuts
1119
+ if( module.is.visible() ) {
1120
+
1121
+ // enter (select or open sub-menu)
1122
+ if(pressedKey == keys.enter || pressedKey == keys.delimiter) {
1123
+ if(pressedKey == keys.enter && hasSelectedItem && hasSubMenu && !settings.allowCategorySelection) {
1124
+ module.verbose('Pressed enter on unselectable category, opening sub menu');
1125
+ pressedKey = keys.rightArrow;
1126
+ }
1127
+ else if(selectedIsSelectable) {
1128
+ module.verbose('Selecting item from keyboard shortcut', $selectedItem);
1129
+ module.event.item.click.call($selectedItem, event);
1130
+ if(module.is.searchSelection()) {
1131
+ module.remove.searchTerm();
1132
+ }
1133
+ }
1134
+ event.preventDefault();
1135
+ }
1136
+
1137
+ // left arrow (hide sub-menu)
1138
+ if(pressedKey == keys.leftArrow) {
1139
+
1140
+ isSubMenuItem = ($parentMenu[0] !== $menu[0]);
1141
+
1142
+ if(isSubMenuItem) {
1143
+ module.verbose('Left key pressed, closing sub-menu');
1144
+ module.animate.hide(false, $parentMenu);
1145
+ $selectedItem
1146
+ .removeClass(className.selected)
1147
+ ;
1148
+ $parentMenu
1149
+ .closest(selector.item)
1150
+ .addClass(className.selected)
1151
+ ;
1152
+ event.preventDefault();
1153
+ }
1154
+ }
1155
+
1156
+ // right arrow (show sub-menu)
1157
+ if(pressedKey == keys.rightArrow) {
1158
+ if(hasSubMenu) {
1159
+ module.verbose('Right key pressed, opening sub-menu');
1160
+ module.animate.show(false, $subMenu);
1161
+ $selectedItem
1162
+ .removeClass(className.selected)
1163
+ ;
1164
+ $subMenu
1165
+ .find(selector.item).eq(0)
1166
+ .addClass(className.selected)
1167
+ ;
1168
+ event.preventDefault();
1169
+ }
1170
+ }
1171
+
1172
+ // up arrow (traverse menu up)
1173
+ if(pressedKey == keys.upArrow) {
1174
+ $nextItem = (hasSelectedItem && inVisibleMenu)
1175
+ ? $selectedItem.prevAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
1176
+ : $item.eq(0)
1177
+ ;
1178
+ if($visibleItems.index( $nextItem ) < 0) {
1179
+ module.verbose('Up key pressed but reached top of current menu');
1180
+ event.preventDefault();
1181
+ return;
1182
+ }
1183
+ else {
1184
+ module.verbose('Up key pressed, changing active item');
1185
+ $selectedItem
1186
+ .removeClass(className.selected)
1187
+ ;
1188
+ $nextItem
1189
+ .addClass(className.selected)
1190
+ ;
1191
+ module.set.scrollPosition($nextItem);
1192
+ }
1193
+ event.preventDefault();
1194
+ }
1195
+
1196
+ // down arrow (traverse menu down)
1197
+ if(pressedKey == keys.downArrow) {
1198
+ $nextItem = (hasSelectedItem && inVisibleMenu)
1199
+ ? $nextItem = $selectedItem.nextAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
1200
+ : $item.eq(0)
1201
+ ;
1202
+ if($nextItem.length === 0) {
1203
+ module.verbose('Down key pressed but reached bottom of current menu');
1204
+ event.preventDefault();
1205
+ return;
1206
+ }
1207
+ else {
1208
+ module.verbose('Down key pressed, changing active item');
1209
+ $item
1210
+ .removeClass(className.selected)
1211
+ ;
1212
+ $nextItem
1213
+ .addClass(className.selected)
1214
+ ;
1215
+ module.set.scrollPosition($nextItem);
1216
+ }
1217
+ event.preventDefault();
1218
+ }
1219
+
1220
+ // page down (show next page)
1221
+ if(pressedKey == keys.pageUp) {
1222
+ module.scrollPage('up');
1223
+ event.preventDefault();
1224
+ }
1225
+ if(pressedKey == keys.pageDown) {
1226
+ module.scrollPage('down');
1227
+ event.preventDefault();
1228
+ }
1229
+
1230
+ // escape (close menu)
1231
+ if(pressedKey == keys.escape) {
1232
+ module.verbose('Escape key pressed, closing dropdown');
1233
+ module.hide();
1234
+ }
1235
+
1236
+ }
1237
+ else {
1238
+ // delimiter key
1239
+ if(pressedKey == keys.delimiter) {
1240
+ event.preventDefault();
1241
+ }
1242
+ // down arrow (open menu)
1243
+ if(pressedKey == keys.downArrow) {
1244
+ module.verbose('Down key pressed, showing dropdown');
1245
+ module.show();
1246
+ event.preventDefault();
1247
+ }
1248
+ }
1249
+ }
1250
+ else {
1251
+ if( module.is.selection() && !module.is.search() ) {
1252
+ module.set.selectedLetter( String.fromCharCode(pressedKey) );
1253
+ }
1254
+ }
1255
+ }
1256
+ },
1257
+
1258
+ determine: {
1259
+ selectAction: function(text, value) {
1260
+ module.verbose('Determining action', settings.action);
1261
+ if( $.isFunction( module.action[settings.action] ) ) {
1262
+ module.verbose('Triggering preset action', settings.action, text, value);
1263
+ module.action[ settings.action ].call(this, text, value);
1264
+ }
1265
+ else if( $.isFunction(settings.action) ) {
1266
+ module.verbose('Triggering user action', settings.action, text, value);
1267
+ settings.action.call(this, text, value);
1268
+ }
766
1269
  else {
767
1270
  module.error(error.action, settings.action);
768
1271
  }
769
1272
  },
770
1273
  eventInModule: function(event, callback) {
1274
+ var
1275
+ $target = $(event.target),
1276
+ inDocument = ($target.closest(document.documentElement).length > 0),
1277
+ inModule = ($target.closest($module).length > 0)
1278
+ ;
771
1279
  callback = $.isFunction(callback)
772
1280
  ? callback
773
1281
  : function(){}
774
1282
  ;
775
- if( $(event.target).closest($module).length === 0 ) {
1283
+ if(inDocument && !inModule) {
776
1284
  module.verbose('Triggering event', callback);
777
1285
  callback();
778
1286
  return true;
@@ -782,12 +1290,17 @@ $.fn.dropdown = function(parameters) {
782
1290
  return false;
783
1291
  }
784
1292
  },
785
- eventInMenu: function(event, callback) {
1293
+ eventOnElement: function(event, callback) {
1294
+ var
1295
+ $target = $(event.target),
1296
+ notOnLabel = ($target.closest(selector.siblingLabel).length === 0),
1297
+ notInMenu = ($target.closest($menu).length === 0)
1298
+ ;
786
1299
  callback = $.isFunction(callback)
787
1300
  ? callback
788
1301
  : function(){}
789
1302
  ;
790
- if( $(event.target).closest($menu).length === 0 ) {
1303
+ if(notOnLabel && notInMenu) {
791
1304
  module.verbose('Triggering event', callback);
792
1305
  callback();
793
1306
  return true;
@@ -808,21 +1321,18 @@ $.fn.dropdown = function(parameters) {
808
1321
  ? value
809
1322
  : text
810
1323
  ;
811
- module.set.selected(value);
812
- module.hide(function() {
813
- module.remove.filteredItem();
814
- });
1324
+ module.set.selected(value, $(this));
1325
+ if(module.is.multiple() && !module.is.allFiltered()) {
1326
+ return;
1327
+ }
1328
+ else {
1329
+ module.hideAndClear();
1330
+ }
815
1331
  },
816
1332
 
817
1333
  select: function(text, value) {
818
- value = (value !== undefined)
819
- ? value
820
- : text
821
- ;
822
- module.set.selected(value);
823
- module.hide(function() {
824
- module.remove.filteredItem();
825
- });
1334
+ // mimics action.activate but does not select text
1335
+ module.action.activate.call(this);
826
1336
  },
827
1337
 
828
1338
  combo: function(text, value) {
@@ -830,16 +1340,12 @@ $.fn.dropdown = function(parameters) {
830
1340
  ? value
831
1341
  : text
832
1342
  ;
833
- module.set.selected(value);
834
- module.hide(function() {
835
- module.remove.filteredItem();
836
- });
1343
+ module.set.selected(value, $(this));
1344
+ module.hideAndClear();
837
1345
  },
838
1346
 
839
1347
  hide: function() {
840
- module.hide(function() {
841
- module.remove.filteredItem();
842
- });
1348
+ module.hideAndClear();
843
1349
  }
844
1350
 
845
1351
  },
@@ -851,18 +1357,134 @@ $.fn.dropdown = function(parameters) {
851
1357
  text: function() {
852
1358
  return $text.text();
853
1359
  },
1360
+ query: function() {
1361
+ return $.trim($search.val());
1362
+ },
1363
+ searchWidth: function(characterCount) {
1364
+ return (characterCount * settings.glyphWidth) + 'em';
1365
+ },
1366
+ selectionCount: function() {
1367
+ var
1368
+ values = module.get.values()
1369
+ ;
1370
+ return ( module.is.multiple() )
1371
+ ? $.isArray(values)
1372
+ ? values.length
1373
+ : 0
1374
+ : (module.get.value() !== '')
1375
+ ? 1
1376
+ : 0
1377
+ ;
1378
+ },
1379
+ transition: function($subMenu) {
1380
+ return (settings.transition == 'auto')
1381
+ ? module.is.upward($subMenu)
1382
+ ? 'slide up'
1383
+ : 'slide down'
1384
+ : settings.transition
1385
+ ;
1386
+ },
1387
+ userValues: function() {
1388
+ var
1389
+ values = module.get.values()
1390
+ ;
1391
+ if(!values) {
1392
+ return false;
1393
+ }
1394
+ values = $.isArray(values)
1395
+ ? values
1396
+ : [values]
1397
+ ;
1398
+ return $.grep(values, function(value) {
1399
+ return (module.get.item(value) === false);
1400
+ });
1401
+ },
1402
+ uniqueArray: function(array) {
1403
+ return $.grep(array, function (value, index) {
1404
+ return $.inArray(value, array) === index;
1405
+ });
1406
+ },
1407
+ caretPosition: function() {
1408
+ var
1409
+ input = $search.get(0),
1410
+ range,
1411
+ rangeLength
1412
+ ;
1413
+ if('selectionStart' in input) {
1414
+ return input.selectionStart;
1415
+ }
1416
+ else if (document.selection) {
1417
+ input.focus();
1418
+ range = document.selection.createRange();
1419
+ rangeLength = range.text.length;
1420
+ range.moveStart('character', -input.value.length);
1421
+ return range.text.length - rangeLength;
1422
+ }
1423
+ },
1424
+ shortcutKeys: function() {
1425
+ return {
1426
+ backspace : 8,
1427
+ delimiter : 188, // comma
1428
+ deleteKey : 46,
1429
+ enter : 13,
1430
+ escape : 27,
1431
+ pageUp : 33,
1432
+ pageDown : 34,
1433
+ leftArrow : 37,
1434
+ upArrow : 38,
1435
+ rightArrow : 39,
1436
+ downArrow : 40
1437
+ };
1438
+ },
854
1439
  value: function() {
855
1440
  return ($input.length > 0)
856
1441
  ? $input.val()
857
1442
  : $module.data(metadata.value)
858
1443
  ;
859
1444
  },
1445
+ values: function() {
1446
+ var
1447
+ value = module.get.value()
1448
+ ;
1449
+ if(value === '') {
1450
+ return '';
1451
+ }
1452
+ return (!$input.is('select') && module.is.multiple())
1453
+ ? typeof value == 'string'
1454
+ ? value.split(settings.delimiter)
1455
+ : ''
1456
+ : value
1457
+ ;
1458
+ },
1459
+ remoteValues: function() {
1460
+ var
1461
+ values = module.get.values(),
1462
+ remoteValues = false
1463
+ ;
1464
+ if(values) {
1465
+ if(typeof values == 'string') {
1466
+ values = [values];
1467
+ }
1468
+ remoteValues = {};
1469
+ $.each(values, function(index, value) {
1470
+ var
1471
+ name = module.read.remoteData(value)
1472
+ ;
1473
+ module.verbose('Restoring value from session data', name, value);
1474
+ remoteValues[value] = (name)
1475
+ ? name
1476
+ : value
1477
+ ;
1478
+ });
1479
+ }
1480
+ return remoteValues;
1481
+ },
860
1482
  choiceText: function($choice, preserveHTML) {
861
1483
  preserveHTML = (preserveHTML !== undefined)
862
1484
  ? preserveHTML
863
1485
  : settings.preserveHTML
864
1486
  ;
865
- if($choice !== undefined) {
1487
+ if($choice) {
866
1488
  if($choice.find(selector.menu).length > 0) {
867
1489
  module.verbose('Retreiving text of element with sub-menu');
868
1490
  $choice = $choice.clone();
@@ -879,11 +1501,14 @@ $.fn.dropdown = function(parameters) {
879
1501
  },
880
1502
  choiceValue: function($choice, choiceText) {
881
1503
  choiceText = choiceText || module.get.choiceText($choice);
1504
+ if(!$choice) {
1505
+ return false;
1506
+ }
882
1507
  return ($choice.data(metadata.value) !== undefined)
883
1508
  ? $choice.data(metadata.value)
884
1509
  : (typeof choiceText === 'string')
885
1510
  ? choiceText.toLowerCase().trim()
886
- : choiceText.trim()
1511
+ : choiceText
887
1512
  ;
888
1513
  },
889
1514
  inputEvent: function() {
@@ -904,39 +1529,41 @@ $.fn.dropdown = function(parameters) {
904
1529
  var
905
1530
  select = {}
906
1531
  ;
907
- select.values = (settings.sortSelect)
908
- ? {} // properties will be sorted in object when re-accessed
909
- : [] // properties will keep original order in array
910
- ;
1532
+ select.values = [];
911
1533
  $module
912
1534
  .find('option')
913
1535
  .each(function() {
914
1536
  var
915
- name = $(this).html(),
916
- value = ( $(this).attr('value') !== undefined )
917
- ? $(this).attr('value')
1537
+ $option = $(this),
1538
+ name = $option.html(),
1539
+ disabled = $option.attr('disabled'),
1540
+ value = ( $option.attr('value') !== undefined )
1541
+ ? $option.attr('value')
918
1542
  : name
919
1543
  ;
920
- if(value === '') {
1544
+ if(settings.placeholder === 'auto' && value === '') {
921
1545
  select.placeholder = name;
922
1546
  }
923
1547
  else {
924
- if(settings.sortSelect) {
925
- select.values[value] = {
926
- name : name,
927
- value : value
928
- };
929
- }
930
- else {
931
- select.values.push({
932
- name: name,
933
- value: value
934
- });
935
- }
1548
+ select.values.push({
1549
+ name : name,
1550
+ value : value,
1551
+ disabled : disabled
1552
+ });
936
1553
  }
937
1554
  })
938
1555
  ;
1556
+ if(settings.placeholder && settings.placeholder !== 'auto') {
1557
+ module.debug('Setting placeholder value to', settings.placeholder);
1558
+ select.placeholder = settings.placeholder;
1559
+ }
939
1560
  if(settings.sortSelect) {
1561
+ select.values.sort(function(a, b) {
1562
+ return (a.name > b.name)
1563
+ ? 1
1564
+ : -1
1565
+ ;
1566
+ });
940
1567
  module.debug('Retrieved and sorted values from select', select);
941
1568
  }
942
1569
  else {
@@ -947,21 +1574,51 @@ $.fn.dropdown = function(parameters) {
947
1574
  activeItem: function() {
948
1575
  return $item.filter('.' + className.active);
949
1576
  },
1577
+ selectedItem: function() {
1578
+ var
1579
+ $selectedItem = $item.not(selector.unselectable).filter('.' + className.selected)
1580
+ ;
1581
+ return ($selectedItem.length > 0)
1582
+ ? $selectedItem
1583
+ : $item.eq(0)
1584
+ ;
1585
+ },
1586
+ itemWithAdditions: function(value) {
1587
+ var
1588
+ $items = module.get.item(value),
1589
+ $userItems = module.create.userChoice(value),
1590
+ hasUserItems = ($userItems && $userItems.length > 0)
1591
+ ;
1592
+ if(hasUserItems) {
1593
+ $items = ($items.length > 0)
1594
+ ? $items.add($userItems)
1595
+ : $userItems
1596
+ ;
1597
+ }
1598
+ return $items;
1599
+ },
950
1600
  item: function(value, strict) {
951
1601
  var
952
- $selectedItem = false
1602
+ $selectedItem = false,
1603
+ shouldSearch,
1604
+ isMultiple
953
1605
  ;
954
1606
  value = (value !== undefined)
955
1607
  ? value
956
- : ( module.get.value() !== undefined)
957
- ? module.get.value()
1608
+ : ( module.get.values() !== undefined)
1609
+ ? module.get.values()
958
1610
  : module.get.text()
959
1611
  ;
960
- strict = (value === '' || value === 0)
1612
+ shouldSearch = (isMultiple)
1613
+ ? (value.length > 0)
1614
+ : (value !== undefined && value !== '' && value !== null)
1615
+ ;
1616
+ isMultiple = (module.is.multiple() && $.isArray(value));
1617
+ strict = (value === '' || value === 0)
961
1618
  ? true
962
1619
  : strict || false
963
1620
  ;
964
- if(value !== undefined) {
1621
+ if(shouldSearch) {
965
1622
  $item
966
1623
  .each(function() {
967
1624
  var
@@ -969,41 +1626,69 @@ $.fn.dropdown = function(parameters) {
969
1626
  optionText = module.get.choiceText($choice),
970
1627
  optionValue = module.get.choiceValue($choice, optionText)
971
1628
  ;
972
- if(strict) {
973
- module.verbose('Ambiguous dropdown value using strict type check', $choice, value);
974
- if( optionValue === value ) {
975
- $selectedItem = $(this);
976
- return true;
1629
+ // safe early exit
1630
+ if(optionValue === null || optionValue === undefined) {
1631
+ return;
1632
+ }
1633
+ if(isMultiple) {
1634
+ if($.inArray(optionValue.toString(), value) !== -1 || $.inArray(optionText, value) !== -1) {
1635
+ $selectedItem = ($selectedItem)
1636
+ ? $selectedItem.add($choice)
1637
+ : $choice
1638
+ ;
977
1639
  }
978
- else if( !$selectedItem && optionText === value ) {
979
- $selectedItem = $(this);
1640
+ }
1641
+ else if(strict) {
1642
+ module.verbose('Ambiguous dropdown value using strict type check', $choice, value);
1643
+ if( optionValue === value || optionText === value) {
1644
+ $selectedItem = $choice;
980
1645
  return true;
981
1646
  }
982
1647
  }
983
1648
  else {
984
- if( optionValue == value ) {
1649
+ if( optionValue.toString() == value.toString() || optionText == value) {
985
1650
  module.verbose('Found select item by value', optionValue, value);
986
- $selectedItem = $(this);
987
- return true;
988
- }
989
- else if( !$selectedItem && optionText == value ) {
990
- module.verbose('Found select item by text', optionText, value);
991
- $selectedItem = $(this);
1651
+ $selectedItem = $choice;
992
1652
  return true;
993
1653
  }
994
1654
  }
995
1655
  })
996
1656
  ;
997
1657
  }
998
- else {
999
- value = module.get.text();
1658
+ return $selectedItem;
1659
+ }
1660
+ },
1661
+
1662
+ check: {
1663
+ maxSelections: function(selectionCount) {
1664
+ if(settings.maxSelections) {
1665
+ selectionCount = (selectionCount !== undefined)
1666
+ ? selectionCount
1667
+ : module.get.selectionCount()
1668
+ ;
1669
+ if(selectionCount >= settings.maxSelections) {
1670
+ module.debug('Maximum selection count reached');
1671
+ $item.addClass(className.filtered);
1672
+ module.add.message(message.maxSelections);
1673
+ return true;
1674
+ }
1675
+ else {
1676
+ module.verbose('No longer at maximum selection count');
1677
+ module.remove.message();
1678
+ module.remove.filteredItem();
1679
+ if(module.is.searchSelection()) {
1680
+ module.filterItems();
1681
+ }
1682
+ return false;
1683
+ }
1000
1684
  }
1001
- return $selectedItem || false;
1685
+ return true;
1002
1686
  }
1003
1687
  },
1004
1688
 
1005
1689
  restore: {
1006
1690
  defaults: function() {
1691
+ module.clear();
1007
1692
  module.restore.defaultText();
1008
1693
  module.restore.defaultValue();
1009
1694
  },
@@ -1021,14 +1706,87 @@ $.fn.dropdown = function(parameters) {
1021
1706
  ;
1022
1707
  if(defaultValue !== undefined) {
1023
1708
  module.debug('Restoring default value', defaultValue);
1024
- if(defaultValue.length) {
1025
- module.set.selected(defaultValue);
1709
+ if(defaultValue !== '') {
1710
+ module.set.value(defaultValue);
1711
+ module.set.selected();
1026
1712
  }
1027
1713
  else {
1028
1714
  module.remove.activeItem();
1029
1715
  module.remove.selectedItem();
1030
1716
  }
1031
1717
  }
1718
+ },
1719
+ labels: function() {
1720
+ if(settings.allowAdditions) {
1721
+ if(!settings.useLabels) {
1722
+ module.error(error.labels);
1723
+ settings.useLabels = true;
1724
+ }
1725
+ module.debug('Restoring selected values');
1726
+ module.create.userLabels();
1727
+ }
1728
+ module.check.maxSelections();
1729
+ },
1730
+ selected: function() {
1731
+ module.restore.values();
1732
+ if(module.is.multiple()) {
1733
+ module.debug('Restoring previously selected values and labels');
1734
+ module.restore.labels();
1735
+ }
1736
+ else {
1737
+ module.debug('Restoring previously selected values');
1738
+ }
1739
+ },
1740
+ values: function() {
1741
+ // prevents callbacks from occuring on initial load
1742
+ module.set.initialLoad();
1743
+ if(settings.apiSettings) {
1744
+ if(settings.saveRemoteData) {
1745
+ module.restore.remoteValues();
1746
+ }
1747
+ else {
1748
+ module.clearValue();
1749
+ }
1750
+ }
1751
+ else {
1752
+ module.set.selected();
1753
+ }
1754
+ module.remove.initialLoad();
1755
+ },
1756
+ remoteValues: function() {
1757
+ var
1758
+ values = module.get.remoteValues()
1759
+ ;
1760
+ module.debug('Recreating selected from session data', values);
1761
+ if(values) {
1762
+ if( module.is.single() ) {
1763
+ $.each(values, function(value, name) {
1764
+ module.set.text(name);
1765
+ });
1766
+ }
1767
+ else {
1768
+ $.each(values, function(value, name) {
1769
+ module.add.label(value, name);
1770
+ });
1771
+ }
1772
+ }
1773
+ }
1774
+ },
1775
+
1776
+ read: {
1777
+ remoteData: function(value) {
1778
+ var
1779
+ name
1780
+ ;
1781
+ if(window.Storage === undefined) {
1782
+ module.error(error.noStorage);
1783
+ return;
1784
+ }
1785
+ name = sessionStorage.getItem(value);
1786
+ return (name !== undefined)
1787
+ ? name
1788
+ : false
1789
+ ;
1032
1790
  }
1033
1791
  },
1034
1792
 
@@ -1039,68 +1797,168 @@ $.fn.dropdown = function(parameters) {
1039
1797
  module.save.defaultValue();
1040
1798
  },
1041
1799
  defaultValue: function() {
1042
- $module.data(metadata.defaultValue, module.get.value() );
1800
+ var
1801
+ value = module.get.value()
1802
+ ;
1803
+ module.verbose('Saving default value as', value);
1804
+ $module.data(metadata.defaultValue, value);
1043
1805
  },
1044
1806
  defaultText: function() {
1045
- $module.data(metadata.defaultText, $text.text() );
1807
+ var
1808
+ text = module.get.text()
1809
+ ;
1810
+ module.verbose('Saving default text as', text);
1811
+ $module.data(metadata.defaultText, text);
1046
1812
  },
1047
1813
  placeholderText: function() {
1814
+ var
1815
+ text
1816
+ ;
1048
1817
  if($text.hasClass(className.placeholder)) {
1049
- $module.data(metadata.placeholderText, $text.text());
1818
+ text = module.get.text();
1819
+ module.verbose('Saving placeholder text as', text);
1820
+ $module.data(metadata.placeholderText, text);
1050
1821
  }
1822
+ },
1823
+ remoteData: function(name, value) {
1824
+ if(window.Storage === undefined) {
1825
+ module.error(error.noStorage);
1826
+ return;
1827
+ }
1828
+ module.verbose('Saving remote data to session storage', value, name);
1829
+ sessionStorage.setItem(value, name);
1051
1830
  }
1052
1831
  },
1053
1832
 
1054
1833
  clear: function() {
1834
+ if(module.is.multiple()) {
1835
+ module.remove.labels();
1836
+ }
1837
+ else {
1838
+ module.remove.activeItem();
1839
+ module.remove.selectedItem();
1840
+ }
1841
+ module.set.placeholderText();
1842
+ module.clearValue();
1843
+ },
1844
+
1845
+ clearValue: function() {
1846
+ module.set.value('');
1847
+ },
1848
+
1849
+ scrollPage: function(direction, $selectedItem) {
1055
1850
  var
1056
- placeholderText = $module.data(metadata.placeholderText)
1851
+ $selectedItem = $selectedItem || module.get.selectedItem(),
1852
+ $menu = $selectedItem.closest(selector.menu),
1853
+ menuHeight = $menu.outerHeight(),
1854
+ currentScroll = $menu.scrollTop(),
1855
+ itemHeight = $item.eq(0).outerHeight(),
1856
+ itemsPerPage = Math.floor(menuHeight / itemHeight),
1857
+ maxScroll = $menu.prop('scrollHeight'),
1858
+ newScroll = (direction == 'up')
1859
+ ? currentScroll - (itemHeight * itemsPerPage)
1860
+ : currentScroll + (itemHeight * itemsPerPage),
1861
+ $selectableItem = $item.not(selector.unselectable),
1862
+ isWithinRange,
1863
+ $nextSelectedItem,
1864
+ elementIndex
1057
1865
  ;
1058
- module.set.text(placeholderText);
1059
- module.set.value('');
1060
- module.remove.activeItem();
1061
- module.remove.selectedItem();
1062
- $text.addClass(className.placeholder);
1866
+ elementIndex = (direction == 'up')
1867
+ ? $selectableItem.index($selectedItem) - itemsPerPage
1868
+ : $selectableItem.index($selectedItem) + itemsPerPage
1869
+ ;
1870
+ isWithinRange = (direction == 'up')
1871
+ ? (elementIndex >= 0)
1872
+ : (elementIndex < $selectableItem.length)
1873
+ ;
1874
+ $nextSelectedItem = (isWithinRange)
1875
+ ? $selectableItem.eq(elementIndex)
1876
+ : (direction == 'up')
1877
+ ? $selectableItem.first()
1878
+ : $selectableItem.last()
1879
+ ;
1880
+ if($nextSelectedItem.length > 0) {
1881
+ module.debug('Scrolling page', direction, $nextSelectedItem);
1882
+ $selectedItem
1883
+ .removeClass(className.selected)
1884
+ ;
1885
+ $nextSelectedItem
1886
+ .addClass(className.selected)
1887
+ ;
1888
+ $menu
1889
+ .scrollTop(newScroll)
1890
+ ;
1891
+ }
1063
1892
  },
1064
1893
 
1065
1894
  set: {
1066
1895
  filtered: function() {
1067
1896
  var
1068
- searchValue = $search.val(),
1069
- hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0)
1897
+ isMultiple = module.is.multiple(),
1898
+ isSearch = module.is.searchSelection(),
1899
+ isSearchMultiple = (isMultiple && isSearch),
1900
+ searchValue = (isSearch)
1901
+ ? module.get.query()
1902
+ : '',
1903
+ hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0),
1904
+ searchWidth = module.get.searchWidth(searchValue.length),
1905
+ valueIsSet = searchValue !== ''
1070
1906
  ;
1071
- if(hasSearchValue) {
1907
+ if(isMultiple && hasSearchValue) {
1908
+ module.verbose('Adjusting input width', searchWidth, settings.glyphWidth);
1909
+ $search.css('width', searchWidth);
1910
+ }
1911
+ if(hasSearchValue || (isSearchMultiple && valueIsSet)) {
1912
+ module.verbose('Hiding placeholder text');
1072
1913
  $text.addClass(className.filtered);
1073
1914
  }
1074
- else {
1915
+ else if(!isMultiple || (isSearchMultiple && !valueIsSet)) {
1916
+ module.verbose('Showing placeholder text');
1075
1917
  $text.removeClass(className.filtered);
1076
1918
  }
1077
1919
  },
1920
+ loading: function() {
1921
+ $module.addClass(className.loading);
1922
+ },
1923
+ placeholderText: function(text) {
1924
+ text = text || $module.data(metadata.placeholderText);
1925
+ if(text) {
1926
+ module.debug('Restoring placeholder text');
1927
+ module.set.text(text);
1928
+ $text.addClass(className.placeholder);
1929
+ }
1930
+ },
1078
1931
  tabbable: function() {
1079
- if( module.is.searchable() ) {
1080
- module.debug('Searchable dropdown initialized');
1932
+ if( module.has.search() ) {
1933
+ module.debug('Added tabindex to searchable dropdown');
1081
1934
  $search
1082
1935
  .val('')
1083
1936
  .attr('tabindex', 0)
1084
1937
  ;
1085
1938
  $menu
1086
- .attr('tabindex', '-1')
1939
+ .attr('tabindex', -1)
1087
1940
  ;
1088
1941
  }
1089
1942
  else {
1090
- module.debug('Simple selection dropdown initialized');
1943
+ module.debug('Added tabindex to dropdown');
1091
1944
  if(!$module.attr('tabindex') ) {
1092
1945
  $module
1093
1946
  .attr('tabindex', 0)
1094
1947
  ;
1095
1948
  $menu
1096
- .attr('tabindex', '-1')
1949
+ .attr('tabindex', -1)
1097
1950
  ;
1098
1951
  }
1099
1952
  }
1100
1953
  },
1954
+ initialLoad: function() {
1955
+ module.verbose('Setting initial load');
1956
+ initialLoad = true;
1957
+ },
1101
1958
  scrollPosition: function($item, forceScroll) {
1102
1959
  var
1103
1960
  edgeTolerance = 5,
1961
+ $menu,
1104
1962
  hasActive,
1105
1963
  offset,
1106
1964
  itemHeight,
@@ -1112,100 +1970,412 @@ $.fn.dropdown = function(parameters) {
1112
1970
  belowPage
1113
1971
  ;
1114
1972
 
1115
- $item = $item || module.get.activeItem();
1973
+ $item = $item || module.get.selectedItem();
1974
+ $menu = $item.closest(selector.menu);
1116
1975
  hasActive = ($item && $item.length > 0);
1117
1976
  forceScroll = (forceScroll !== undefined)
1118
1977
  ? forceScroll
1119
1978
  : false
1120
1979
  ;
1980
+ if($item && $menu.length > 0 && hasActive) {
1981
+ itemOffset = $item.position().top;
1121
1982
 
1122
- if($item && hasActive) {
1123
-
1124
- if(!$menu.hasClass(className.visible)) {
1125
- $menu.addClass(className.loading);
1126
- }
1127
-
1128
- menuHeight = $menu.height();
1129
- itemHeight = $item.height();
1983
+ $menu.addClass(className.loading);
1130
1984
  menuScroll = $menu.scrollTop();
1131
1985
  menuOffset = $menu.offset().top;
1132
1986
  itemOffset = $item.offset().top;
1133
1987
  offset = menuScroll - menuOffset + itemOffset;
1134
- belowPage = menuScroll + menuHeight < (offset + edgeTolerance);
1135
- abovePage = ((offset - edgeTolerance) < menuScroll);
1988
+ if(!forceScroll) {
1989
+ menuHeight = $menu.height();
1990
+ belowPage = menuScroll + menuHeight < (offset + edgeTolerance);
1991
+ abovePage = ((offset - edgeTolerance) < menuScroll);
1992
+ }
1136
1993
  module.debug('Scrolling to active item', offset);
1137
- if(abovePage || belowPage || forceScroll) {
1138
- $menu
1139
- .scrollTop(offset)
1140
- .removeClass(className.loading)
1994
+ if(forceScroll || abovePage || belowPage) {
1995
+ $menu.scrollTop(offset);
1996
+ }
1997
+ $menu.removeClass(className.loading);
1998
+ }
1999
+ },
2000
+ text: function(text) {
2001
+ if(settings.action !== 'select') {
2002
+ if(settings.action == 'combo') {
2003
+ module.debug('Changing combo button text', text, $combo);
2004
+ if(settings.preserveHTML) {
2005
+ $combo.html(text);
2006
+ }
2007
+ else {
2008
+ $combo.text(text);
2009
+ }
2010
+ }
2011
+ else {
2012
+ module.debug('Changing text', text, $text);
2013
+ $text
2014
+ .removeClass(className.filtered)
2015
+ .removeClass(className.placeholder)
1141
2016
  ;
2017
+ if(settings.preserveHTML) {
2018
+ $text.html(text);
2019
+ }
2020
+ else {
2021
+ $text.text(text);
2022
+ }
2023
+ }
2024
+ }
2025
+ },
2026
+ selectedLetter: function(letter) {
2027
+ var
2028
+ $selectedItem = $item.filter('.' + className.selected),
2029
+ alreadySelectedLetter = $selectedItem.length > 0 && module.has.firstLetter($selectedItem, letter),
2030
+ $nextValue = false,
2031
+ $nextItem
2032
+ ;
2033
+ // check next of same letter
2034
+ if(alreadySelectedLetter) {
2035
+ $nextItem = $selectedItem.nextAll($item).eq(0);
2036
+ if( module.has.firstLetter($nextItem, letter) ) {
2037
+ $nextValue = $nextItem;
2038
+ }
2039
+ }
2040
+ // check all values
2041
+ if(!$nextValue) {
2042
+ $item
2043
+ .each(function(){
2044
+ if(module.has.firstLetter($(this), letter)) {
2045
+ $nextValue = $(this);
2046
+ return false;
2047
+ }
2048
+ })
2049
+ ;
2050
+ }
2051
+ // set next value
2052
+ if($nextValue) {
2053
+ module.verbose('Scrolling to next value with letter', letter);
2054
+ module.set.scrollPosition($nextValue);
2055
+ $selectedItem.removeClass(className.selected);
2056
+ $nextValue.addClass(className.selected);
2057
+ }
2058
+ },
2059
+ direction: function($menu) {
2060
+ if(settings.direction == 'auto') {
2061
+ if(module.is.onScreen($menu)) {
2062
+ module.remove.upward($menu);
2063
+ }
2064
+ else {
2065
+ module.set.upward($menu);
2066
+ }
2067
+ }
2068
+ else if(settings.direction == 'upward') {
2069
+ module.set.upward($menu);
2070
+ }
2071
+ },
2072
+ upward: function($menu) {
2073
+ var $element = $menu || $module;
2074
+ $element.addClass(className.upward);
2075
+ },
2076
+ value: function(value, text, $selected) {
2077
+ var
2078
+ hasInput = ($input.length > 0),
2079
+ isAddition = !module.has.value(value),
2080
+ currentValue = module.get.values(),
2081
+ stringValue = (typeof value == 'number')
2082
+ ? value.toString()
2083
+ : value,
2084
+ newValue
2085
+ ;
2086
+ if(hasInput) {
2087
+ if(stringValue == currentValue) {
2088
+ module.verbose('Skipping value update already same value', value, currentValue);
2089
+ if(!module.is.initialLoad()) {
2090
+ return;
2091
+ }
2092
+ }
2093
+
2094
+ if( $input.is('select') && (settings.allowAdditions || settings.apiSettings) ) {
2095
+ module.debug('Adding an option to the select before setting the value', value);
2096
+ module.add.optionValue(value);
2097
+ }
2098
+
2099
+ module.debug('Updating input value', value, currentValue);
2100
+ $input
2101
+ .val(value)
2102
+ .trigger('change')
2103
+ ;
2104
+ }
2105
+ else {
2106
+ module.verbose('Storing value in metadata', value, $input);
2107
+ if(value !== currentValue) {
2108
+ $module.data(metadata.value, value);
1142
2109
  }
1143
2110
  }
1144
- },
1145
- text: function(text) {
1146
- if(settings.action == 'combo') {
1147
- module.debug('Changing combo button text', text, $combo);
1148
- if(settings.preserveHTML) {
1149
- $combo.html(text);
1150
- }
1151
- else {
1152
- $combo.text(text);
1153
- }
2111
+ if(settings.fireOnInit === false && module.is.initialLoad()) {
2112
+ module.verbose('No callback on initial load', settings.onChange);
2113
+ }
2114
+ else {
2115
+ settings.onChange.call(element, value, text, $selected);
2116
+ }
2117
+ },
2118
+ active: function() {
2119
+ $module
2120
+ .addClass(className.active)
2121
+ ;
2122
+ },
2123
+ multiple: function() {
2124
+ $module.addClass(className.multiple);
2125
+ },
2126
+ visible: function() {
2127
+ $module.addClass(className.visible);
2128
+ },
2129
+ selected: function(value, $selectedItem) {
2130
+ var
2131
+ isMultiple = module.is.multiple(),
2132
+ $userSelectedItem
2133
+ ;
2134
+ $selectedItem = (settings.allowAdditions)
2135
+ ? $selectedItem || module.get.itemWithAdditions(value)
2136
+ : $selectedItem || module.get.item(value)
2137
+ ;
2138
+ if(!$selectedItem) {
2139
+ return;
2140
+ }
2141
+ module.debug('Setting selected menu item to', $selectedItem);
2142
+ if(module.is.single()) {
2143
+ module.remove.activeItem();
2144
+ module.remove.selectedItem();
2145
+ }
2146
+ else if(settings.useLabels) {
2147
+ module.remove.selectedItem();
2148
+ }
2149
+ // select each item
2150
+ $selectedItem
2151
+ .each(function() {
2152
+ var
2153
+ $selected = $(this),
2154
+ selectedText = module.get.choiceText($selected),
2155
+ selectedValue = module.get.choiceValue($selected, selectedText),
2156
+
2157
+ isFiltered = $selected.hasClass(className.filtered),
2158
+ isActive = $selected.hasClass(className.active),
2159
+ isUserValue = $selected.hasClass(className.addition),
2160
+ shouldAnimate = (isMultiple && $selectedItem.length == 1)
2161
+ ;
2162
+ if(isMultiple) {
2163
+ if(!isActive || isUserValue) {
2164
+ if(settings.apiSettings && settings.saveRemoteData) {
2165
+ module.save.remoteData(selectedText, selectedValue);
2166
+ }
2167
+ if(settings.useLabels) {
2168
+ module.add.value(selectedValue, selectedText, $selected);
2169
+ module.add.label(selectedValue, selectedText, shouldAnimate);
2170
+ $selected.addClass(className.active);
2171
+ module.filterActive();
2172
+ module.select.nextAvailable($selectedItem);
2173
+ }
2174
+ else {
2175
+ module.set.text(module.add.variables(message.count));
2176
+ module.add.value(selectedValue, selectedText, $selected);
2177
+ $selected.addClass(className.active);
2178
+ }
2179
+ }
2180
+ else if(!isFiltered) {
2181
+ module.debug('Selected active value, removing label');
2182
+ module.remove.selected(selectedValue);
2183
+ }
2184
+ }
2185
+ else {
2186
+ if(settings.apiSettings && settings.saveRemoteData) {
2187
+ module.save.remoteData(selectedText, selectedValue);
2188
+ }
2189
+ module.set.text(selectedText);
2190
+ module.set.value(selectedValue, selectedText, $selected);
2191
+ $selected
2192
+ .addClass(className.active)
2193
+ .addClass(className.selected)
2194
+ ;
2195
+ }
2196
+ })
2197
+ ;
2198
+ }
2199
+ },
2200
+
2201
+ add: {
2202
+ label: function(value, text, shouldAnimate) {
2203
+ var
2204
+ $next = module.is.searchSelection()
2205
+ ? $search
2206
+ : $text,
2207
+ $label
2208
+ ;
2209
+ $label = $('<a />')
2210
+ .addClass(className.label)
2211
+ .attr('data-value', value)
2212
+ .html(templates.label(value, text))
2213
+ ;
2214
+ $label = settings.onLabelCreate.call($label, value, text);
2215
+
2216
+ if(module.has.label(value)) {
2217
+ module.debug('Label already exists, skipping', value);
2218
+ return;
2219
+ }
2220
+ if(settings.label.variation) {
2221
+ $label.addClass(settings.label.variation);
1154
2222
  }
1155
- else if(settings.action !== 'select') {
1156
- module.debug('Changing text', text, $text);
1157
- $text
1158
- .removeClass(className.filtered)
1159
- .removeClass(className.placeholder)
2223
+ if(shouldAnimate === true) {
2224
+ module.debug('Animating in label', $label);
2225
+ $label
2226
+ .addClass(className.hidden)
2227
+ .insertBefore($next)
2228
+ .transition(settings.label.transition, settings.label.duration)
2229
+ ;
2230
+ }
2231
+ else {
2232
+ module.debug('Adding selection label', $label);
2233
+ $label
2234
+ .insertBefore($next)
1160
2235
  ;
1161
- if(settings.preserveHTML) {
1162
- $text.html(text);
1163
- }
1164
- else {
1165
- $text.text(text);
1166
- }
1167
2236
  }
1168
2237
  },
1169
- value: function(value) {
1170
- module.debug('Adding selected value to hidden input', value, $input);
1171
- if($input.length > 0) {
1172
- $input
1173
- .val(value)
1174
- .trigger('change')
2238
+ message: function(message) {
2239
+ var
2240
+ $message = $menu.children(selector.message),
2241
+ html = settings.templates.message(module.add.variables(message))
2242
+ ;
2243
+ if($message.length > 0) {
2244
+ $message
2245
+ .html(html)
1175
2246
  ;
1176
2247
  }
1177
2248
  else {
1178
- $module.data(metadata.value, value);
2249
+ $message = $('<div/>')
2250
+ .html(html)
2251
+ .addClass(className.message)
2252
+ .appendTo($menu)
2253
+ ;
1179
2254
  }
1180
2255
  },
1181
- active: function() {
1182
- $module
1183
- .addClass(className.active)
2256
+ optionValue: function(value) {
2257
+ var
2258
+ $option = $input.find('option[value="' + value + '"]'),
2259
+ hasOption = ($option.length > 0)
1184
2260
  ;
2261
+ if(hasOption) {
2262
+ return;
2263
+ }
2264
+ // temporarily disconnect observer
2265
+ if(selectObserver) {
2266
+ selectObserver.disconnect();
2267
+ module.verbose('Temporarily disconnecting mutation observer', value);
2268
+ }
2269
+ $('<option/>')
2270
+ .prop('value', value)
2271
+ .html(value)
2272
+ .appendTo($input)
2273
+ ;
2274
+ module.verbose('Adding user addition as an <option>', value);
2275
+ if(selectObserver) {
2276
+ selectObserver.observe($input[0], {
2277
+ childList : true,
2278
+ subtree : true
2279
+ });
2280
+ }
1185
2281
  },
1186
- visible: function() {
1187
- $module.addClass(className.visible);
1188
- },
1189
- selected: function(value) {
2282
+ userSuggestion: function(value) {
1190
2283
  var
1191
- $selectedItem = module.get.item(value),
1192
- selectedText,
1193
- selectedValue
2284
+ $addition = $menu.children(selector.addition),
2285
+ alreadyHasValue = module.get.item(value),
2286
+ hasUserSuggestion = $addition.length > 0,
2287
+ html
1194
2288
  ;
1195
- if($selectedItem && !$selectedItem.hasClass(className.active) ) {
1196
- module.debug('Setting selected menu item to', $selectedItem);
1197
- module.remove.activeItem();
1198
- module.remove.selectedItem();
1199
- $selectedItem
1200
- .addClass(className.active)
2289
+ if(module.has.maxSelections()) {
2290
+ return;
2291
+ }
2292
+ if(value === '' || alreadyHasValue) {
2293
+ $addition.remove();
2294
+ return;
2295
+ }
2296
+ $item
2297
+ .removeClass(className.selected)
2298
+ ;
2299
+ if(hasUserSuggestion) {
2300
+ html = settings.templates.addition(value);
2301
+ $addition
2302
+ .html(html)
2303
+ .data(metadata.value, value)
2304
+ .removeClass(className.filtered)
2305
+ .addClass(className.selected)
2306
+ ;
2307
+ module.verbose('Replacing user suggestion with new value', $addition);
2308
+ }
2309
+ else {
2310
+ $addition = module.create.userChoice(value);
2311
+ $addition
2312
+ .prependTo($menu)
1201
2313
  .addClass(className.selected)
1202
2314
  ;
1203
- selectedText = module.get.choiceText($selectedItem);
1204
- selectedValue = module.get.choiceValue($selectedItem, selectedText);
1205
- module.set.text(selectedText);
1206
- module.set.value(selectedValue);
1207
- settings.onChange.call(element, value, selectedText, $selectedItem);
2315
+ module.verbose('Adding item choice to menu corresponding with user choice addition', $addition);
2316
+ }
2317
+ },
2318
+ variables: function(message) {
2319
+ var
2320
+ hasCount = (message.search('{count}') !== -1),
2321
+ hasMaxCount = (message.search('{maxCount}') !== -1),
2322
+ hasTerm = (message.search('{term}') !== -1),
2323
+ values,
2324
+ count,
2325
+ query
2326
+ ;
2327
+ module.verbose('Adding templated variables to message', message);
2328
+ if(hasCount) {
2329
+ count = module.get.selectionCount();
2330
+ message = message.replace('{count}', count);
2331
+ }
2332
+ if(hasMaxCount) {
2333
+ count = module.get.selectionCount();
2334
+ message = message.replace('{maxCount}', settings.maxSelections);
2335
+ }
2336
+ if(hasTerm) {
2337
+ query = module.get.query();
2338
+ message = message.replace('{term}', query);
2339
+ }
2340
+ return message;
2341
+ },
2342
+ value: function(addedValue, addedText, $selectedItem) {
2343
+ var
2344
+ currentValue = module.get.values(),
2345
+ newValue
2346
+ ;
2347
+ if(addedValue === '') {
2348
+ module.debug('Cannot select blank values from multiselect');
2349
+ return;
2350
+ }
2351
+ // extend currently array
2352
+ if($.isArray(currentValue)) {
2353
+ newValue = currentValue.concat([addedValue]);
2354
+ newValue = module.get.uniqueArray(newValue);
2355
+ }
2356
+ else {
2357
+ newValue = [addedValue];
2358
+ }
2359
+ // add values
2360
+ if($input.is('select')) {
2361
+ if(settings.allowAdditions || settings.apiSettings) {
2362
+ module.debug('Adding value to select', addedValue, newValue, $input);
2363
+ module.add.optionValue(addedValue);
2364
+ }
1208
2365
  }
2366
+ else {
2367
+ newValue = newValue.join(settings.delimiter);
2368
+ module.debug('Setting hidden input to delimited value', newValue, $input);
2369
+ }
2370
+
2371
+ if(settings.fireOnInit === false && module.is.initialLoad()) {
2372
+ module.verbose('No callback on initial load', settings.onAdd);
2373
+ }
2374
+ else {
2375
+ settings.onAdd.call(element, addedValue, addedText, $selectedItem);
2376
+ }
2377
+ module.set.value(newValue, addedValue, addedText, $selectedItem);
2378
+ module.check.maxSelections();
1209
2379
  }
1210
2380
  },
1211
2381
 
@@ -1213,6 +2383,19 @@ $.fn.dropdown = function(parameters) {
1213
2383
  active: function() {
1214
2384
  $module.removeClass(className.active);
1215
2385
  },
2386
+ activeLabel: function() {
2387
+ $module.find(selector.label).removeClass(className.active);
2388
+ },
2389
+ loading: function() {
2390
+ $module.removeClass(className.loading);
2391
+ },
2392
+ initialLoad: function() {
2393
+ initialLoad = false;
2394
+ },
2395
+ upward: function($menu) {
2396
+ var $element = $menu || $module;
2397
+ $element.removeClass(className.upward);
2398
+ },
1216
2399
  visible: function() {
1217
2400
  $module.removeClass(className.visible);
1218
2401
  },
@@ -1220,16 +2403,146 @@ $.fn.dropdown = function(parameters) {
1220
2403
  $item.removeClass(className.active);
1221
2404
  },
1222
2405
  filteredItem: function() {
1223
- $item.removeClass(className.filtered);
2406
+ if( module.has.maxSelections() ) {
2407
+ return;
2408
+ }
2409
+ if(settings.useLabels) {
2410
+ $item.not('.' + className.active).removeClass(className.filtered);
2411
+ }
2412
+ else {
2413
+ $item.removeClass(className.filtered);
2414
+ }
2415
+ },
2416
+ message: function() {
2417
+ $menu.children(selector.message).remove();
1224
2418
  },
1225
2419
  searchTerm: function() {
2420
+ module.verbose('Cleared search term');
1226
2421
  $search.val('');
2422
+ module.set.filtered();
2423
+ },
2424
+ selected: function(value, $selectedItem) {
2425
+ $selectedItem = (settings.allowAdditions)
2426
+ ? $selectedItem || module.get.itemWithAdditions(value)
2427
+ : $selectedItem || module.get.item(value)
2428
+ ;
2429
+
2430
+ if(!$selectedItem) {
2431
+ return false;
2432
+ }
2433
+
2434
+ $selectedItem
2435
+ .each(function() {
2436
+ var
2437
+ $selected = $(this),
2438
+ selectedText = module.get.choiceText($selected),
2439
+ selectedValue = module.get.choiceValue($selected, selectedText)
2440
+ ;
2441
+ if(module.is.multiple()) {
2442
+ if(settings.useLabels) {
2443
+ module.remove.value(selectedValue, selectedText, $selected);
2444
+ module.remove.label(selectedValue);
2445
+ }
2446
+ else {
2447
+ module.remove.value(selectedValue, selectedText, $selected);
2448
+ module.set.text(module.add.variables(message.count));
2449
+ }
2450
+ }
2451
+ else {
2452
+ module.remove.value(selectedValue, selectedText, $selected);
2453
+ }
2454
+ $selected
2455
+ .removeClass(className.filtered)
2456
+ .removeClass(className.active)
2457
+ ;
2458
+ if(settings.useLabels) {
2459
+ $selected.removeClass(className.selected);
2460
+ }
2461
+ })
2462
+ ;
1227
2463
  },
1228
2464
  selectedItem: function() {
1229
2465
  $item.removeClass(className.selected);
1230
2466
  },
2467
+ value: function(removedValue, removedText, $removedItem) {
2468
+ var
2469
+ values = $input.val(),
2470
+ newValue
2471
+ ;
2472
+ if( $input.is('select') ) {
2473
+ module.verbose('Input is <select> removing selected option', removedValue);
2474
+ newValue = module.remove.arrayValue(removedValue, values);
2475
+ }
2476
+ else {
2477
+ module.verbose('Removing from delimited values', removedValue);
2478
+ values = values.split(settings.delimiter);
2479
+ newValue = module.remove.arrayValue(removedValue, values);
2480
+ newValue = newValue.join(settings.delimiter);
2481
+ }
2482
+ if(settings.fireOnInit === false && module.is.initialLoad()) {
2483
+ module.verbose('No callback on initial load', settings.onRemove);
2484
+ }
2485
+ else {
2486
+ settings.onRemove.call(element, removedValue, removedText, $removedItem);
2487
+ }
2488
+ module.set.value(newValue, removedText, $removedItem);
2489
+ module.check.maxSelections();
2490
+ },
2491
+ arrayValue: function(removedValue, values) {
2492
+ values = $.grep(values, function(value){
2493
+ return (removedValue != value);
2494
+ });
2495
+ module.verbose('Removed value from delimited string', removedValue, values);
2496
+ return values;
2497
+ },
2498
+ label: function(value) {
2499
+ var
2500
+ $labels = $module.find(selector.label),
2501
+ $removedLabel = $labels.filter('[data-value="' + value +'"]'),
2502
+ labelCount = $labels.length,
2503
+ isLastLabel = ($labels.index($removedLabel) + 1 == labelCount),
2504
+ shouldAnimate = ( (!module.is.searchSelection() || !module.is.focusedOnSearch()) && isLastLabel)
2505
+ ;
2506
+ if(shouldAnimate) {
2507
+ module.verbose('Animating and removing label', $removedLabel);
2508
+ $removedLabel
2509
+ .transition(settings.label.transition, settings.label.duration, function() {
2510
+ $removedLabel.remove();
2511
+ })
2512
+ ;
2513
+ }
2514
+ else {
2515
+ module.verbose('Removing label', $removedLabel);
2516
+ $removedLabel.remove();
2517
+ }
2518
+ },
2519
+ activeLabels: function($activeLabels) {
2520
+ $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active);
2521
+ module.verbose('Removing active label selections', $activeLabels);
2522
+ module.remove.labels($activeLabels);
2523
+ },
2524
+ labels: function($labels) {
2525
+ $labels = $labels || $module.find(selector.label);
2526
+ module.verbose('Removing labels', $labels);
2527
+ $labels
2528
+ .each(function(){
2529
+ var
2530
+ value = $(this).data('value'),
2531
+ isUserValue = module.is.userValue(value)
2532
+ ;
2533
+ if(isUserValue) {
2534
+ module.remove.value(value);
2535
+ module.remove.label(value);
2536
+ }
2537
+ else {
2538
+ // selected will also remove label
2539
+ module.remove.selected(value);
2540
+ }
2541
+ })
2542
+ ;
2543
+ },
1231
2544
  tabbable: function() {
1232
- if( module.is.searchable() ) {
2545
+ if( module.has.search() ) {
1233
2546
  module.debug('Searchable dropdown initialized');
1234
2547
  $search
1235
2548
  .attr('tabindex', '-1')
@@ -1250,27 +2563,146 @@ $.fn.dropdown = function(parameters) {
1250
2563
  }
1251
2564
  },
1252
2565
 
2566
+ has: {
2567
+ search: function() {
2568
+ return ($search.length > 0);
2569
+ },
2570
+ firstLetter: function($item, letter) {
2571
+ var
2572
+ text,
2573
+ firstLetter
2574
+ ;
2575
+ if(!$item || $item.length === 0 || typeof letter !== 'string') {
2576
+ return false;
2577
+ }
2578
+ text = module.get.choiceText($item, false);
2579
+ letter = letter.toLowerCase();
2580
+ firstLetter = String(text).charAt(0).toLowerCase();
2581
+ return (letter == firstLetter);
2582
+ },
2583
+ input: function() {
2584
+ return ($input.length > 0);
2585
+ },
2586
+ items: function() {
2587
+ return ($item.length > 0);
2588
+ },
2589
+ menu: function() {
2590
+ return ($menu.length > 0);
2591
+ },
2592
+ message: function() {
2593
+ return ($menu.children(selector.message).length !== 0);
2594
+ },
2595
+ label: function(value) {
2596
+ var
2597
+ $labels = $module.find(selector.label)
2598
+ ;
2599
+ return ($labels.filter('[data-value="' + value +'"]').length > 0);
2600
+ },
2601
+ maxSelections: function() {
2602
+ return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections);
2603
+ },
2604
+ allResultsFiltered: function() {
2605
+ return ($item.filter(selector.unselectable).length === $item.length);
2606
+ },
2607
+ value: function(value) {
2608
+ var
2609
+ values = module.get.values(),
2610
+ hasValue = $.isArray(values)
2611
+ ? values && ($.inArray(value, values) !== -1)
2612
+ : (values == value)
2613
+ ;
2614
+ return (hasValue)
2615
+ ? true
2616
+ : false
2617
+ ;
2618
+ }
2619
+ },
2620
+
1253
2621
  is: {
1254
2622
  active: function() {
1255
2623
  return $module.hasClass(className.active);
1256
2624
  },
1257
2625
  alreadySetup: function() {
1258
- return ($module.is('select') && $module.parent(selector.dropdown).length > 0);
2626
+ return ($module.is('select') && $module.parent(selector.dropdown).length > 0 && $module.prev().length === 0);
1259
2627
  },
1260
2628
  animating: function($subMenu) {
1261
2629
  return ($subMenu)
1262
- ? $subMenu.is(':animated') || $subMenu.transition && $subMenu.transition('is animating')
1263
- : $menu.is(':animated') || $menu.transition && $menu.transition('is animating')
2630
+ ? $subMenu.transition && $subMenu.transition('is animating')
2631
+ : $menu.transition && $menu.transition('is animating')
1264
2632
  ;
1265
2633
  },
2634
+ disabled: function() {
2635
+ $module.hasClass(className.disabled);
2636
+ },
2637
+ focused: function() {
2638
+ return (document.activeElement === $module[0]);
2639
+ },
2640
+ focusedOnSearch: function() {
2641
+ return (document.activeElement === $search[0]);
2642
+ },
1266
2643
  allFiltered: function() {
1267
- return ($item.filter('.' + className.filtered).length === $item.length);
2644
+ return( (module.is.multiple() || module.has.search()) && !module.has.message() && module.has.allResultsFiltered() );
1268
2645
  },
1269
2646
  hidden: function($subMenu) {
1270
- return ($subMenu)
1271
- ? $subMenu.is(':hidden')
1272
- : $menu.is(':hidden')
2647
+ return !module.is.visible($subMenu);
2648
+ },
2649
+ initialLoad: function() {
2650
+ return initialLoad;
2651
+ },
2652
+ onScreen: function($subMenu) {
2653
+ var
2654
+ $currentMenu = $subMenu || $menu,
2655
+ canOpenDownward = true,
2656
+ onScreen = {},
2657
+ calculations
2658
+ ;
2659
+ $currentMenu.addClass(className.loading);
2660
+ calculations = {
2661
+ context: {
2662
+ scrollTop : $context.scrollTop(),
2663
+ height : $context.outerHeight()
2664
+ },
2665
+ menu : {
2666
+ offset: $currentMenu.offset(),
2667
+ height: $currentMenu.outerHeight()
2668
+ }
2669
+ };
2670
+ onScreen = {
2671
+ above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.menu.height,
2672
+ below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top + calculations.menu.height
2673
+ };
2674
+ if(onScreen.below) {
2675
+ module.verbose('Dropdown can fit in context downward', onScreen);
2676
+ canOpenDownward = true;
2677
+ }
2678
+ else if(!onScreen.below && !onScreen.above) {
2679
+ module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen);
2680
+ canOpenDownward = true;
2681
+ }
2682
+ else {
2683
+ module.verbose('Dropdown cannot fit below, opening upward', onScreen);
2684
+ canOpenDownward = false;
2685
+ }
2686
+ $currentMenu.removeClass(className.loading);
2687
+ return canOpenDownward;
2688
+ },
2689
+ inObject: function(needle, object) {
2690
+ var
2691
+ found = false
1273
2692
  ;
2693
+ $.each(object, function(index, property) {
2694
+ if(property == needle) {
2695
+ found = true;
2696
+ return true;
2697
+ }
2698
+ });
2699
+ return found;
2700
+ },
2701
+ multiple: function() {
2702
+ return $module.hasClass(className.multiple);
2703
+ },
2704
+ single: function() {
2705
+ return !module.is.multiple();
1274
2706
  },
1275
2707
  selectMutation: function(mutations) {
1276
2708
  var
@@ -1287,22 +2719,23 @@ $.fn.dropdown = function(parameters) {
1287
2719
  search: function() {
1288
2720
  return $module.hasClass(className.search);
1289
2721
  },
1290
- searchable: function() {
1291
- return ($search.length > 0);
1292
- },
1293
2722
  searchSelection: function() {
1294
- return ( module.is.searchable() && $search.parent().is($module) );
2723
+ return ( module.has.search() && $search.parent(selector.dropdown).length === 1 );
1295
2724
  },
1296
2725
  selection: function() {
1297
2726
  return $module.hasClass(className.selection);
1298
2727
  },
1299
- upward: function() {
1300
- return $module.hasClass(className.upward);
2728
+ userValue: function(value) {
2729
+ return ($.inArray(value, module.get.userValues()) !== -1);
2730
+ },
2731
+ upward: function($menu) {
2732
+ var $element = $menu || $module;
2733
+ return $element.hasClass(className.upward);
1301
2734
  },
1302
2735
  visible: function($subMenu) {
1303
2736
  return ($subMenu)
1304
- ? $subMenu.is(':visible')
1305
- : $menu.is(':visible')
2737
+ ? $subMenu.hasClass(className.visible)
2738
+ : $menu.hasClass(className.visible)
1306
2739
  ;
1307
2740
  }
1308
2741
  },
@@ -1312,7 +2745,10 @@ $.fn.dropdown = function(parameters) {
1312
2745
  return (hasTouch || settings.on == 'click');
1313
2746
  },
1314
2747
  show: function() {
1315
- return !$module.hasClass(className.disabled);
2748
+ return !module.is.disabled() && (module.has.items() || module.has.message());
2749
+ },
2750
+ useAPI: function() {
2751
+ return $.fn.api !== undefined;
1316
2752
  }
1317
2753
  },
1318
2754
 
@@ -1326,30 +2762,29 @@ $.fn.dropdown = function(parameters) {
1326
2762
  module.hideSubMenus();
1327
2763
  module.hideOthers();
1328
2764
  module.set.active();
1329
- }
2765
+ },
2766
+ transition
1330
2767
  ;
1331
2768
  callback = $.isFunction(callback)
1332
2769
  ? callback
1333
2770
  : function(){}
1334
2771
  ;
1335
- module.set.scrollPosition(module.get.activeItem(), true);
1336
2772
  module.verbose('Doing menu show animation', $currentMenu);
2773
+ module.set.direction($subMenu);
2774
+ transition = module.get.transition($subMenu);
2775
+ if( module.is.selection() ) {
2776
+ module.set.scrollPosition(module.get.selectedItem(), true);
2777
+ }
1337
2778
  if( module.is.hidden($currentMenu) || module.is.animating($currentMenu) ) {
1338
-
1339
- if(settings.transition == 'auto') {
1340
- settings.transition = module.is.upward()
1341
- ? 'slide up'
1342
- : 'slide down'
1343
- ;
1344
- module.verbose('Automatically determining animation based on animation direction', settings.transition);
1345
- }
1346
- if(settings.transition == 'none') {
2779
+ if(transition == 'none') {
2780
+ start();
2781
+ $currentMenu.transition('show');
1347
2782
  callback.call(element);
1348
2783
  }
1349
2784
  else if($.fn.transition !== undefined && $module.transition('is supported')) {
1350
2785
  $currentMenu
1351
2786
  .transition({
1352
- animation : settings.transition + ' in',
2787
+ animation : transition + ' in',
1353
2788
  debug : settings.debug,
1354
2789
  verbose : settings.verbose,
1355
2790
  duration : settings.duration,
@@ -1361,38 +2796,8 @@ $.fn.dropdown = function(parameters) {
1361
2796
  })
1362
2797
  ;
1363
2798
  }
1364
- else if(settings.transition == 'slide down') {
1365
- start();
1366
- $currentMenu
1367
- .hide()
1368
- .clearQueue()
1369
- .children()
1370
- .clearQueue()
1371
- .css('opacity', 0)
1372
- .delay(50)
1373
- .animate({
1374
- opacity : 1
1375
- }, settings.duration, 'easeOutQuad', module.event.resetStyle)
1376
- .end()
1377
- .slideDown(100, 'easeOutQuad', function() {
1378
- module.event.resetStyle.call(this);
1379
- callback.call(element);
1380
- })
1381
- ;
1382
- }
1383
- else if(settings.transition == 'fade') {
1384
- start();
1385
- $currentMenu
1386
- .hide()
1387
- .clearQueue()
1388
- .fadeIn(settings.duration, function() {
1389
- module.event.resetStyle.call(this);
1390
- callback.call(element);
1391
- })
1392
- ;
1393
- }
1394
2799
  else {
1395
- module.error(error.transition, settings.transition);
2800
+ module.error(error.noTransition, transition);
1396
2801
  }
1397
2802
  }
1398
2803
  },
@@ -1408,9 +2813,9 @@ $.fn.dropdown = function(parameters) {
1408
2813
  if( module.can.click() ) {
1409
2814
  module.unbind.intent();
1410
2815
  }
1411
- module.focusSearch();
1412
2816
  module.remove.active();
1413
- }
2817
+ },
2818
+ transition = module.get.transition($subMenu)
1414
2819
  ;
1415
2820
  callback = $.isFunction(callback)
1416
2821
  ? callback
@@ -1419,63 +2824,29 @@ $.fn.dropdown = function(parameters) {
1419
2824
  if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) {
1420
2825
  module.verbose('Doing menu hide animation', $currentMenu);
1421
2826
 
1422
- if(settings.transition == 'auto') {
1423
- settings.transition = module.is.upward()
1424
- ? 'slide up'
1425
- : 'slide down'
1426
- ;
1427
- }
1428
-
1429
- $input.trigger('blur');
1430
-
1431
- if(settings.transition == 'none') {
2827
+ if(transition == 'none') {
2828
+ start();
2829
+ $currentMenu.transition('hide');
1432
2830
  callback.call(element);
1433
2831
  }
1434
2832
  else if($.fn.transition !== undefined && $module.transition('is supported')) {
1435
2833
  $currentMenu
1436
2834
  .transition({
1437
- animation : settings.transition + ' out',
2835
+ animation : transition + ' out',
1438
2836
  duration : settings.duration,
1439
2837
  debug : settings.debug,
1440
2838
  verbose : settings.verbose,
1441
2839
  queue : true,
1442
2840
  onStart : start,
1443
2841
  onComplete : function() {
2842
+ if(settings.direction == 'auto') {
2843
+ module.remove.upward($subMenu);
2844
+ }
1444
2845
  callback.call(element);
1445
2846
  }
1446
2847
  })
1447
2848
  ;
1448
2849
  }
1449
- else if(settings.transition == 'slide down') {
1450
- start();
1451
- $currentMenu
1452
- .show()
1453
- .clearQueue()
1454
- .children()
1455
- .clearQueue()
1456
- .css('opacity', 1)
1457
- .animate({
1458
- opacity : 0
1459
- }, 100, 'easeOutQuad', module.event.resetStyle)
1460
- .end()
1461
- .delay(50)
1462
- .slideUp(100, 'easeOutQuad', function() {
1463
- module.event.resetStyle.call(this);
1464
- callback.call(element);
1465
- })
1466
- ;
1467
- }
1468
- else if(settings.transition == 'fade') {
1469
- start();
1470
- $currentMenu
1471
- .show()
1472
- .clearQueue()
1473
- .fadeOut(150, function() {
1474
- module.event.resetStyle.call(this);
1475
- callback.call(element);
1476
- })
1477
- ;
1478
- }
1479
2850
  else {
1480
2851
  module.error(error.transition);
1481
2852
  }
@@ -1483,6 +2854,21 @@ $.fn.dropdown = function(parameters) {
1483
2854
  }
1484
2855
  },
1485
2856
 
2857
+ hideAndClear: function() {
2858
+ module.remove.searchTerm();
2859
+ if( module.has.maxSelections() ) {
2860
+ return;
2861
+ }
2862
+ if(module.has.search()) {
2863
+ module.hide(function() {
2864
+ module.remove.filteredItem();
2865
+ });
2866
+ }
2867
+ else {
2868
+ module.hide();
2869
+ }
2870
+ },
2871
+
1486
2872
  delay: {
1487
2873
  show: function() {
1488
2874
  module.verbose('Delaying show event to ensure user intent');
@@ -1499,7 +2885,7 @@ $.fn.dropdown = function(parameters) {
1499
2885
  escape: {
1500
2886
  regExp: function(text) {
1501
2887
  text = String(text);
1502
- return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
2888
+ return text.replace(regExp.escape, '\\$&');
1503
2889
  }
1504
2890
  },
1505
2891
 
@@ -1682,75 +3068,137 @@ $.fn.dropdown = function(parameters) {
1682
3068
  $.fn.dropdown.settings = {
1683
3069
 
1684
3070
  debug : false,
1685
- verbose : true,
3071
+ verbose : false,
1686
3072
  performance : true,
1687
3073
 
1688
- on : 'click',
1689
- action : 'activate',
3074
+ on : 'click', // what event should show menu action on item selection
3075
+ action : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){})
3076
+
3077
+
3078
+ apiSettings : false,
3079
+ saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh
3080
+ throttle : 200, // How long to wait after last user input to search remotely
3081
+
3082
+ context : window, // Context to use when determining if on screen
3083
+ direction : 'auto', // Whether dropdown should always open in one direction
3084
+ keepOnScreen : true, // Whether dropdown should check whether it is on screen before showing
3085
+
3086
+ match : 'both', // what to match against with search selection (both, text, or label)
3087
+ fullTextSearch : false, // search anywhere in value
3088
+
3089
+ placeholder : 'auto', // whether to convert blank <select> values to placeholder text
3090
+ preserveHTML : true, // preserve html when selecting value
3091
+ sortSelect : false, // sort selection on init
3092
+
3093
+ forceSelection : true, // force a choice on blur with search selection
3094
+ allowAdditions : false, // whether multiple select should allow user added values
3095
+
3096
+ maxSelections : false, // When set to a number limits the number of selections to this count
3097
+ useLabels : true, // whether multiple select should filter currently active selections from choices
3098
+ delimiter : ',', // when multiselect uses normal <input> the values will be delmited with this character
1690
3099
 
1691
- allowTab : true,
1692
- fullTextSearch : false,
1693
- preserveHTML : true,
1694
- sortSelect : false,
3100
+ showOnFocus : true, // show menu on focus
3101
+ allowTab : true, // add tabindex to element
3102
+ allowCategorySelection : false, // allow elements with sub-menus to be selected
1695
3103
 
1696
- allowCategorySelection : false,
3104
+ fireOnInit : false, // Whether callbacks should fire when initializing dropdown values
1697
3105
 
1698
- delay : {
3106
+ transition : 'auto', // auto transition will slide down or up based on direction
3107
+ duration : 200, // duration of transition
3108
+
3109
+ glyphWidth : 1.0714, // widest glyph width in em (W is 1.0714 em) used to calculate multiselect input width
3110
+
3111
+ // label settings on multi-select
3112
+ label: {
3113
+ transition : 'scale',
3114
+ duration : 200,
3115
+ variation : false
3116
+ },
3117
+
3118
+ // delay before event
3119
+ delay : {
1699
3120
  hide : 300,
1700
3121
  show : 200,
1701
- search : 50,
3122
+ search : 20,
1702
3123
  touch : 50
1703
3124
  },
1704
3125
 
1705
- forceSelection: true,
1706
-
1707
- transition : 'auto',
1708
- duration : 250,
1709
-
1710
3126
  /* Callbacks */
1711
- onNoResults : function(searchTerm){},
1712
- onChange : function(value, text){},
1713
- onShow : function(){},
1714
- onHide : function(){},
3127
+ onChange : function(value, text, $selected){},
3128
+ onAdd : function(value, text, $selected){},
3129
+ onRemove : function(value, text, $selected){},
1715
3130
 
1716
- /* Component */
3131
+ onLabelSelect : function($selectedLabels){},
3132
+ onLabelCreate : function(value, text) { return $(this); },
3133
+ onNoResults : function(searchTerm) { return true; },
3134
+ onShow : function(){},
3135
+ onHide : function(){},
1717
3136
 
3137
+ /* Component */
1718
3138
  name : 'Dropdown',
1719
3139
  namespace : 'dropdown',
1720
3140
 
1721
- error : {
3141
+ message: {
3142
+ addResult : 'Add <b>{term}</b>',
3143
+ count : '{count} selected',
3144
+ maxSelections : 'Max {maxCount} selections',
3145
+ noResults : 'No results found.',
3146
+ serverError : 'There was an error contacting the server'
3147
+ },
3148
+
3149
+ error : {
1722
3150
  action : 'You called a dropdown action that was not defined',
1723
3151
  alreadySetup : 'Once a select has been initialized behaviors must be called on the created ui dropdown',
3152
+ labels : 'Allowing user additions currently requires the use of labels.',
1724
3153
  method : 'The method you called is not defined.',
1725
- transition : 'The requested transition was not found'
3154
+ noAPI : 'The API module is required to load resources remotely',
3155
+ noStorage : 'Saving remote data requires session storage',
3156
+ noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>'
3157
+ },
3158
+
3159
+ regExp : {
3160
+ escape : /[-[\]{}()*+?.,\\^$|#\s]/g,
1726
3161
  },
1727
3162
 
1728
- metadata: {
3163
+ metadata : {
1729
3164
  defaultText : 'defaultText',
1730
3165
  defaultValue : 'defaultValue',
1731
- placeholderText : 'placeholderText',
3166
+ placeholderText : 'placeholder',
1732
3167
  text : 'text',
1733
3168
  value : 'value'
1734
3169
  },
1735
3170
 
1736
3171
  selector : {
1737
- dropdown : '.ui.dropdown',
1738
- input : '> input[type="hidden"], > select',
1739
- item : '.item',
1740
- menu : '.menu',
1741
- menuIcon : '.dropdown.icon',
1742
- search : '> input.search, .menu > .search > input, .menu > input.search',
1743
- text : '> .text:not(.icon)'
3172
+ addition : '.addition',
3173
+ dropdown : '.ui.dropdown',
3174
+ icon : '> .dropdown.icon',
3175
+ input : '> input[type="hidden"], > select',
3176
+ item : '.item',
3177
+ label : '> .label',
3178
+ remove : '> .label > .delete.icon',
3179
+ siblingLabel : '.label',
3180
+ menu : '.menu',
3181
+ message : '.message',
3182
+ menuIcon : '.dropdown.icon',
3183
+ search : 'input.search, .menu > .search > input',
3184
+ text : '> .text:not(.icon)',
3185
+ unselectable : '.disabled, .filtered'
1744
3186
  },
1745
3187
 
1746
3188
  className : {
1747
3189
  active : 'active',
3190
+ addition : 'addition',
1748
3191
  animating : 'animating',
1749
3192
  disabled : 'disabled',
1750
3193
  dropdown : 'ui dropdown',
1751
3194
  filtered : 'filtered',
3195
+ hidden : 'hidden transition',
3196
+ item : 'item',
3197
+ label : 'ui label',
1752
3198
  loading : 'loading',
1753
3199
  menu : 'menu',
3200
+ message : 'message',
3201
+ multiple : 'multiple',
1754
3202
  placeholder : 'default',
1755
3203
  search : 'search',
1756
3204
  selected : 'selected',
@@ -1763,17 +3211,8 @@ $.fn.dropdown.settings = {
1763
3211
 
1764
3212
  /* Templates */
1765
3213
  $.fn.dropdown.settings.templates = {
1766
- menu: function(select) {
1767
- var
1768
- placeholder = select.placeholder || false,
1769
- values = select.values || {},
1770
- html = ''
1771
- ;
1772
- $.each(select.values, function(index, option) {
1773
- html += '<div class="item" data-value="' + option.value + '">' + option.name + '</div>';
1774
- });
1775
- return html;
1776
- },
3214
+
3215
+ // generates dropdown from select values
1777
3216
  dropdown: function(select) {
1778
3217
  var
1779
3218
  placeholder = select.placeholder || false,
@@ -1789,20 +3228,43 @@ $.fn.dropdown.settings.templates = {
1789
3228
  }
1790
3229
  html += '<div class="menu">';
1791
3230
  $.each(select.values, function(index, option) {
1792
- html += '<div class="item" data-value="' + option.value + '">' + option.name + '</div>';
3231
+ html += (option.disabled)
3232
+ ? '<div class="disabled item" data-value="' + option.value + '">' + option.name + '</div>'
3233
+ : '<div class="item" data-value="' + option.value + '">' + option.name + '</div>'
3234
+ ;
1793
3235
  });
1794
3236
  html += '</div>';
1795
3237
  return html;
1796
- }
1797
- };
3238
+ },
3239
+
3240
+ // generates just menu from select
3241
+ menu: function(response) {
3242
+ var
3243
+ values = response.values || {},
3244
+ html = ''
3245
+ ;
3246
+ $.each(response.values, function(index, option) {
3247
+ html += '<div class="item" data-value="' + option.value + '">' + option.name + '</div>';
3248
+ });
3249
+ return html;
3250
+ },
3251
+
3252
+ // generates label for multiselect
3253
+ label: function(value, text) {
3254
+ return text + '<i class="delete icon"></i>';
3255
+ },
1798
3256
 
1799
3257
 
1800
- /* Dependencies */
1801
- $.extend( $.easing, {
1802
- easeOutQuad: function (x, t, b, c, d) {
1803
- return -c *(t/=d)*(t-2) + b;
3258
+ // generates messages like "No results"
3259
+ message: function(message) {
3260
+ return message;
1804
3261
  },
1805
- });
1806
3262
 
3263
+ // generates user addition to selection menu
3264
+ addition: function(choice) {
3265
+ return choice;
3266
+ }
3267
+
3268
+ };
1807
3269
 
1808
3270
  })( jQuery, window , document );