less-rails-semantic_ui 1.12.3.0 → 2.0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. checksums.yaml +4 -4
  2. data/assets/javascripts/semantic_ui/definitions/behaviors/api.js +394 -188
  3. data/assets/javascripts/semantic_ui/definitions/behaviors/colorize.js +4 -2
  4. data/assets/javascripts/semantic_ui/definitions/behaviors/form.js +263 -125
  5. data/assets/javascripts/semantic_ui/definitions/behaviors/state.js +3 -3
  6. data/assets/javascripts/semantic_ui/definitions/behaviors/visibility.js +213 -96
  7. data/assets/javascripts/semantic_ui/definitions/behaviors/visit.js +6 -4
  8. data/assets/javascripts/semantic_ui/definitions/globals/site.js +4 -4
  9. data/assets/javascripts/semantic_ui/definitions/modules/accordion.js +66 -52
  10. data/assets/javascripts/semantic_ui/definitions/modules/checkbox.js +309 -112
  11. data/assets/javascripts/semantic_ui/definitions/modules/dimmer.js +24 -26
  12. data/assets/javascripts/semantic_ui/definitions/modules/dropdown.js +2005 -590
  13. data/assets/javascripts/semantic_ui/definitions/modules/embed.js +662 -0
  14. data/assets/javascripts/semantic_ui/definitions/modules/modal.js +106 -79
  15. data/assets/javascripts/semantic_ui/definitions/modules/nag.js +7 -8
  16. data/assets/javascripts/semantic_ui/definitions/modules/popup.js +323 -194
  17. data/assets/javascripts/semantic_ui/definitions/modules/progress.js +111 -103
  18. data/assets/javascripts/semantic_ui/definitions/modules/rating.js +78 -54
  19. data/assets/javascripts/semantic_ui/definitions/modules/search.js +304 -122
  20. data/assets/javascripts/semantic_ui/definitions/modules/shape.js +93 -47
  21. data/assets/javascripts/semantic_ui/definitions/modules/sidebar.js +83 -149
  22. data/assets/javascripts/semantic_ui/definitions/modules/sticky.js +99 -29
  23. data/assets/javascripts/semantic_ui/definitions/modules/tab.js +219 -124
  24. data/assets/javascripts/semantic_ui/definitions/modules/transition.js +202 -171
  25. data/assets/stylesheets/semantic_ui/definitions/collections/breadcrumb.less +2 -1
  26. data/assets/stylesheets/semantic_ui/definitions/collections/form.less +135 -58
  27. data/assets/stylesheets/semantic_ui/definitions/collections/grid.less +366 -383
  28. data/assets/stylesheets/semantic_ui/definitions/collections/menu.less +838 -631
  29. data/assets/stylesheets/semantic_ui/definitions/collections/message.less +89 -71
  30. data/assets/stylesheets/semantic_ui/definitions/collections/table.less +183 -131
  31. data/assets/stylesheets/semantic_ui/definitions/elements/button.less +1097 -300
  32. data/assets/stylesheets/semantic_ui/definitions/elements/container.less +135 -0
  33. data/assets/stylesheets/semantic_ui/definitions/elements/divider.less +31 -30
  34. data/assets/stylesheets/semantic_ui/definitions/elements/flag.less +1 -1
  35. data/assets/stylesheets/semantic_ui/definitions/elements/header.less +209 -121
  36. data/assets/stylesheets/semantic_ui/definitions/elements/icon.less +201 -96
  37. data/assets/stylesheets/semantic_ui/definitions/elements/image.less +26 -11
  38. data/assets/stylesheets/semantic_ui/definitions/elements/input.less +49 -14
  39. data/assets/stylesheets/semantic_ui/definitions/elements/label.less +391 -221
  40. data/assets/stylesheets/semantic_ui/definitions/elements/list.less +107 -68
  41. data/assets/stylesheets/semantic_ui/definitions/elements/loader.less +3 -1
  42. data/assets/stylesheets/semantic_ui/definitions/elements/rail.less +34 -25
  43. data/assets/stylesheets/semantic_ui/definitions/elements/reveal.less +25 -10
  44. data/assets/stylesheets/semantic_ui/definitions/elements/segment.less +261 -173
  45. data/assets/stylesheets/semantic_ui/definitions/elements/step.less +169 -70
  46. data/assets/stylesheets/semantic_ui/definitions/globals/reset.less +1 -1
  47. data/assets/stylesheets/semantic_ui/definitions/globals/site.less +3 -1
  48. data/assets/stylesheets/semantic_ui/definitions/modules/accordion.less +1 -2
  49. data/assets/stylesheets/semantic_ui/definitions/modules/checkbox.less +175 -103
  50. data/assets/stylesheets/semantic_ui/definitions/modules/dimmer.less +26 -15
  51. data/assets/stylesheets/semantic_ui/definitions/modules/dropdown.less +349 -80
  52. data/assets/stylesheets/semantic_ui/definitions/modules/embed.less +174 -0
  53. data/assets/stylesheets/semantic_ui/definitions/modules/modal.less +83 -36
  54. data/assets/stylesheets/semantic_ui/definitions/modules/nag.less +1 -1
  55. data/assets/stylesheets/semantic_ui/definitions/modules/popup.less +48 -7
  56. data/assets/stylesheets/semantic_ui/definitions/modules/progress.less +160 -107
  57. data/assets/stylesheets/semantic_ui/definitions/modules/rating.less +57 -54
  58. data/assets/stylesheets/semantic_ui/definitions/modules/search.less +55 -10
  59. data/assets/stylesheets/semantic_ui/definitions/modules/shape.less +5 -6
  60. data/assets/stylesheets/semantic_ui/definitions/modules/sidebar.less +9 -5
  61. data/assets/stylesheets/semantic_ui/definitions/modules/sticky.less +1 -1
  62. data/assets/stylesheets/semantic_ui/definitions/modules/tab.less +1 -1
  63. data/assets/stylesheets/semantic_ui/definitions/modules/transition.less +3 -4
  64. data/assets/stylesheets/semantic_ui/definitions/views/card.less +240 -130
  65. data/assets/stylesheets/semantic_ui/definitions/views/comment.less +1 -1
  66. data/assets/stylesheets/semantic_ui/definitions/views/feed.less +15 -8
  67. data/assets/stylesheets/semantic_ui/definitions/views/item.less +13 -13
  68. data/assets/stylesheets/semantic_ui/definitions/views/statistic.less +230 -86
  69. data/assets/stylesheets/semantic_ui/theme.less +22 -15
  70. data/assets/stylesheets/semantic_ui/themes/amazon/elements/button.overrides +5 -5
  71. data/assets/stylesheets/semantic_ui/themes/amazon/elements/button.variables +5 -4
  72. data/assets/stylesheets/semantic_ui/themes/basic/collections/table.variables +1 -0
  73. data/assets/stylesheets/semantic_ui/themes/basic/views/card.variables +6 -4
  74. data/assets/stylesheets/semantic_ui/themes/bookish/elements/header.variables +4 -4
  75. data/assets/stylesheets/semantic_ui/themes/bootstrap3/elements/button.variables +1 -1
  76. data/assets/stylesheets/semantic_ui/themes/chubby/collections/form.overrides +8 -0
  77. data/assets/stylesheets/semantic_ui/themes/chubby/collections/menu.overrides +0 -0
  78. data/assets/stylesheets/semantic_ui/themes/chubby/collections/menu.variables +40 -0
  79. data/assets/stylesheets/semantic_ui/themes/classic/modules/progress.variables +1 -0
  80. data/assets/stylesheets/semantic_ui/themes/classic/views/card.overrides +1 -1
  81. data/assets/stylesheets/semantic_ui/themes/colored/modules/checkbox.overrides +0 -0
  82. data/assets/stylesheets/semantic_ui/themes/colored/modules/checkbox.variables +17 -0
  83. data/assets/stylesheets/semantic_ui/themes/default/collections/breadcrumb.overrides +0 -0
  84. data/assets/stylesheets/semantic_ui/themes/default/collections/breadcrumb.variables +5 -15
  85. data/assets/stylesheets/semantic_ui/themes/default/collections/form.overrides +0 -0
  86. data/assets/stylesheets/semantic_ui/themes/default/collections/form.variables +31 -35
  87. data/assets/stylesheets/semantic_ui/themes/default/collections/grid.overrides +0 -0
  88. data/assets/stylesheets/semantic_ui/themes/default/collections/grid.variables +34 -28
  89. data/assets/stylesheets/semantic_ui/themes/default/collections/menu.overrides +0 -0
  90. data/assets/stylesheets/semantic_ui/themes/default/collections/menu.variables +259 -163
  91. data/assets/stylesheets/semantic_ui/themes/default/collections/message.overrides +0 -0
  92. data/assets/stylesheets/semantic_ui/themes/default/collections/message.variables +42 -19
  93. data/assets/stylesheets/semantic_ui/themes/default/collections/table.overrides +0 -3
  94. data/assets/stylesheets/semantic_ui/themes/default/collections/table.variables +63 -59
  95. data/assets/stylesheets/semantic_ui/themes/default/elements/button.overrides +0 -0
  96. data/assets/stylesheets/semantic_ui/themes/default/elements/button.variables +90 -50
  97. data/assets/stylesheets/semantic_ui/themes/default/elements/container.overrides +3 -0
  98. data/assets/stylesheets/semantic_ui/themes/default/elements/container.variables +45 -0
  99. data/assets/stylesheets/semantic_ui/themes/default/elements/divider.overrides +15 -0
  100. data/assets/stylesheets/semantic_ui/themes/default/elements/divider.variables +3 -4
  101. data/assets/stylesheets/semantic_ui/themes/default/elements/flag.overrides +0 -0
  102. data/assets/stylesheets/semantic_ui/themes/default/elements/header.overrides +0 -0
  103. data/assets/stylesheets/semantic_ui/themes/default/elements/header.variables +40 -36
  104. data/assets/stylesheets/semantic_ui/themes/default/elements/icon.overrides +4 -3
  105. data/assets/stylesheets/semantic_ui/themes/default/elements/icon.variables +29 -12
  106. data/assets/stylesheets/semantic_ui/themes/default/elements/image.overrides +0 -0
  107. data/assets/stylesheets/semantic_ui/themes/default/elements/image.variables +5 -3
  108. data/assets/stylesheets/semantic_ui/themes/default/elements/input.overrides +0 -0
  109. data/assets/stylesheets/semantic_ui/themes/default/elements/input.variables +10 -24
  110. data/assets/stylesheets/semantic_ui/themes/default/elements/label.overrides +0 -0
  111. data/assets/stylesheets/semantic_ui/themes/default/elements/label.variables +88 -35
  112. data/assets/stylesheets/semantic_ui/themes/default/elements/list.overrides +0 -0
  113. data/assets/stylesheets/semantic_ui/themes/default/elements/list.variables +53 -34
  114. data/assets/stylesheets/semantic_ui/themes/default/elements/loader.overrides +0 -0
  115. data/assets/stylesheets/semantic_ui/themes/default/elements/loader.variables +9 -10
  116. data/assets/stylesheets/semantic_ui/themes/default/elements/rail.overrides +0 -0
  117. data/assets/stylesheets/semantic_ui/themes/default/elements/rail.variables +13 -5
  118. data/assets/stylesheets/semantic_ui/themes/default/elements/reveal.overrides +0 -0
  119. data/assets/stylesheets/semantic_ui/themes/default/elements/reveal.variables +6 -4
  120. data/assets/stylesheets/semantic_ui/themes/default/elements/segment.overrides +0 -0
  121. data/assets/stylesheets/semantic_ui/themes/default/elements/segment.variables +66 -28
  122. data/assets/stylesheets/semantic_ui/themes/default/elements/step.overrides +0 -0
  123. data/assets/stylesheets/semantic_ui/themes/default/elements/step.variables +60 -31
  124. data/assets/stylesheets/semantic_ui/themes/default/globals/reset.overrides +0 -0
  125. data/assets/stylesheets/semantic_ui/themes/default/globals/site.variables +477 -303
  126. data/assets/stylesheets/semantic_ui/themes/default/modules/accordion.overrides +0 -0
  127. data/assets/stylesheets/semantic_ui/themes/default/modules/accordion.variables +6 -7
  128. data/assets/stylesheets/semantic_ui/themes/default/modules/chatroom.overrides +0 -0
  129. data/assets/stylesheets/semantic_ui/themes/default/modules/checkbox.overrides +19 -16
  130. data/assets/stylesheets/semantic_ui/themes/default/modules/checkbox.variables +71 -43
  131. data/assets/stylesheets/semantic_ui/themes/default/modules/dimmer.overrides +0 -0
  132. data/assets/stylesheets/semantic_ui/themes/default/modules/dimmer.variables +12 -9
  133. data/assets/stylesheets/semantic_ui/themes/default/modules/dropdown.overrides +0 -5
  134. data/assets/stylesheets/semantic_ui/themes/default/modules/dropdown.variables +146 -64
  135. data/assets/stylesheets/semantic_ui/themes/default/modules/{video.overrides → embed.overrides} +0 -0
  136. data/assets/stylesheets/semantic_ui/themes/default/modules/embed.variables +53 -0
  137. data/assets/stylesheets/semantic_ui/themes/default/modules/modal.overrides +0 -0
  138. data/assets/stylesheets/semantic_ui/themes/default/modules/modal.variables +24 -18
  139. data/assets/stylesheets/semantic_ui/themes/default/modules/nag.overrides +0 -0
  140. data/assets/stylesheets/semantic_ui/themes/default/modules/popup.overrides +0 -0
  141. data/assets/stylesheets/semantic_ui/themes/default/modules/popup.variables +3 -9
  142. data/assets/stylesheets/semantic_ui/themes/default/modules/progress.variables +13 -7
  143. data/assets/stylesheets/semantic_ui/themes/default/modules/rating.overrides +0 -0
  144. data/assets/stylesheets/semantic_ui/themes/default/modules/rating.variables +71 -33
  145. data/assets/stylesheets/semantic_ui/themes/default/modules/search.overrides +0 -0
  146. data/assets/stylesheets/semantic_ui/themes/default/modules/search.variables +36 -34
  147. data/assets/stylesheets/semantic_ui/themes/default/modules/shape.overrides +0 -0
  148. data/assets/stylesheets/semantic_ui/themes/default/modules/shape.variables +7 -2
  149. data/assets/stylesheets/semantic_ui/themes/default/modules/sidebar.overrides +0 -0
  150. data/assets/stylesheets/semantic_ui/themes/default/modules/sidebar.variables +2 -5
  151. data/assets/stylesheets/semantic_ui/themes/default/modules/sticky.overrides +0 -0
  152. data/assets/stylesheets/semantic_ui/themes/default/modules/sticky.variables +2 -7
  153. data/assets/stylesheets/semantic_ui/themes/default/modules/tab.variables +1 -3
  154. data/assets/stylesheets/semantic_ui/themes/default/modules/transition.overrides +8 -9
  155. data/assets/stylesheets/semantic_ui/themes/default/views/ad.variables +2 -2
  156. data/assets/stylesheets/semantic_ui/themes/default/views/card.variables +33 -23
  157. data/assets/stylesheets/semantic_ui/themes/default/views/comment.overrides +0 -0
  158. data/assets/stylesheets/semantic_ui/themes/default/views/feed.overrides +0 -0
  159. data/assets/stylesheets/semantic_ui/themes/default/views/feed.variables +15 -23
  160. data/assets/stylesheets/semantic_ui/themes/default/views/item.overrides +0 -0
  161. data/assets/stylesheets/semantic_ui/themes/default/views/item.variables +12 -11
  162. data/assets/stylesheets/semantic_ui/themes/default/views/statistic.overrides +0 -0
  163. data/assets/stylesheets/semantic_ui/themes/default/views/statistic.variables +25 -24
  164. data/assets/stylesheets/semantic_ui/themes/flat/collections/form.overrides +4 -0
  165. data/assets/stylesheets/semantic_ui/themes/flat/collections/form.variables +1 -2
  166. data/assets/stylesheets/semantic_ui/themes/flat/globals/site.variables +1 -0
  167. data/assets/stylesheets/semantic_ui/themes/github/collections/form.variables +1 -1
  168. data/assets/stylesheets/semantic_ui/themes/github/collections/menu.variables +10 -15
  169. data/assets/stylesheets/semantic_ui/themes/github/elements/button.variables +2 -2
  170. data/assets/stylesheets/semantic_ui/themes/github/elements/step.variables +2 -2
  171. data/assets/stylesheets/semantic_ui/themes/instagram/views/card.overrides +12 -0
  172. data/assets/stylesheets/semantic_ui/themes/instagram/views/card.variables +23 -0
  173. data/assets/stylesheets/semantic_ui/themes/material/collections/menu.overrides +1 -0
  174. data/assets/stylesheets/semantic_ui/themes/material/collections/menu.variables +10 -0
  175. data/assets/stylesheets/semantic_ui/themes/material/elements/button.overrides +1 -0
  176. data/assets/stylesheets/semantic_ui/themes/material/elements/button.variables +14 -6
  177. data/assets/stylesheets/semantic_ui/themes/material/globals/site.variables +3 -2
  178. data/assets/stylesheets/semantic_ui/themes/material/modules/dropdown.overrides +5 -0
  179. data/assets/stylesheets/semantic_ui/themes/material/modules/dropdown.variables +20 -0
  180. data/assets/stylesheets/semantic_ui/themes/raised/elements/button.variables +5 -5
  181. data/assets/stylesheets/semantic_ui/themes/round/elements/button.variables +1 -1
  182. data/assets/stylesheets/semantic_ui/themes/timeline/views/feed.overrides +4 -12
  183. data/assets/stylesheets/semantic_ui/themes/timeline/views/feed.variables +3 -7
  184. data/lib/generators/semantic_ui/install/templates/config/elements/container.overrides +3 -0
  185. data/lib/generators/semantic_ui/install/templates/config/elements/container.variables +3 -0
  186. data/lib/generators/semantic_ui/install/templates/config/modules/embed.overrides +3 -0
  187. data/lib/generators/semantic_ui/install/templates/config/modules/embed.variables +0 -0
  188. data/lib/generators/semantic_ui/install/templates/semantic_ui.css +2 -1
  189. data/lib/generators/semantic_ui/install/templates/semantic_ui.js +1 -1
  190. data/lib/generators/semantic_ui/install/templates/theme.config +5 -3
  191. data/lib/less/rails/semantic_ui/version.rb +1 -1
  192. data/tasks/update.rake +21 -6
  193. metadata +23 -7
  194. data/assets/fonts/semantic_ui/themes/default/assets/fonts/icons.otf +0 -0
  195. data/assets/javascripts/semantic_ui/definitions/modules/video.js +0 -540
  196. data/assets/stylesheets/semantic_ui/definitions/modules/video.less +0 -135
  197. data/assets/stylesheets/semantic_ui/themes/default/modules/video.variables +0 -16
@@ -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
  *
@@ -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 {
@@ -99,8 +100,8 @@ $.fn.dimmer = function(parameters) {
99
100
 
100
101
  if( module.is.closable() ) {
101
102
  module.verbose('Adding dimmer close event', $dimmer);
102
- $dimmer
103
- .on(clickEvent + eventNamespace, module.event.click)
103
+ $dimmable
104
+ .on(clickEvent + eventNamespace, selector.dimmer, module.event.click)
104
105
  ;
105
106
  }
106
107
  module.set.dimmable();
@@ -123,9 +124,6 @@ $.fn.dimmer = function(parameters) {
123
124
  $dimmable
124
125
  .off(eventNamespace)
125
126
  ;
126
- $dimmer
127
- .off(eventNamespace)
128
- ;
129
127
  },
130
128
 
131
129
  event: {
@@ -313,10 +311,10 @@ $.fn.dimmer = function(parameters) {
313
311
  has: {
314
312
  dimmer: function() {
315
313
  if(settings.dimmerName) {
316
- return ($module.children(selector.dimmer).filter('.' + settings.dimmerName).length > 0);
314
+ return ($module.find(selector.dimmer).filter('.' + settings.dimmerName).length > 0);
317
315
  }
318
316
  else {
319
- return ( $module.children(selector.dimmer).length > 0 );
317
+ return ( $module.find(selector.dimmer).length > 0 );
320
318
  }
321
319
  }
322
320
  },
@@ -338,10 +336,10 @@ $.fn.dimmer = function(parameters) {
338
336
  return settings.closable;
339
337
  },
340
338
  dimmer: function() {
341
- return $module.is(selector.dimmer);
339
+ return $module.hasClass(className.dimmer);
342
340
  },
343
341
  dimmable: function() {
344
- return $module.is(selector.dimmable);
342
+ return $module.hasClass(className.dimmable);
345
343
  },
346
344
  dimmed: function() {
347
345
  return $dimmable.hasClass(className.dimmed);
@@ -484,7 +482,7 @@ $.fn.dimmer = function(parameters) {
484
482
  });
485
483
  }
486
484
  clearTimeout(module.performance.timer);
487
- module.performance.timer = setTimeout(module.performance.display, 100);
485
+ module.performance.timer = setTimeout(module.performance.display, 500);
488
486
  },
489
487
  display: function() {
490
488
  var
@@ -603,7 +601,7 @@ $.fn.dimmer.settings = {
603
601
  namespace : 'dimmer',
604
602
 
605
603
  debug : false,
606
- verbose : true,
604
+ verbose : false,
607
605
  performance : true,
608
606
 
609
607
  // name to distinguish between multiple dimmers in context
@@ -641,27 +639,27 @@ $.fn.dimmer.settings = {
641
639
  method : 'The method you called is not defined.'
642
640
  },
643
641
 
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
642
  className : {
657
643
  active : 'active',
658
644
  animating : 'animating',
659
645
  dimmable : 'dimmable',
660
646
  dimmed : 'dimmed',
647
+ dimmer : 'dimmer',
661
648
  disabled : 'disabled',
662
649
  hide : 'hide',
663
650
  pageDimmer : 'page',
664
651
  show : 'show'
652
+ },
653
+
654
+ selector: {
655
+ dimmer : '> .ui.dimmer',
656
+ content : '.ui.dimmer > .content, .ui.dimmer > .content > .center'
657
+ },
658
+
659
+ template: {
660
+ dimmer: function() {
661
+ return $('<div />').attr('class', 'ui dimmer');
662
+ }
665
663
  }
666
664
 
667
665
  };
@@ -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
  *
@@ -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,151 @@ $.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();
270
+ console.log($module);
167
271
  }
168
- if( module.is.search() && !module.is.searchable() ) {
272
+ if( module.is.search() && !module.has.search() ) {
273
+ module.verbose('Adding search input');
169
274
  $search = $('<input />')
170
275
  .addClass(className.search)
171
276
  .insertBefore($text)
@@ -174,6 +279,12 @@ $.fn.dropdown = function(parameters) {
174
279
  if(settings.allowTab) {
175
280
  module.set.tabbable();
176
281
  }
282
+ if($menu.length === 0) {
283
+ $menu = $('<div />')
284
+ .addClass(className.menu)
285
+ .appendTo($module)
286
+ ;
287
+ }
177
288
  },
178
289
  select: function() {
179
290
  var
@@ -188,13 +299,7 @@ $.fn.dropdown = function(parameters) {
188
299
  module.debug('UI dropdown already exists. Creating dropdown menu only');
189
300
  $module = $input.closest(selector.dropdown);
190
301
  $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 ));
302
+ module.setup.menu(selectValues);
198
303
  }
199
304
  else {
200
305
  module.debug('Creating entire dropdown from select');
@@ -202,51 +307,80 @@ $.fn.dropdown = function(parameters) {
202
307
  .attr('class', $input.attr('class') )
203
308
  .addClass(className.selection)
204
309
  .addClass(className.dropdown)
205
- .html( settings.templates.dropdown(selectValues) )
310
+ .html( templates.dropdown(selectValues) )
206
311
  .insertBefore($input)
207
312
  ;
208
313
  $input
209
314
  .removeAttr('class')
315
+ .detach()
210
316
  .prependTo($module)
211
317
  ;
318
+ console.log($module);
319
+ }
320
+ if($input.is('[multiple]')) {
321
+ module.set.multiple();
212
322
  }
213
323
  module.refresh();
214
324
  },
325
+ menu: function(values) {
326
+ $menu.html( templates.menu( values ));
327
+ $item = $menu.find(selector.item);
328
+ },
215
329
  reference: function() {
216
- var
217
- index = $allModules.index($module),
218
- $firstModules,
219
- $lastModules
220
- ;
221
330
  module.debug('Dropdown behavior was called on select, replacing with closest dropdown');
222
331
  // replace module reference
223
332
  $module = $module.parent(selector.dropdown);
224
333
  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);
334
+ module.setup.returnedObject();
229
335
  // invoke method in context of current instance
230
336
  if(methodInvoked) {
231
337
  instance = module;
232
338
  module.invoke(query);
233
339
  }
340
+ },
341
+ returnedObject: function() {
342
+ var
343
+ $firstModules = $allModules.slice(0, elementIndex),
344
+ $lastModules = $allModules.slice(elementIndex + 1)
345
+ ;
346
+ // adjust all modules to use correct reference
347
+ $allModules = $firstModules.add($module).add($lastModules);
234
348
  }
235
349
  },
236
350
 
237
351
  refresh: function() {
352
+ module.refreshSelectors();
353
+ module.refreshData();
354
+ },
355
+
356
+ refreshSelectors: function() {
238
357
  module.verbose('Refreshing selector cache');
239
358
  $text = $module.find(selector.text);
240
359
  $search = $module.find(selector.search);
241
360
  $input = $module.find(selector.input);
361
+ $icon = $module.find(selector.icon);
242
362
  $combo = ($module.prev().find(selector.text).length > 0)
243
363
  ? $module.prev().find(selector.text)
244
364
  : $module.prev()
245
365
  ;
246
- $menu = $module.children(selector.menu);
247
- $item = $menu.find(selector.item);
366
+ $menu = $module.children(selector.menu);
367
+ $item = $menu.find(selector.item);
368
+ },
369
+
370
+ refreshData: function() {
371
+ module.verbose('Refreshing cached metadata');
372
+ $item
373
+ .removeData(metadata.text)
374
+ .removeData(metadata.value)
375
+ ;
376
+ $module
377
+ .removeData(metadata.defaultText)
378
+ .removeData(metadata.defaultValue)
379
+ .removeData(metadata.placeholderText)
380
+ ;
248
381
  },
249
382
 
383
+
250
384
  toggle: function() {
251
385
  module.verbose('Toggling menu visibility');
252
386
  if( !module.is.active() ) {
@@ -262,11 +396,13 @@ $.fn.dropdown = function(parameters) {
262
396
  ? callback
263
397
  : function(){}
264
398
  ;
265
- if( module.is.searchSelection() && module.is.allFiltered() ) {
266
- return;
267
- }
268
399
  if( module.can.show() && !module.is.active() ) {
269
400
  module.debug('Showing dropdown');
401
+ if(module.is.multiple()) {
402
+ if(!module.has.search() && module.is.allFiltered()) {
403
+ return true;
404
+ }
405
+ }
270
406
  module.animate.show(function() {
271
407
  if( module.can.click() ) {
272
408
  module.bind.intent();
@@ -297,15 +433,23 @@ $.fn.dropdown = function(parameters) {
297
433
  module.verbose('Finding other dropdowns to hide');
298
434
  $allModules
299
435
  .not($module)
300
- .has(selector.menu + ':visible:not(.' + className.animating + ')')
436
+ .has(selector.menu + '.' + className.visible)
301
437
  .dropdown('hide')
302
438
  ;
303
439
  },
304
440
 
441
+ hideMenu: function() {
442
+ module.verbose('Hiding menu instantaneously');
443
+ module.remove.active();
444
+ module.remove.visible();
445
+ $menu.transition('hide');
446
+ },
447
+
305
448
  hideSubMenus: function() {
306
449
  var
307
- $subMenus = $menu.find(selector.menu)
450
+ $subMenus = $menu.children(selector.item).find(selector.menu)
308
451
  ;
452
+ module.verbose('Hiding sub menus', $subMenus);
309
453
  $subMenus.transition('hide');
310
454
  },
311
455
 
@@ -315,9 +459,14 @@ $.fn.dropdown = function(parameters) {
315
459
  $module
316
460
  .on('keydown' + eventNamespace, module.event.keydown)
317
461
  ;
318
- if( module.is.searchable() ) {
462
+ if( module.has.search() ) {
319
463
  $module
320
- .on(module.get.inputEvent(), selector.search, module.event.input)
464
+ .on(module.get.inputEvent() + eventNamespace, selector.search, module.event.input)
465
+ ;
466
+ }
467
+ if( module.is.multiple() ) {
468
+ $document
469
+ .on('keydown' + elementNamespace, module.event.document.keydown)
321
470
  ;
322
471
  }
323
472
  },
@@ -336,16 +485,27 @@ $.fn.dropdown = function(parameters) {
336
485
  ;
337
486
  },
338
487
  mouseEvents: function() {
339
- module.verbose('Mouse detected binding mouse events');
488
+ module.debug('Mouse detected binding mouse events');
489
+ if(module.is.multiple()) {
490
+ $module
491
+ .on('click' + eventNamespace, selector.label, module.event.label.click)
492
+ .on('click' + eventNamespace, selector.remove, module.event.remove.click)
493
+ ;
494
+ }
340
495
  if( module.is.searchSelection() ) {
341
496
  $module
342
- .on('mousedown' + eventNamespace, selector.menu, module.event.menu.activate)
343
- .on('mouseup' + eventNamespace, selector.menu, module.event.menu.deactivate)
497
+ .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown)
498
+ .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup)
344
499
  .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)
500
+ .on('focus' + eventNamespace, selector.search, module.event.search.focus)
501
+ .on('blur' + eventNamespace, selector.search, module.event.search.blur)
502
+ .on('click' + eventNamespace, selector.text, module.event.text.focus)
348
503
  ;
504
+ if(module.is.multiple()) {
505
+ $module
506
+ .on('click' + eventNamespace, module.event.click)
507
+ ;
508
+ }
349
509
  }
350
510
  else {
351
511
  if(settings.on == 'click') {
@@ -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
+ }
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')
434
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,127 @@ $.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) || $target.is($icon)) && !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
+ text: {
829
+ focus: function(event) {
830
+ activated = true;
831
+ module.focusSearch();
832
+ }
526
833
  },
527
834
  input: function(event) {
528
- if(module.is.searchSelection()) {
835
+ if(module.is.multiple() || module.is.searchSelection()) {
529
836
  module.set.filtered();
530
837
  }
531
838
  clearTimeout(module.timer);
532
839
  module.timer = setTimeout(module.search, settings.delay.search);
533
840
  },
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
- }
841
+ label: {
842
+ click: function(event) {
843
+ var
844
+ $label = $(this),
845
+ $labels = $module.find(selector.label),
846
+ $activeLabels = $labels.filter('.' + className.active),
847
+ $nextActive = $label.nextAll('.' + className.active),
848
+ $prevActive = $label.prevAll('.' + className.active),
849
+ $range = ($nextActive.length > 0)
850
+ ? $label.nextUntil($nextActive).add($activeLabels).add($label)
851
+ : $label.prevUntil($prevActive).add($activeLabels).add($label)
852
+ ;
853
+ if(event.shiftKey) {
854
+ $activeLabels.removeClass(className.active);
855
+ $range.addClass(className.active);
605
856
  }
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();
857
+ else if(event.ctrlKey) {
858
+ $label.toggleClass(className.active);
627
859
  }
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();
860
+ else {
861
+ $activeLabels.removeClass(className.active);
862
+ $label.addClass(className.active);
649
863
  }
864
+ settings.onLabelSelect.apply(this, $labels.filter('.' + className.active));
650
865
  }
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();
866
+ },
867
+ remove: {
868
+ click: function() {
869
+ var
870
+ $label = $(this).parent()
871
+ ;
872
+ if( $label.hasClass(className.active) ) {
873
+ // remove all selected labels
874
+ module.remove.activeLabels();
661
875
  }
662
- // down arrow (open menu)
663
- if(pressedKey == keys.downArrow) {
664
- module.verbose('Down key pressed, showing dropdown');
665
- module.show();
876
+ else {
877
+ // remove this label only
878
+ module.remove.activeLabels( $label );
666
879
  }
667
880
  }
668
881
  },
669
882
  test: {
670
883
  toggle: function(event) {
671
- if( module.determine.eventInMenu(event, module.toggle) ) {
884
+ var
885
+ toggleBehavior = (module.is.multiple())
886
+ ? module.show
887
+ : module.toggle
888
+ ;
889
+ if( module.determine.eventOnElement(event, toggleBehavior) ) {
672
890
  event.preventDefault();
673
891
  }
674
892
  },
675
893
  touch: function(event) {
676
- module.determine.eventInMenu(event, function() {
894
+ module.determine.eventOnElement(event, function() {
677
895
  if(event.type == 'touchstart') {
678
896
  module.timer = setTimeout(module.hide, settings.delay.touch);
679
897
  }
@@ -687,12 +905,11 @@ $.fn.dropdown = function(parameters) {
687
905
  module.determine.eventInModule(event, module.hide);
688
906
  }
689
907
  },
690
-
691
908
  menu: {
692
- activate: function() {
909
+ mousedown: function() {
693
910
  itemActivated = true;
694
911
  },
695
- deactivate: function() {
912
+ mouseup: function() {
696
913
  itemActivated = false;
697
914
  }
698
915
  },
@@ -735,38 +952,320 @@ $.fn.dropdown = function(parameters) {
735
952
  $subMenu = $choice.find(selector.menu),
736
953
  text = module.get.choiceText($choice),
737
954
  value = module.get.choiceValue($choice, text),
738
- callback = function() {
739
- module.remove.searchTerm();
740
- module.determine.selectAction(text, value);
741
- },
742
955
  hasSubMenu = ($subMenu.length > 0),
743
956
  isBubbledEvent = ($subMenu.find($target).length > 0)
744
957
  ;
745
958
  if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) {
746
- callback();
959
+ if(!settings.useLabels) {
960
+ module.remove.searchTerm();
961
+ }
962
+ module.determine.selectAction.call(this, text, value);
747
963
  }
748
964
  }
749
965
  },
750
- resetStyle: function() {
751
- $(this).removeAttr('style');
752
- }
753
- },
754
966
 
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
- }
766
- else {
767
- module.error(error.action, settings.action);
768
- }
769
- },
967
+ document: {
968
+ // label selection should occur even when element has no focus
969
+ keydown: function(event) {
970
+ var
971
+ pressedKey = event.which,
972
+ keys = module.get.shortcutKeys(),
973
+ isShortcutKey = module.is.inObject(pressedKey, keys)
974
+ ;
975
+ if(isShortcutKey) {
976
+ var
977
+ $label = $module.find(selector.label),
978
+ $activeLabel = $label.filter('.' + className.active),
979
+ activeValue = $activeLabel.data('value'),
980
+ labelIndex = $label.index($activeLabel),
981
+ labelCount = $label.length,
982
+ hasActiveLabel = ($activeLabel.length > 0),
983
+ hasMultipleActive = ($activeLabel.length > 1),
984
+ isFirstLabel = (labelIndex === 0),
985
+ isLastLabel = (labelIndex + 1 == labelCount),
986
+ isSearch = module.is.searchSelection(),
987
+ isFocusedOnSearch = module.is.focusedOnSearch(),
988
+ isFocused = module.is.focused(),
989
+ caretAtStart = (isFocusedOnSearch && module.get.caretPosition() === 0),
990
+ $nextLabel
991
+ ;
992
+ if(isSearch && !hasActiveLabel && !isFocusedOnSearch) {
993
+ return;
994
+ }
995
+
996
+ if(pressedKey == keys.leftArrow) {
997
+ // activate previous label
998
+ if((isFocused || caretAtStart) && !hasActiveLabel) {
999
+ module.verbose('Selecting previous label');
1000
+ $label.last().addClass(className.active);
1001
+ }
1002
+ else if(hasActiveLabel) {
1003
+ if(!event.shiftKey) {
1004
+ module.verbose('Selecting previous label');
1005
+ $label.removeClass(className.active);
1006
+ }
1007
+ else {
1008
+ module.verbose('Adding previous label to selection');
1009
+ }
1010
+ if(isFirstLabel && !hasMultipleActive) {
1011
+ $activeLabel.addClass(className.active);
1012
+ }
1013
+ else {
1014
+ $activeLabel.prev(selector.siblingLabel)
1015
+ .addClass(className.active)
1016
+ .end()
1017
+ ;
1018
+ }
1019
+ event.preventDefault();
1020
+ }
1021
+ }
1022
+ else if(pressedKey == keys.rightArrow) {
1023
+ // activate first label
1024
+ if(isFocused && !hasActiveLabel) {
1025
+ $label.first().addClass(className.active);
1026
+ }
1027
+ // activate next label
1028
+ if(hasActiveLabel) {
1029
+ if(!event.shiftKey) {
1030
+ module.verbose('Selecting next label');
1031
+ $label.removeClass(className.active);
1032
+ }
1033
+ else {
1034
+ module.verbose('Adding next label to selection');
1035
+ }
1036
+ if(isLastLabel) {
1037
+ if(isSearch) {
1038
+ if(!isFocusedOnSearch) {
1039
+ module.focusSearch();
1040
+ }
1041
+ else {
1042
+ $label.removeClass(className.active);
1043
+ }
1044
+ }
1045
+ else if(hasMultipleActive) {
1046
+ $activeLabel.next(selector.siblingLabel).addClass(className.active);
1047
+ }
1048
+ else {
1049
+ $activeLabel.addClass(className.active);
1050
+ }
1051
+ }
1052
+ else {
1053
+ $activeLabel.next(selector.siblingLabel).addClass(className.active);
1054
+ }
1055
+ event.preventDefault();
1056
+ }
1057
+ }
1058
+ else if(pressedKey == keys.deleteKey || pressedKey == keys.backspace) {
1059
+ if(hasActiveLabel) {
1060
+ module.verbose('Removing active labels');
1061
+ if(isLastLabel) {
1062
+ if(isSearch && !isFocusedOnSearch) {
1063
+ module.focusSearch();
1064
+ }
1065
+ }
1066
+ $activeLabel.last().next(selector.siblingLabel).addClass(className.active);
1067
+ module.remove.activeLabels($activeLabel);
1068
+ event.preventDefault();
1069
+ }
1070
+ else if(caretAtStart && !hasActiveLabel && pressedKey == keys.backspace) {
1071
+ module.verbose('Removing last label on input backspace');
1072
+ $activeLabel = $label.last().addClass(className.active);
1073
+ module.remove.activeLabels($activeLabel);
1074
+ }
1075
+ }
1076
+ else {
1077
+ $activeLabel.removeClass(className.active);
1078
+ }
1079
+ }
1080
+ }
1081
+ },
1082
+
1083
+ keydown: function(event) {
1084
+ var
1085
+ pressedKey = event.which,
1086
+ keys = module.get.shortcutKeys(),
1087
+ isShortcutKey = module.is.inObject(pressedKey, keys)
1088
+ ;
1089
+ if(isShortcutKey) {
1090
+ var
1091
+ $currentlySelected = $item.not(selector.unselectable).filter('.' + className.selected).eq(0),
1092
+ $activeItem = $menu.children('.' + className.active).eq(0),
1093
+ $selectedItem = ($currentlySelected.length > 0)
1094
+ ? $currentlySelected
1095
+ : $activeItem,
1096
+ $visibleItems = ($selectedItem.length > 0)
1097
+ ? $selectedItem.siblings(':not(.' + className.filtered +')').andSelf()
1098
+ : $menu.children(':not(.' + className.filtered +')'),
1099
+ $subMenu = $selectedItem.children(selector.menu),
1100
+ $parentMenu = $selectedItem.closest(selector.menu),
1101
+ inVisibleMenu = ($parentMenu.hasClass(className.visible) || $parentMenu.hasClass(className.animating) || $parentMenu.parent(selector.menu).length > 0),
1102
+ hasSubMenu = ($subMenu.length> 0),
1103
+ hasSelectedItem = ($selectedItem.length > 0),
1104
+ selectedIsVisible = ($selectedItem.not(selector.unselectable).length > 0),
1105
+ $nextItem,
1106
+ isSubMenuItem,
1107
+ newIndex
1108
+ ;
1109
+
1110
+ // visible menu keyboard shortcuts
1111
+ if( module.is.visible() ) {
1112
+
1113
+ // enter (select or open sub-menu)
1114
+ if(pressedKey == keys.enter || pressedKey == keys.delimiter) {
1115
+
1116
+ if(pressedKey == keys.enter && hasSelectedItem && hasSubMenu && !settings.allowCategorySelection) {
1117
+ module.verbose('Pressed enter on unselectable category, opening sub menu');
1118
+ pressedKey = keys.rightArrow;
1119
+ }
1120
+ else if(selectedIsVisible) {
1121
+ module.verbose('Selecting item from keyboard shortcut', $selectedItem);
1122
+ module.event.item.click.call($selectedItem, event);
1123
+ if(settings.useLabels && module.is.searchSelection()) {
1124
+ module.hideAndClear();
1125
+ }
1126
+ else {
1127
+ module.remove.searchTerm();
1128
+ }
1129
+ }
1130
+ event.preventDefault();
1131
+ }
1132
+
1133
+ // left arrow (hide sub-menu)
1134
+ if(pressedKey == keys.leftArrow) {
1135
+
1136
+ isSubMenuItem = ($parentMenu[0] !== $menu[0]);
1137
+
1138
+ if(isSubMenuItem) {
1139
+ module.verbose('Left key pressed, closing sub-menu');
1140
+ module.animate.hide(false, $parentMenu);
1141
+ $selectedItem
1142
+ .removeClass(className.selected)
1143
+ ;
1144
+ $parentMenu
1145
+ .closest(selector.item)
1146
+ .addClass(className.selected)
1147
+ ;
1148
+ event.preventDefault();
1149
+ }
1150
+ }
1151
+
1152
+ // right arrow (show sub-menu)
1153
+ if(pressedKey == keys.rightArrow) {
1154
+ if(hasSubMenu) {
1155
+ module.verbose('Right key pressed, opening sub-menu');
1156
+ module.animate.show(false, $subMenu);
1157
+ $selectedItem
1158
+ .removeClass(className.selected)
1159
+ ;
1160
+ $subMenu
1161
+ .find(selector.item).eq(0)
1162
+ .addClass(className.selected)
1163
+ ;
1164
+ event.preventDefault();
1165
+ }
1166
+ }
1167
+
1168
+ // up arrow (traverse menu up)
1169
+ if(pressedKey == keys.upArrow) {
1170
+ $nextItem = (hasSelectedItem && inVisibleMenu)
1171
+ ? $selectedItem.prevAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
1172
+ : $item.eq(0)
1173
+ ;
1174
+ if($visibleItems.index( $nextItem ) < 0) {
1175
+ module.verbose('Up key pressed but reached top of current menu');
1176
+ event.preventDefault();
1177
+ return;
1178
+ }
1179
+ else {
1180
+ module.verbose('Up key pressed, changing active item');
1181
+ $selectedItem
1182
+ .removeClass(className.selected)
1183
+ ;
1184
+ $nextItem
1185
+ .addClass(className.selected)
1186
+ ;
1187
+ module.set.scrollPosition($nextItem);
1188
+ }
1189
+ event.preventDefault();
1190
+ }
1191
+
1192
+ // down arrow (traverse menu down)
1193
+ if(pressedKey == keys.downArrow) {
1194
+ $nextItem = (hasSelectedItem && inVisibleMenu)
1195
+ ? $nextItem = $selectedItem.nextAll(selector.item + ':not(' + selector.unselectable + ')').eq(0)
1196
+ : $item.eq(0)
1197
+ ;
1198
+ if($nextItem.length === 0) {
1199
+ module.verbose('Down key pressed but reached bottom of current menu');
1200
+ event.preventDefault();
1201
+ return;
1202
+ }
1203
+ else {
1204
+ module.verbose('Down key pressed, changing active item');
1205
+ $item
1206
+ .removeClass(className.selected)
1207
+ ;
1208
+ $nextItem
1209
+ .addClass(className.selected)
1210
+ ;
1211
+ module.set.scrollPosition($nextItem);
1212
+ }
1213
+ event.preventDefault();
1214
+ }
1215
+
1216
+ // page down (show next page)
1217
+ if(pressedKey == keys.pageUp) {
1218
+ module.scrollPage('up');
1219
+ event.preventDefault();
1220
+ }
1221
+ if(pressedKey == keys.pageDown) {
1222
+ module.scrollPage('down');
1223
+ event.preventDefault();
1224
+ }
1225
+
1226
+ // escape (close menu)
1227
+ if(pressedKey == keys.escape) {
1228
+ module.verbose('Escape key pressed, closing dropdown');
1229
+ module.hide();
1230
+ }
1231
+
1232
+ }
1233
+ else {
1234
+ // delimiter key
1235
+ if(pressedKey == keys.delimiter) {
1236
+ event.preventDefault();
1237
+ }
1238
+ // down arrow (open menu)
1239
+ if(pressedKey == keys.downArrow) {
1240
+ module.verbose('Down key pressed, showing dropdown');
1241
+ module.show();
1242
+ event.preventDefault();
1243
+ }
1244
+ }
1245
+ }
1246
+ else {
1247
+ if( module.is.selection() && !module.is.search() ) {
1248
+ module.set.selectedLetter( String.fromCharCode(pressedKey) );
1249
+ }
1250
+ }
1251
+ }
1252
+ },
1253
+
1254
+ determine: {
1255
+ selectAction: function(text, value) {
1256
+ module.verbose('Determining action', settings.action);
1257
+ if( $.isFunction( module.action[settings.action] ) ) {
1258
+ module.verbose('Triggering preset action', settings.action, text, value);
1259
+ module.action[ settings.action ].call(this, text, value);
1260
+ }
1261
+ else if( $.isFunction(settings.action) ) {
1262
+ module.verbose('Triggering user action', settings.action, text, value);
1263
+ settings.action.call(this, text, value);
1264
+ }
1265
+ else {
1266
+ module.error(error.action, settings.action);
1267
+ }
1268
+ },
770
1269
  eventInModule: function(event, callback) {
771
1270
  callback = $.isFunction(callback)
772
1271
  ? callback
@@ -782,12 +1281,15 @@ $.fn.dropdown = function(parameters) {
782
1281
  return false;
783
1282
  }
784
1283
  },
785
- eventInMenu: function(event, callback) {
1284
+ eventOnElement: function(event, callback) {
1285
+ var
1286
+ $target = $(event.target)
1287
+ ;
786
1288
  callback = $.isFunction(callback)
787
1289
  ? callback
788
1290
  : function(){}
789
1291
  ;
790
- if( $(event.target).closest($menu).length === 0 ) {
1292
+ if($target.closest($menu).length === 0) {
791
1293
  module.verbose('Triggering event', callback);
792
1294
  callback();
793
1295
  return true;
@@ -808,21 +1310,18 @@ $.fn.dropdown = function(parameters) {
808
1310
  ? value
809
1311
  : text
810
1312
  ;
811
- module.set.selected(value);
812
- module.hide(function() {
813
- module.remove.filteredItem();
814
- });
1313
+ module.set.selected(value, $(this));
1314
+ if(module.is.multiple() && !module.is.allFiltered()) {
1315
+ return;
1316
+ }
1317
+ else {
1318
+ module.hideAndClear();
1319
+ }
815
1320
  },
816
1321
 
817
1322
  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
- });
1323
+ // mimics action.activate but does not select text
1324
+ module.action.activate.call(this);
826
1325
  },
827
1326
 
828
1327
  combo: function(text, value) {
@@ -830,16 +1329,12 @@ $.fn.dropdown = function(parameters) {
830
1329
  ? value
831
1330
  : text
832
1331
  ;
833
- module.set.selected(value);
834
- module.hide(function() {
835
- module.remove.filteredItem();
836
- });
1332
+ module.set.selected(value, $(this));
1333
+ module.hideAndClear();
837
1334
  },
838
1335
 
839
1336
  hide: function() {
840
- module.hide(function() {
841
- module.remove.filteredItem();
842
- });
1337
+ module.hideAndClear();
843
1338
  }
844
1339
 
845
1340
  },
@@ -851,18 +1346,134 @@ $.fn.dropdown = function(parameters) {
851
1346
  text: function() {
852
1347
  return $text.text();
853
1348
  },
1349
+ query: function() {
1350
+ return $.trim($search.val());
1351
+ },
1352
+ searchWidth: function(characterCount) {
1353
+ return (characterCount * settings.glyphWidth) + 'em';
1354
+ },
1355
+ selectionCount: function() {
1356
+ var
1357
+ values = module.get.values()
1358
+ ;
1359
+ return ( module.is.multiple() )
1360
+ ? $.isArray(values)
1361
+ ? values.length
1362
+ : 0
1363
+ : (module.get.value() !== '')
1364
+ ? 1
1365
+ : 0
1366
+ ;
1367
+ },
1368
+ transition: function($subMenu) {
1369
+ return (settings.transition == 'auto')
1370
+ ? module.is.upward($subMenu)
1371
+ ? 'slide up'
1372
+ : 'slide down'
1373
+ : settings.transition
1374
+ ;
1375
+ },
1376
+ userValues: function() {
1377
+ var
1378
+ values = module.get.values()
1379
+ ;
1380
+ if(!values) {
1381
+ return false;
1382
+ }
1383
+ values = $.isArray(values)
1384
+ ? values
1385
+ : [values]
1386
+ ;
1387
+ return $.grep(values, function(value) {
1388
+ return (module.get.item(value) === false);
1389
+ });
1390
+ },
1391
+ uniqueArray: function(array) {
1392
+ return $.grep(array, function (value, index) {
1393
+ return $.inArray(value, array) === index;
1394
+ });
1395
+ },
1396
+ caretPosition: function() {
1397
+ var
1398
+ input = $search.get(0),
1399
+ range,
1400
+ rangeLength
1401
+ ;
1402
+ if('selectionStart' in input) {
1403
+ return input.selectionStart;
1404
+ }
1405
+ else if (document.selection) {
1406
+ input.focus();
1407
+ range = document.selection.createRange();
1408
+ rangeLength = range.text.length;
1409
+ range.moveStart('character', -input.value.length);
1410
+ return range.text.length - rangeLength;
1411
+ }
1412
+ },
1413
+ shortcutKeys: function() {
1414
+ return {
1415
+ backspace : 8,
1416
+ delimiter : 188, // comma
1417
+ deleteKey : 46,
1418
+ enter : 13,
1419
+ escape : 27,
1420
+ pageUp : 33,
1421
+ pageDown : 34,
1422
+ leftArrow : 37,
1423
+ upArrow : 38,
1424
+ rightArrow : 39,
1425
+ downArrow : 40
1426
+ };
1427
+ },
854
1428
  value: function() {
855
1429
  return ($input.length > 0)
856
1430
  ? $input.val()
857
1431
  : $module.data(metadata.value)
858
1432
  ;
859
1433
  },
1434
+ values: function() {
1435
+ var
1436
+ value = module.get.value()
1437
+ ;
1438
+ if(value === '') {
1439
+ return '';
1440
+ }
1441
+ return (!$input.is('select') && module.is.multiple())
1442
+ ? typeof value == 'string'
1443
+ ? value.split(settings.delimiter)
1444
+ : ''
1445
+ : value
1446
+ ;
1447
+ },
1448
+ remoteValues: function() {
1449
+ var
1450
+ values = module.get.values(),
1451
+ remoteValues = false
1452
+ ;
1453
+ if(values) {
1454
+ if(typeof values == 'string') {
1455
+ values = [values];
1456
+ }
1457
+ remoteValues = {};
1458
+ $.each(values, function(index, value) {
1459
+ var
1460
+ name = module.read.remoteData(value)
1461
+ ;
1462
+ module.verbose('Restoring value from session data', name, value);
1463
+ remoteValues[value] = (name)
1464
+ ? name
1465
+ : value
1466
+ ;
1467
+ });
1468
+ }
1469
+ return remoteValues;
1470
+ },
860
1471
  choiceText: function($choice, preserveHTML) {
861
1472
  preserveHTML = (preserveHTML !== undefined)
862
1473
  ? preserveHTML
863
1474
  : settings.preserveHTML
864
1475
  ;
865
- if($choice !== undefined) {
1476
+ if($choice) {
866
1477
  if($choice.find(selector.menu).length > 0) {
867
1478
  module.verbose('Retreiving text of element with sub-menu');
868
1479
  $choice = $choice.clone();
@@ -879,11 +1490,14 @@ $.fn.dropdown = function(parameters) {
879
1490
  },
880
1491
  choiceValue: function($choice, choiceText) {
881
1492
  choiceText = choiceText || module.get.choiceText($choice);
1493
+ if(!$choice) {
1494
+ return false;
1495
+ }
882
1496
  return ($choice.data(metadata.value) !== undefined)
883
1497
  ? $choice.data(metadata.value)
884
1498
  : (typeof choiceText === 'string')
885
1499
  ? choiceText.toLowerCase().trim()
886
- : choiceText.trim()
1500
+ : choiceText
887
1501
  ;
888
1502
  },
889
1503
  inputEvent: function() {
@@ -904,39 +1518,41 @@ $.fn.dropdown = function(parameters) {
904
1518
  var
905
1519
  select = {}
906
1520
  ;
907
- select.values = (settings.sortSelect)
908
- ? {} // properties will be sorted in object when re-accessed
909
- : [] // properties will keep original order in array
910
- ;
1521
+ select.values = [];
911
1522
  $module
912
1523
  .find('option')
913
1524
  .each(function() {
914
1525
  var
915
- name = $(this).html(),
916
- value = ( $(this).attr('value') !== undefined )
917
- ? $(this).attr('value')
1526
+ $option = $(this),
1527
+ name = $option.html(),
1528
+ disabled = $option.attr('disabled'),
1529
+ value = ( $option.attr('value') !== undefined )
1530
+ ? $option.attr('value')
918
1531
  : name
919
1532
  ;
920
- if(value === '') {
1533
+ if(settings.placeholder === 'auto' && value === '') {
921
1534
  select.placeholder = name;
922
1535
  }
923
1536
  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
- }
1537
+ select.values.push({
1538
+ name : name,
1539
+ value : value,
1540
+ disabled : disabled
1541
+ });
936
1542
  }
937
1543
  })
938
1544
  ;
1545
+ if(settings.placeholder && settings.placeholder !== 'auto') {
1546
+ module.debug('Setting placeholder value to', settings.placeholder);
1547
+ select.placeholder = settings.placeholder;
1548
+ }
939
1549
  if(settings.sortSelect) {
1550
+ select.values.sort(function(a, b) {
1551
+ return (a.name > b.name)
1552
+ ? 1
1553
+ : -1
1554
+ ;
1555
+ });
940
1556
  module.debug('Retrieved and sorted values from select', select);
941
1557
  }
942
1558
  else {
@@ -947,21 +1563,51 @@ $.fn.dropdown = function(parameters) {
947
1563
  activeItem: function() {
948
1564
  return $item.filter('.' + className.active);
949
1565
  },
1566
+ selectedItem: function() {
1567
+ var
1568
+ $selectedItem = $item.not(selector.unselectable).filter('.' + className.selected)
1569
+ ;
1570
+ return ($selectedItem.length > 0)
1571
+ ? $selectedItem
1572
+ : $item.eq(0)
1573
+ ;
1574
+ },
1575
+ itemWithAdditions: function(value) {
1576
+ var
1577
+ $items = module.get.item(value),
1578
+ $userItems = module.create.userChoice(value),
1579
+ hasUserItems = ($userItems && $userItems.length > 0)
1580
+ ;
1581
+ if(hasUserItems) {
1582
+ $items = ($items.length > 0)
1583
+ ? $items.add($userItems)
1584
+ : $userItems
1585
+ ;
1586
+ }
1587
+ return $items;
1588
+ },
950
1589
  item: function(value, strict) {
951
1590
  var
952
- $selectedItem = false
1591
+ $selectedItem = false,
1592
+ shouldSearch,
1593
+ isMultiple
953
1594
  ;
954
1595
  value = (value !== undefined)
955
1596
  ? value
956
- : ( module.get.value() !== undefined)
957
- ? module.get.value()
1597
+ : ( module.get.values() !== undefined)
1598
+ ? module.get.values()
958
1599
  : module.get.text()
959
1600
  ;
960
- strict = (value === '' || value === 0)
1601
+ shouldSearch = (isMultiple)
1602
+ ? (value.length > 0)
1603
+ : (value !== undefined && value !== '' && value !== null)
1604
+ ;
1605
+ isMultiple = (module.is.multiple() && $.isArray(value));
1606
+ strict = (value === '' || value === 0)
961
1607
  ? true
962
1608
  : strict || false
963
1609
  ;
964
- if(value !== undefined) {
1610
+ if(shouldSearch) {
965
1611
  $item
966
1612
  .each(function() {
967
1613
  var
@@ -969,36 +1615,63 @@ $.fn.dropdown = function(parameters) {
969
1615
  optionText = module.get.choiceText($choice),
970
1616
  optionValue = module.get.choiceValue($choice, optionText)
971
1617
  ;
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;
1618
+ // safe early exit
1619
+ if(optionValue === null || optionValue === undefined) {
1620
+ return;
1621
+ }
1622
+ if(isMultiple) {
1623
+ if($.inArray(optionValue.toString(), value) !== -1 || $.inArray(optionText, value) !== -1) {
1624
+ $selectedItem = ($selectedItem)
1625
+ ? $selectedItem.add($choice)
1626
+ : $choice
1627
+ ;
977
1628
  }
978
- else if( !$selectedItem && optionText === value ) {
979
- $selectedItem = $(this);
1629
+ }
1630
+ else if(strict) {
1631
+ module.verbose('Ambiguous dropdown value using strict type check', $choice, value);
1632
+ if( optionValue === value || optionText === value) {
1633
+ $selectedItem = $choice;
980
1634
  return true;
981
1635
  }
982
1636
  }
983
1637
  else {
984
- if( optionValue == value ) {
1638
+ if( optionValue.toString() == value.toString() || optionText == value) {
985
1639
  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);
1640
+ $selectedItem = $choice;
992
1641
  return true;
993
1642
  }
994
1643
  }
995
1644
  })
996
1645
  ;
997
1646
  }
998
- else {
999
- value = module.get.text();
1647
+ return $selectedItem;
1648
+ }
1649
+ },
1650
+
1651
+ check: {
1652
+ maxSelections: function(selectionCount) {
1653
+ if(settings.maxSelections) {
1654
+ selectionCount = (selectionCount !== undefined)
1655
+ ? selectionCount
1656
+ : module.get.selectionCount()
1657
+ ;
1658
+ if(selectionCount >= settings.maxSelections) {
1659
+ module.debug('Maximum selection count reached');
1660
+ $item.addClass(className.filtered);
1661
+ module.add.message(message.maxSelections);
1662
+ return true;
1663
+ }
1664
+ else {
1665
+ module.verbose('No longer at maximum selection count');
1666
+ module.remove.message();
1667
+ module.remove.filteredItem();
1668
+ if(module.is.searchSelection()) {
1669
+ module.filterItems();
1670
+ }
1671
+ return false;
1672
+ }
1000
1673
  }
1001
- return $selectedItem || false;
1674
+ return true;
1002
1675
  }
1003
1676
  },
1004
1677
 
@@ -1021,14 +1694,87 @@ $.fn.dropdown = function(parameters) {
1021
1694
  ;
1022
1695
  if(defaultValue !== undefined) {
1023
1696
  module.debug('Restoring default value', defaultValue);
1024
- if(defaultValue.length) {
1025
- module.set.selected(defaultValue);
1697
+ if(defaultValue !== '') {
1698
+ module.set.value(defaultValue);
1699
+ module.set.selected();
1026
1700
  }
1027
1701
  else {
1028
1702
  module.remove.activeItem();
1029
1703
  module.remove.selectedItem();
1030
1704
  }
1031
1705
  }
1706
+ },
1707
+ labels: function() {
1708
+ if(settings.allowAdditions) {
1709
+ if(!settings.useLabels) {
1710
+ module.error(error.labels);
1711
+ settings.useLabels = true;
1712
+ }
1713
+ module.debug('Restoring selected values');
1714
+ module.create.userLabels();
1715
+ }
1716
+ module.check.maxSelections();
1717
+ },
1718
+ selected: function() {
1719
+ module.restore.values();
1720
+ if(module.is.multiple()) {
1721
+ module.debug('Restoring previously selected values and labels');
1722
+ module.restore.labels();
1723
+ }
1724
+ else {
1725
+ module.debug('Restoring previously selected values');
1726
+ }
1727
+ },
1728
+ values: function() {
1729
+ // prevents callbacks from occuring on initial load
1730
+ module.set.initialLoad();
1731
+ if(settings.apiSettings) {
1732
+ if(settings.saveRemoteData) {
1733
+ module.restore.remoteValues();
1734
+ }
1735
+ else {
1736
+ module.clearValue();
1737
+ }
1738
+ }
1739
+ else {
1740
+ module.set.selected();
1741
+ }
1742
+ module.remove.initialLoad();
1743
+ },
1744
+ remoteValues: function() {
1745
+ var
1746
+ values = module.get.remoteValues()
1747
+ ;
1748
+ module.debug('Recreating selected from session data', values);
1749
+ if(values) {
1750
+ if( module.is.single() ) {
1751
+ $.each(values, function(value, name) {
1752
+ module.set.text(name);
1753
+ });
1754
+ }
1755
+ else {
1756
+ $.each(values, function(value, name) {
1757
+ module.add.label(value, name);
1758
+ });
1759
+ }
1760
+ }
1761
+ }
1762
+ },
1763
+
1764
+ read: {
1765
+ remoteData: function(value) {
1766
+ var
1767
+ name
1768
+ ;
1769
+ if(window.Storage === undefined) {
1770
+ module.error(error.noStorage);
1771
+ return;
1772
+ }
1773
+ name = sessionStorage.getItem(value);
1774
+ return (name !== undefined)
1775
+ ? name
1776
+ : false
1777
+ ;
1032
1778
  }
1033
1779
  },
1034
1780
 
@@ -1039,68 +1785,168 @@ $.fn.dropdown = function(parameters) {
1039
1785
  module.save.defaultValue();
1040
1786
  },
1041
1787
  defaultValue: function() {
1042
- $module.data(metadata.defaultValue, module.get.value() );
1788
+ var
1789
+ value = module.get.value()
1790
+ ;
1791
+ module.verbose('Saving default value as', value);
1792
+ $module.data(metadata.defaultValue, value);
1043
1793
  },
1044
1794
  defaultText: function() {
1045
- $module.data(metadata.defaultText, $text.text() );
1795
+ var
1796
+ text = module.get.text()
1797
+ ;
1798
+ module.verbose('Saving default text as', text);
1799
+ $module.data(metadata.defaultText, text);
1046
1800
  },
1047
1801
  placeholderText: function() {
1802
+ var
1803
+ text
1804
+ ;
1048
1805
  if($text.hasClass(className.placeholder)) {
1049
- $module.data(metadata.placeholderText, $text.text());
1806
+ text = module.get.text();
1807
+ module.verbose('Saving placeholder text as', text);
1808
+ $module.data(metadata.placeholderText, text);
1050
1809
  }
1810
+ },
1811
+ remoteData: function(name, value) {
1812
+ if(window.Storage === undefined) {
1813
+ module.error(error.noStorage);
1814
+ return;
1815
+ }
1816
+ module.verbose('Saving remote data to session storage', value, name);
1817
+ sessionStorage.setItem(value, name);
1051
1818
  }
1052
1819
  },
1053
1820
 
1054
1821
  clear: function() {
1822
+ if(module.is.multiple()) {
1823
+ module.remove.labels();
1824
+ }
1825
+ else {
1826
+ module.remove.activeItem();
1827
+ module.remove.selectedItem();
1828
+ }
1829
+ module.set.placeholderText();
1830
+ module.clearValue();
1831
+ },
1832
+
1833
+ clearValue: function() {
1834
+ module.set.value('');
1835
+ },
1836
+
1837
+ scrollPage: function(direction, $selectedItem) {
1055
1838
  var
1056
- placeholderText = $module.data(metadata.placeholderText)
1839
+ $selectedItem = $selectedItem || module.get.selectedItem(),
1840
+ $menu = $selectedItem.closest(selector.menu),
1841
+ menuHeight = $menu.outerHeight(),
1842
+ currentScroll = $menu.scrollTop(),
1843
+ itemHeight = $item.eq(0).outerHeight(),
1844
+ itemsPerPage = Math.floor(menuHeight / itemHeight),
1845
+ maxScroll = $menu.prop('scrollHeight'),
1846
+ newScroll = (direction == 'up')
1847
+ ? currentScroll - (itemHeight * itemsPerPage)
1848
+ : currentScroll + (itemHeight * itemsPerPage),
1849
+ $selectableItem = $item.not(selector.unselectable),
1850
+ isWithinRange,
1851
+ $nextSelectedItem,
1852
+ elementIndex
1057
1853
  ;
1058
- module.set.text(placeholderText);
1059
- module.set.value('');
1060
- module.remove.activeItem();
1061
- module.remove.selectedItem();
1062
- $text.addClass(className.placeholder);
1854
+ elementIndex = (direction == 'up')
1855
+ ? $selectableItem.index($selectedItem) - itemsPerPage
1856
+ : $selectableItem.index($selectedItem) + itemsPerPage
1857
+ ;
1858
+ isWithinRange = (direction == 'up')
1859
+ ? (elementIndex >= 0)
1860
+ : (elementIndex < $selectableItem.length)
1861
+ ;
1862
+ $nextSelectedItem = (isWithinRange)
1863
+ ? $selectableItem.eq(elementIndex)
1864
+ : (direction == 'up')
1865
+ ? $selectableItem.first()
1866
+ : $selectableItem.last()
1867
+ ;
1868
+ if($nextSelectedItem.length > 0) {
1869
+ module.debug('Scrolling page', direction, $nextSelectedItem);
1870
+ $selectedItem
1871
+ .removeClass(className.selected)
1872
+ ;
1873
+ $nextSelectedItem
1874
+ .addClass(className.selected)
1875
+ ;
1876
+ $menu
1877
+ .scrollTop(newScroll)
1878
+ ;
1879
+ }
1063
1880
  },
1064
1881
 
1065
1882
  set: {
1066
1883
  filtered: function() {
1067
1884
  var
1068
- searchValue = $search.val(),
1069
- hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0)
1885
+ isMultiple = module.is.multiple(),
1886
+ isSearch = module.is.searchSelection(),
1887
+ isSearchMultiple = (isMultiple && isSearch),
1888
+ searchValue = (isSearch)
1889
+ ? module.get.query()
1890
+ : '',
1891
+ hasSearchValue = (typeof searchValue === 'string' && searchValue.length > 0),
1892
+ searchWidth = module.get.searchWidth(searchValue.length),
1893
+ valueIsSet = searchValue !== ''
1070
1894
  ;
1071
- if(hasSearchValue) {
1895
+ if(isMultiple && hasSearchValue) {
1896
+ module.verbose('Adjusting input width', searchWidth, settings.glyphWidth);
1897
+ $search.css('width', searchWidth);
1898
+ }
1899
+ if(hasSearchValue || (isSearchMultiple && valueIsSet)) {
1900
+ module.verbose('Hiding placeholder text');
1072
1901
  $text.addClass(className.filtered);
1073
1902
  }
1074
- else {
1903
+ else if(!isMultiple || (isSearchMultiple && !valueIsSet)) {
1904
+ module.verbose('Showing placeholder text');
1075
1905
  $text.removeClass(className.filtered);
1076
1906
  }
1077
1907
  },
1908
+ loading: function() {
1909
+ $module.addClass(className.loading);
1910
+ },
1911
+ placeholderText: function(text) {
1912
+ text = text || $module.data(metadata.placeholderText);
1913
+ if(text) {
1914
+ module.debug('Restoring placeholder text');
1915
+ module.set.text(text);
1916
+ $text.addClass(className.placeholder);
1917
+ }
1918
+ },
1078
1919
  tabbable: function() {
1079
- if( module.is.searchable() ) {
1080
- module.debug('Searchable dropdown initialized');
1920
+ if( module.has.search() ) {
1921
+ module.debug('Added tabindex to searchable dropdown');
1081
1922
  $search
1082
1923
  .val('')
1083
1924
  .attr('tabindex', 0)
1084
1925
  ;
1085
1926
  $menu
1086
- .attr('tabindex', '-1')
1927
+ .attr('tabindex', -1)
1087
1928
  ;
1088
1929
  }
1089
1930
  else {
1090
- module.debug('Simple selection dropdown initialized');
1931
+ module.debug('Added tabindex to dropdown');
1091
1932
  if(!$module.attr('tabindex') ) {
1092
1933
  $module
1093
1934
  .attr('tabindex', 0)
1094
1935
  ;
1095
1936
  $menu
1096
- .attr('tabindex', '-1')
1937
+ .attr('tabindex', -1)
1097
1938
  ;
1098
1939
  }
1099
1940
  }
1100
1941
  },
1942
+ initialLoad: function() {
1943
+ module.verbose('Setting initial load');
1944
+ initialLoad = true;
1945
+ },
1101
1946
  scrollPosition: function($item, forceScroll) {
1102
1947
  var
1103
1948
  edgeTolerance = 5,
1949
+ $menu,
1104
1950
  hasActive,
1105
1951
  offset,
1106
1952
  itemHeight,
@@ -1112,100 +1958,399 @@ $.fn.dropdown = function(parameters) {
1112
1958
  belowPage
1113
1959
  ;
1114
1960
 
1115
- $item = $item || module.get.activeItem();
1961
+ $item = $item || module.get.selectedItem();
1962
+ $menu = $item.closest(selector.menu);
1116
1963
  hasActive = ($item && $item.length > 0);
1117
1964
  forceScroll = (forceScroll !== undefined)
1118
1965
  ? forceScroll
1119
1966
  : false
1120
1967
  ;
1968
+ if($item && $menu.length > 0 && hasActive) {
1969
+ itemOffset = $item.position().top;
1121
1970
 
1122
- if($item && hasActive) {
1123
-
1124
- if(!$menu.hasClass(className.visible)) {
1125
- $menu.addClass(className.loading);
1971
+ $menu.addClass(className.loading);
1972
+ menuScroll = $menu.scrollTop();
1973
+ menuOffset = $menu.offset().top;
1974
+ itemOffset = $item.offset().top;
1975
+ offset = menuScroll - menuOffset + itemOffset;
1976
+ if(!forceScroll) {
1977
+ menuHeight = $menu.height();
1978
+ belowPage = menuScroll + menuHeight < (offset + edgeTolerance);
1979
+ abovePage = ((offset - edgeTolerance) < menuScroll);
1980
+ }
1981
+ module.debug('Scrolling to active item', offset);
1982
+ if(forceScroll || abovePage || belowPage) {
1983
+ $menu.scrollTop(offset);
1126
1984
  }
1985
+ $menu.removeClass(className.loading);
1986
+ }
1987
+ },
1988
+ text: function(text) {
1989
+ if(settings.action !== 'select') {
1990
+ if(settings.action == 'combo') {
1991
+ module.debug('Changing combo button text', text, $combo);
1992
+ if(settings.preserveHTML) {
1993
+ $combo.html(text);
1994
+ }
1995
+ else {
1996
+ $combo.text(text);
1997
+ }
1998
+ }
1999
+ else {
2000
+ module.debug('Changing text', text, $text);
2001
+ $text
2002
+ .removeClass(className.filtered)
2003
+ .removeClass(className.placeholder)
2004
+ ;
2005
+ if(settings.preserveHTML) {
2006
+ $text.html(text);
2007
+ }
2008
+ else {
2009
+ $text.text(text);
2010
+ }
2011
+ }
2012
+ }
2013
+ },
2014
+ selectedLetter: function(letter) {
2015
+ var
2016
+ $selectedItem = $item.filter('.' + className.selected),
2017
+ $nextValue = false
2018
+ ;
2019
+ $item
2020
+ .each(function(){
2021
+ var
2022
+ $choice = $(this),
2023
+ text = module.get.choiceText($choice, false),
2024
+ firstLetter = String(text).charAt(0).toLowerCase(),
2025
+ matchedLetter = letter.toLowerCase()
2026
+ ;
2027
+ if(firstLetter == matchedLetter) {
2028
+ $nextValue = $choice;
2029
+ return false;
2030
+ }
2031
+ })
2032
+ ;
2033
+ if($nextValue) {
2034
+ module.verbose('Scrolling to next value with letter', letter);
2035
+ module.set.scrollPosition($nextValue);
2036
+ $selectedItem.removeClass(className.selected);
2037
+ $nextValue.addClass(className.selected);
2038
+ }
2039
+ },
2040
+ direction: function($menu) {
2041
+ if(settings.direction == 'auto') {
2042
+ if(module.is.onScreen($menu)) {
2043
+ module.remove.upward($menu);
2044
+ }
2045
+ else {
2046
+ module.set.upward($menu);
2047
+ }
2048
+ }
2049
+ else if(settings.direction == 'upward') {
2050
+ module.set.upward($menu);
2051
+ }
2052
+ },
2053
+ upward: function($menu) {
2054
+ var $element = $menu || $module;
2055
+ $element.addClass(className.upward);
2056
+ },
2057
+ value: function(value, text, $selected) {
2058
+ var
2059
+ hasInput = ($input.length > 0),
2060
+ isAddition = !module.has.value(value),
2061
+ currentValue = module.get.values(),
2062
+ stringValue = (typeof value == 'number')
2063
+ ? value.toString()
2064
+ : value,
2065
+ newValue
2066
+ ;
2067
+ if(hasInput) {
2068
+ if(stringValue == currentValue) {
2069
+ module.verbose('Skipping value update already same value', value, currentValue);
2070
+ if(!module.is.initialLoad()) {
2071
+ return;
2072
+ }
2073
+ }
2074
+ module.debug('Updating input value', value, currentValue);
2075
+ $input
2076
+ .val(value)
2077
+ .trigger('change')
2078
+ ;
2079
+ }
2080
+ else {
2081
+ module.verbose('Storing value in metadata', value, $input);
2082
+ if(value !== currentValue) {
2083
+ $module.data(metadata.value, value);
2084
+ }
2085
+ }
2086
+ if(settings.fireOnInit === false && module.is.initialLoad()) {
2087
+ module.verbose('No callback on initial load', settings.onChange);
2088
+ }
2089
+ else {
2090
+ settings.onChange.call(element, value, text, $selected);
2091
+ }
2092
+ },
2093
+ active: function() {
2094
+ $module
2095
+ .addClass(className.active)
2096
+ ;
2097
+ },
2098
+ multiple: function() {
2099
+ $module.addClass(className.multiple);
2100
+ },
2101
+ visible: function() {
2102
+ $module.addClass(className.visible);
2103
+ },
2104
+ selected: function(value, $selectedItem) {
2105
+ var
2106
+ isMultiple = module.is.multiple(),
2107
+ $userSelectedItem
2108
+ ;
2109
+ $selectedItem = (settings.allowAdditions)
2110
+ ? $selectedItem || module.get.itemWithAdditions(value)
2111
+ : $selectedItem || module.get.item(value)
2112
+ ;
2113
+ if(!$selectedItem) {
2114
+ return;
2115
+ }
2116
+ module.debug('Setting selected menu item to', $selectedItem);
2117
+ if(module.is.single()) {
2118
+ module.remove.activeItem();
2119
+ module.remove.selectedItem();
2120
+ }
2121
+ else if(settings.useLabels) {
2122
+ module.remove.selectedItem();
2123
+ }
2124
+ // select each item
2125
+ $selectedItem
2126
+ .each(function() {
2127
+ var
2128
+ $selected = $(this),
2129
+ selectedText = module.get.choiceText($selected),
2130
+ selectedValue = module.get.choiceValue($selected, selectedText),
2131
+
2132
+ isFiltered = $selected.hasClass(className.filtered),
2133
+ isActive = $selected.hasClass(className.active),
2134
+ isUserValue = $selected.hasClass(className.addition),
2135
+ shouldAnimate = (isMultiple && $selectedItem.length == 1)
2136
+ ;
2137
+ if(isMultiple) {
2138
+ if(!isActive || isUserValue) {
2139
+ if(settings.apiSettings && settings.saveRemoteData) {
2140
+ module.save.remoteData(selectedText, selectedValue);
2141
+ }
2142
+ if(settings.useLabels) {
2143
+ module.add.value(selectedValue, selectedText, $selected);
2144
+ module.add.label(selectedValue, selectedText, shouldAnimate);
2145
+ $selected.addClass(className.active);
2146
+ module.filterActive();
2147
+ module.select.nextAvailable($selectedItem);
2148
+ }
2149
+ else {
2150
+ module.add.value(selectedValue, selectedText, $selected);
2151
+ module.set.text(module.add.variables(message.count));
2152
+ $selected.addClass(className.active);
2153
+ }
2154
+ }
2155
+ else if(!isFiltered) {
2156
+ module.debug('Selected active value, removing label');
2157
+ module.remove.selected(selectedValue);
2158
+ }
2159
+ }
2160
+ else {
2161
+ if(settings.apiSettings && settings.saveRemoteData) {
2162
+ module.save.remoteData(selectedText, selectedValue);
2163
+ }
2164
+ module.set.value(selectedValue, selectedText, $selected);
2165
+ module.set.text(selectedText);
2166
+ $selected
2167
+ .addClass(className.active)
2168
+ .addClass(className.selected)
2169
+ ;
2170
+ }
2171
+ })
2172
+ ;
2173
+ }
2174
+ },
2175
+
2176
+ add: {
2177
+ label: function(value, text, shouldAnimate) {
2178
+ var
2179
+ $next = module.is.searchSelection()
2180
+ ? $search
2181
+ : $text,
2182
+ $label
2183
+ ;
2184
+ $label = $('<a />')
2185
+ .addClass(className.label)
2186
+ .attr('data-value', value)
2187
+ .html(templates.label(value, text))
2188
+ ;
2189
+ $label = settings.onLabelCreate.call($label, value, text);
1127
2190
 
1128
- menuHeight = $menu.height();
1129
- itemHeight = $item.height();
1130
- menuScroll = $menu.scrollTop();
1131
- menuOffset = $menu.offset().top;
1132
- itemOffset = $item.offset().top;
1133
- offset = menuScroll - menuOffset + itemOffset;
1134
- belowPage = menuScroll + menuHeight < (offset + edgeTolerance);
1135
- abovePage = ((offset - edgeTolerance) < menuScroll);
1136
- module.debug('Scrolling to active item', offset);
1137
- if(abovePage || belowPage || forceScroll) {
1138
- $menu
1139
- .scrollTop(offset)
1140
- .removeClass(className.loading)
1141
- ;
1142
- }
2191
+ if(module.has.label(value)) {
2192
+ module.debug('Label already exists, skipping', value);
2193
+ return;
1143
2194
  }
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
- }
2195
+ if(settings.label.variation) {
2196
+ $label.addClass(settings.label.variation);
1154
2197
  }
1155
- else if(settings.action !== 'select') {
1156
- module.debug('Changing text', text, $text);
1157
- $text
1158
- .removeClass(className.filtered)
1159
- .removeClass(className.placeholder)
2198
+ if(shouldAnimate === true) {
2199
+ module.debug('Animating in label', $label);
2200
+ $label
2201
+ .addClass(className.hidden)
2202
+ .insertBefore($next)
2203
+ .transition(settings.label.transition, settings.label.duration)
2204
+ ;
2205
+ }
2206
+ else {
2207
+ module.debug('Adding selection label', $label);
2208
+ $label
2209
+ .insertBefore($next)
1160
2210
  ;
1161
- if(settings.preserveHTML) {
1162
- $text.html(text);
1163
- }
1164
- else {
1165
- $text.text(text);
1166
- }
1167
2211
  }
1168
2212
  },
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')
2213
+ message: function(message) {
2214
+ var
2215
+ $message = $menu.children(selector.message),
2216
+ html = settings.templates.message(module.add.variables(message))
2217
+ ;
2218
+ if($message.length > 0) {
2219
+ $message
2220
+ .html(html)
1175
2221
  ;
1176
2222
  }
1177
2223
  else {
1178
- $module.data(metadata.value, value);
2224
+ $message = $('<div/>')
2225
+ .html(html)
2226
+ .addClass(className.message)
2227
+ .appendTo($menu)
2228
+ ;
1179
2229
  }
1180
2230
  },
1181
- active: function() {
1182
- $module
1183
- .addClass(className.active)
2231
+ optionValue: function(value) {
2232
+ var
2233
+ $option = $input.find('option[value="' + value + '"]'),
2234
+ hasOption = ($option.length > 0)
1184
2235
  ;
2236
+ if(hasOption) {
2237
+ return;
2238
+ }
2239
+ // temporarily disconnect observer
2240
+ if(selectObserver) {
2241
+ selectObserver.disconnect();
2242
+ module.verbose('Temporarily disconnecting mutation observer', value);
2243
+ }
2244
+ $('<option/>')
2245
+ .prop('value', value)
2246
+ .html(value)
2247
+ .appendTo($input)
2248
+ ;
2249
+ module.verbose('Adding user addition as an <option>', value);
2250
+ if(selectObserver) {
2251
+ selectObserver.observe($input[0], {
2252
+ childList : true,
2253
+ subtree : true
2254
+ });
2255
+ }
1185
2256
  },
1186
- visible: function() {
1187
- $module.addClass(className.visible);
1188
- },
1189
- selected: function(value) {
2257
+ userSuggestion: function(value) {
1190
2258
  var
1191
- $selectedItem = module.get.item(value),
1192
- selectedText,
1193
- selectedValue
2259
+ $addition = $menu.children(selector.addition),
2260
+ alreadyHasValue = module.get.item(value),
2261
+ hasUserSuggestion = $addition.length > 0,
2262
+ html
1194
2263
  ;
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)
2264
+ if(module.has.maxSelections()) {
2265
+ return;
2266
+ }
2267
+ if(value === '' || alreadyHasValue) {
2268
+ $addition.remove();
2269
+ return;
2270
+ }
2271
+ $item
2272
+ .removeClass(className.selected)
2273
+ ;
2274
+ if(hasUserSuggestion) {
2275
+ html = settings.templates.addition(value);
2276
+ $addition
2277
+ .html(html)
2278
+ .data(metadata.value, value)
2279
+ .removeClass(className.filtered)
2280
+ .addClass(className.selected)
2281
+ ;
2282
+ module.verbose('Replacing user suggestion with new value', $addition);
2283
+ }
2284
+ else {
2285
+ $addition = module.create.userChoice(value);
2286
+ $addition
2287
+ .prependTo($menu)
1201
2288
  .addClass(className.selected)
1202
2289
  ;
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);
2290
+ module.verbose('Adding item choice to menu corresponding with user choice addition', $addition);
2291
+ }
2292
+ },
2293
+ variables: function(message) {
2294
+ var
2295
+ hasCount = (message.search('{count}') !== -1),
2296
+ hasMaxCount = (message.search('{maxCount}') !== -1),
2297
+ hasTerm = (message.search('{term}') !== -1),
2298
+ values,
2299
+ count,
2300
+ query
2301
+ ;
2302
+ module.verbose('Adding templated variables to message', message);
2303
+ if(hasCount) {
2304
+ count = module.get.selectionCount();
2305
+ message = message.replace('{count}', count);
2306
+ }
2307
+ if(hasMaxCount) {
2308
+ count = module.get.selectionCount();
2309
+ message = message.replace('{maxCount}', settings.maxSelections);
2310
+ }
2311
+ if(hasTerm) {
2312
+ query = module.get.query();
2313
+ message = message.replace('{term}', query);
2314
+ }
2315
+ return message;
2316
+ },
2317
+ value: function(addedValue, addedText, $selectedItem) {
2318
+ var
2319
+ currentValue = module.get.values(),
2320
+ newValue
2321
+ ;
2322
+ if(addedValue === '') {
2323
+ module.debug('Cannot select blank values from multiselect');
2324
+ return;
2325
+ }
2326
+ // extend currently array
2327
+ if($.isArray(currentValue)) {
2328
+ newValue = currentValue.concat([addedValue]);
2329
+ newValue = module.get.uniqueArray(newValue);
2330
+ }
2331
+ else {
2332
+ newValue = [addedValue];
2333
+ }
2334
+ // add values
2335
+ if( $input.is('select')) {
2336
+ if(settings.allowAdditions) {
2337
+ module.add.optionValue(addedValue);
2338
+ module.debug('Adding value to select', addedValue, newValue, $input);
2339
+ }
2340
+ }
2341
+ else {
2342
+ newValue = newValue.join(settings.delimiter);
2343
+ module.debug('Setting hidden input to delimited value', newValue, $input);
2344
+ }
2345
+
2346
+ if(settings.fireOnInit === false && module.is.initialLoad()) {
2347
+ module.verbose('No callback on initial load', settings.onAdd);
2348
+ }
2349
+ else {
2350
+ settings.onAdd.call(element, addedValue, addedText, $selectedItem);
1208
2351
  }
2352
+ module.set.value(newValue, addedValue, addedText, $selectedItem);
2353
+ module.check.maxSelections();
1209
2354
  }
1210
2355
  },
1211
2356
 
@@ -1213,6 +2358,19 @@ $.fn.dropdown = function(parameters) {
1213
2358
  active: function() {
1214
2359
  $module.removeClass(className.active);
1215
2360
  },
2361
+ activeLabel: function() {
2362
+ $module.find(selector.label).removeClass(className.active);
2363
+ },
2364
+ loading: function() {
2365
+ $module.removeClass(className.loading);
2366
+ },
2367
+ initialLoad: function() {
2368
+ initialLoad = false;
2369
+ },
2370
+ upward: function($menu) {
2371
+ var $element = $menu || $module;
2372
+ $element.removeClass(className.upward);
2373
+ },
1216
2374
  visible: function() {
1217
2375
  $module.removeClass(className.visible);
1218
2376
  },
@@ -1220,16 +2378,146 @@ $.fn.dropdown = function(parameters) {
1220
2378
  $item.removeClass(className.active);
1221
2379
  },
1222
2380
  filteredItem: function() {
1223
- $item.removeClass(className.filtered);
2381
+ if( module.has.maxSelections() ) {
2382
+ return;
2383
+ }
2384
+ if(settings.useLabels) {
2385
+ $item.not('.' + className.active).removeClass(className.filtered);
2386
+ }
2387
+ else {
2388
+ $item.removeClass(className.filtered);
2389
+ }
2390
+ },
2391
+ message: function() {
2392
+ $menu.children(selector.message).remove();
1224
2393
  },
1225
2394
  searchTerm: function() {
2395
+ module.verbose('Cleared search term');
1226
2396
  $search.val('');
2397
+ module.set.filtered();
2398
+ },
2399
+ selected: function(value, $selectedItem) {
2400
+ $selectedItem = (settings.allowAdditions)
2401
+ ? $selectedItem || module.get.itemWithAdditions(value)
2402
+ : $selectedItem || module.get.item(value)
2403
+ ;
2404
+
2405
+ if(!$selectedItem) {
2406
+ return false;
2407
+ }
2408
+
2409
+ $selectedItem
2410
+ .each(function() {
2411
+ var
2412
+ $selected = $(this),
2413
+ selectedText = module.get.choiceText($selected),
2414
+ selectedValue = module.get.choiceValue($selected, selectedText)
2415
+ ;
2416
+ if(module.is.multiple()) {
2417
+ if(settings.useLabels) {
2418
+ module.remove.value(selectedValue, selectedText, $selected);
2419
+ module.remove.label(selectedValue);
2420
+ }
2421
+ else {
2422
+ module.remove.value(selectedValue, selectedText, $selected);
2423
+ module.set.text(module.add.variables(message.count));
2424
+ }
2425
+ }
2426
+ else {
2427
+ module.remove.value(selectedValue, selectedText, $selected);
2428
+ }
2429
+ $selected
2430
+ .removeClass(className.filtered)
2431
+ .removeClass(className.active)
2432
+ ;
2433
+ if(settings.useLabels) {
2434
+ $selected.removeClass(className.selected);
2435
+ }
2436
+ })
2437
+ ;
1227
2438
  },
1228
2439
  selectedItem: function() {
1229
2440
  $item.removeClass(className.selected);
1230
2441
  },
2442
+ value: function(removedValue, removedText, $removedItem) {
2443
+ var
2444
+ values = $input.val(),
2445
+ newValue
2446
+ ;
2447
+ if( $input.is('select') ) {
2448
+ module.verbose('Input is <select> removing selected option', removedValue);
2449
+ newValue = module.remove.arrayValue(removedValue, values);
2450
+ }
2451
+ else {
2452
+ module.verbose('Removing from delimited values', removedValue);
2453
+ values = values.split(settings.delimiter);
2454
+ newValue = module.remove.arrayValue(removedValue, values);
2455
+ newValue = newValue.join(settings.delimiter);
2456
+ }
2457
+ if(settings.fireOnInit === false && module.is.initialLoad()) {
2458
+ module.verbose('No callback on initial load', settings.onRemove);
2459
+ }
2460
+ else {
2461
+ settings.onRemove.call(element, removedValue, removedText, $removedItem);
2462
+ }
2463
+ module.set.value(newValue, removedText, $removedItem);
2464
+ module.check.maxSelections();
2465
+ },
2466
+ arrayValue: function(removedValue, values) {
2467
+ values = $.grep(values, function(value){
2468
+ return (removedValue != value);
2469
+ });
2470
+ module.verbose('Removed value from delimited string', removedValue, values);
2471
+ return values;
2472
+ },
2473
+ label: function(value) {
2474
+ var
2475
+ $labels = $module.find(selector.label),
2476
+ $removedLabel = $labels.filter('[data-value="' + value +'"]'),
2477
+ labelCount = $labels.length,
2478
+ isLastLabel = ($labels.index($removedLabel) + 1 == labelCount),
2479
+ shouldAnimate = ( (!module.is.searchSelection() || !module.is.focusedOnSearch()) && isLastLabel)
2480
+ ;
2481
+ if(shouldAnimate) {
2482
+ module.verbose('Animating and removing label', $removedLabel);
2483
+ $removedLabel
2484
+ .transition(settings.label.transition, settings.label.duration, function() {
2485
+ $removedLabel.remove();
2486
+ })
2487
+ ;
2488
+ }
2489
+ else {
2490
+ module.verbose('Removing label', $removedLabel);
2491
+ $removedLabel.remove();
2492
+ }
2493
+ },
2494
+ activeLabels: function($activeLabels) {
2495
+ $activeLabels = $activeLabels || $module.find(selector.label).filter('.' + className.active);
2496
+ module.verbose('Removing active label selections', $activeLabels);
2497
+ module.remove.labels($activeLabels);
2498
+ },
2499
+ labels: function($labels) {
2500
+ $labels = $labels || $module.find(selector.label);
2501
+ module.verbose('Removing labels', $labels);
2502
+ $labels
2503
+ .each(function(){
2504
+ var
2505
+ value = $(this).data('value'),
2506
+ isUserValue = module.is.userValue(value)
2507
+ ;
2508
+ if(isUserValue) {
2509
+ module.remove.value(value);
2510
+ module.remove.label(value);
2511
+ }
2512
+ else {
2513
+ // selected will also remove label
2514
+ module.remove.selected(value);
2515
+ }
2516
+ })
2517
+ ;
2518
+ },
1231
2519
  tabbable: function() {
1232
- if( module.is.searchable() ) {
2520
+ if( module.has.search() ) {
1233
2521
  module.debug('Searchable dropdown initialized');
1234
2522
  $search
1235
2523
  .attr('tabindex', '-1')
@@ -1250,27 +2538,127 @@ $.fn.dropdown = function(parameters) {
1250
2538
  }
1251
2539
  },
1252
2540
 
2541
+ has: {
2542
+ search: function() {
2543
+ return ($search.length > 0);
2544
+ },
2545
+ input: function() {
2546
+ return ($input.length > 0);
2547
+ },
2548
+ menu: function() {
2549
+ return ($menu.length > 0);
2550
+ },
2551
+ message: function() {
2552
+ return ($menu.children(selector.message).length !== 0);
2553
+ },
2554
+ label: function(value) {
2555
+ var
2556
+ $labels = $module.find(selector.label)
2557
+ ;
2558
+ return ($labels.filter('[data-value="' + value +'"]').length > 0);
2559
+ },
2560
+ maxSelections: function() {
2561
+ return (settings.maxSelections && module.get.selectionCount() >= settings.maxSelections);
2562
+ },
2563
+ allResultsFiltered: function() {
2564
+ return ($item.filter(selector.unselectable).length === $item.length);
2565
+ },
2566
+ value: function(value) {
2567
+ var
2568
+ values = module.get.values(),
2569
+ hasValue = $.isArray(values)
2570
+ ? values && ($.inArray(value, values) !== -1)
2571
+ : (values == value)
2572
+ ;
2573
+ return (hasValue)
2574
+ ? true
2575
+ : false
2576
+ ;
2577
+ }
2578
+ },
2579
+
1253
2580
  is: {
1254
2581
  active: function() {
1255
2582
  return $module.hasClass(className.active);
1256
2583
  },
1257
2584
  alreadySetup: function() {
1258
- return ($module.is('select') && $module.parent(selector.dropdown).length > 0);
2585
+ return ($module.is('select') && $module.parent(selector.dropdown).length > 0 && $module.prev().length === 0);
1259
2586
  },
1260
2587
  animating: function($subMenu) {
1261
2588
  return ($subMenu)
1262
- ? $subMenu.is(':animated') || $subMenu.transition && $subMenu.transition('is animating')
1263
- : $menu.is(':animated') || $menu.transition && $menu.transition('is animating')
2589
+ ? $subMenu.transition && $subMenu.transition('is animating')
2590
+ : $menu.transition && $menu.transition('is animating')
1264
2591
  ;
1265
2592
  },
2593
+ focused: function() {
2594
+ return (document.activeElement === $module[0]);
2595
+ },
2596
+ focusedOnSearch: function() {
2597
+ return (document.activeElement === $search[0]);
2598
+ },
1266
2599
  allFiltered: function() {
1267
- return ($item.filter('.' + className.filtered).length === $item.length);
2600
+ return( (module.is.multiple() || module.has.search()) && !module.has.message() && module.has.allResultsFiltered() );
1268
2601
  },
1269
2602
  hidden: function($subMenu) {
1270
- return ($subMenu)
1271
- ? $subMenu.is(':hidden')
1272
- : $menu.is(':hidden')
2603
+ return !module.is.visible($subMenu);
2604
+ },
2605
+ initialLoad: function() {
2606
+ return initialLoad;
2607
+ },
2608
+ onScreen: function($subMenu) {
2609
+ var
2610
+ $currentMenu = $subMenu || $menu,
2611
+ canOpenDownward = true,
2612
+ onScreen = {},
2613
+ calculations
2614
+ ;
2615
+ $currentMenu.addClass(className.loading);
2616
+ calculations = {
2617
+ context: {
2618
+ scrollTop : $context.scrollTop(),
2619
+ height : $context.outerHeight()
2620
+ },
2621
+ menu : {
2622
+ offset: $currentMenu.offset(),
2623
+ height: $currentMenu.outerHeight()
2624
+ }
2625
+ };
2626
+ onScreen = {
2627
+ above : (calculations.context.scrollTop) <= calculations.menu.offset.top - calculations.menu.height,
2628
+ below : (calculations.context.scrollTop + calculations.context.height) >= calculations.menu.offset.top + calculations.menu.height
2629
+ };
2630
+ if(onScreen.below) {
2631
+ module.verbose('Dropdown can fit in context downward', onScreen);
2632
+ canOpenDownward = true;
2633
+ }
2634
+ else if(!onScreen.below && !onScreen.above) {
2635
+ module.verbose('Dropdown cannot fit in either direction, favoring downward', onScreen);
2636
+ canOpenDownward = true;
2637
+ }
2638
+ else {
2639
+ module.verbose('Dropdown cannot fit below, opening upward', onScreen);
2640
+ canOpenDownward = false;
2641
+ }
2642
+ $currentMenu.removeClass(className.loading);
2643
+ return canOpenDownward;
2644
+ },
2645
+ inObject: function(needle, object) {
2646
+ var
2647
+ found = false
1273
2648
  ;
2649
+ $.each(object, function(index, property) {
2650
+ if(property == needle) {
2651
+ found = true;
2652
+ return true;
2653
+ }
2654
+ });
2655
+ return found;
2656
+ },
2657
+ multiple: function() {
2658
+ return $module.hasClass(className.multiple);
2659
+ },
2660
+ single: function() {
2661
+ return !module.is.multiple();
1274
2662
  },
1275
2663
  selectMutation: function(mutations) {
1276
2664
  var
@@ -1287,22 +2675,23 @@ $.fn.dropdown = function(parameters) {
1287
2675
  search: function() {
1288
2676
  return $module.hasClass(className.search);
1289
2677
  },
1290
- searchable: function() {
1291
- return ($search.length > 0);
1292
- },
1293
2678
  searchSelection: function() {
1294
- return ( module.is.searchable() && $search.parent().is($module) );
2679
+ return ( module.has.search() && $search.closest(selector.menu).length === 0 );
1295
2680
  },
1296
2681
  selection: function() {
1297
2682
  return $module.hasClass(className.selection);
1298
2683
  },
1299
- upward: function() {
1300
- return $module.hasClass(className.upward);
2684
+ userValue: function(value) {
2685
+ return ($.inArray(value, module.get.userValues()) !== -1);
2686
+ },
2687
+ upward: function($menu) {
2688
+ var $element = $menu || $module;
2689
+ return $element.hasClass(className.upward);
1301
2690
  },
1302
2691
  visible: function($subMenu) {
1303
2692
  return ($subMenu)
1304
- ? $subMenu.is(':visible')
1305
- : $menu.is(':visible')
2693
+ ? $subMenu.hasClass(className.visible)
2694
+ : $menu.hasClass(className.visible)
1306
2695
  ;
1307
2696
  }
1308
2697
  },
@@ -1312,7 +2701,10 @@ $.fn.dropdown = function(parameters) {
1312
2701
  return (hasTouch || settings.on == 'click');
1313
2702
  },
1314
2703
  show: function() {
1315
- return !$module.hasClass(className.disabled);
2704
+ return !$module.hasClass(className.disabled) && $item.length > 0;
2705
+ },
2706
+ useAPI: function() {
2707
+ return $.fn.api !== undefined;
1316
2708
  }
1317
2709
  },
1318
2710
 
@@ -1326,30 +2718,29 @@ $.fn.dropdown = function(parameters) {
1326
2718
  module.hideSubMenus();
1327
2719
  module.hideOthers();
1328
2720
  module.set.active();
1329
- }
2721
+ },
2722
+ transition
1330
2723
  ;
1331
2724
  callback = $.isFunction(callback)
1332
2725
  ? callback
1333
2726
  : function(){}
1334
2727
  ;
1335
- module.set.scrollPosition(module.get.activeItem(), true);
1336
2728
  module.verbose('Doing menu show animation', $currentMenu);
2729
+ module.set.direction($subMenu);
2730
+ transition = module.get.transition($subMenu);
2731
+ if( module.is.selection() ) {
2732
+ module.set.scrollPosition(module.get.selectedItem(), true);
2733
+ }
1337
2734
  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') {
2735
+ if(transition == 'none') {
2736
+ start();
2737
+ $currentMenu.transition('show');
1347
2738
  callback.call(element);
1348
2739
  }
1349
2740
  else if($.fn.transition !== undefined && $module.transition('is supported')) {
1350
2741
  $currentMenu
1351
2742
  .transition({
1352
- animation : settings.transition + ' in',
2743
+ animation : transition + ' in',
1353
2744
  debug : settings.debug,
1354
2745
  verbose : settings.verbose,
1355
2746
  duration : settings.duration,
@@ -1361,38 +2752,8 @@ $.fn.dropdown = function(parameters) {
1361
2752
  })
1362
2753
  ;
1363
2754
  }
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
2755
  else {
1395
- module.error(error.transition, settings.transition);
2756
+ module.error(error.noTransition, transition);
1396
2757
  }
1397
2758
  }
1398
2759
  },
@@ -1408,9 +2769,9 @@ $.fn.dropdown = function(parameters) {
1408
2769
  if( module.can.click() ) {
1409
2770
  module.unbind.intent();
1410
2771
  }
1411
- module.focusSearch();
1412
2772
  module.remove.active();
1413
- }
2773
+ },
2774
+ transition = module.get.transition($subMenu)
1414
2775
  ;
1415
2776
  callback = $.isFunction(callback)
1416
2777
  ? callback
@@ -1419,63 +2780,29 @@ $.fn.dropdown = function(parameters) {
1419
2780
  if( module.is.visible($currentMenu) || module.is.animating($currentMenu) ) {
1420
2781
  module.verbose('Doing menu hide animation', $currentMenu);
1421
2782
 
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') {
2783
+ if(transition == 'none') {
2784
+ start();
2785
+ $currentMenu.transition('hide');
1432
2786
  callback.call(element);
1433
2787
  }
1434
2788
  else if($.fn.transition !== undefined && $module.transition('is supported')) {
1435
2789
  $currentMenu
1436
2790
  .transition({
1437
- animation : settings.transition + ' out',
2791
+ animation : transition + ' out',
1438
2792
  duration : settings.duration,
1439
2793
  debug : settings.debug,
1440
2794
  verbose : settings.verbose,
1441
2795
  queue : true,
1442
2796
  onStart : start,
1443
2797
  onComplete : function() {
2798
+ if(settings.direction == 'auto') {
2799
+ module.remove.upward($subMenu);
2800
+ }
1444
2801
  callback.call(element);
1445
2802
  }
1446
2803
  })
1447
2804
  ;
1448
2805
  }
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
2806
  else {
1480
2807
  module.error(error.transition);
1481
2808
  }
@@ -1483,6 +2810,18 @@ $.fn.dropdown = function(parameters) {
1483
2810
  }
1484
2811
  },
1485
2812
 
2813
+ hideAndClear: function() {
2814
+ if(module.has.search()) {
2815
+ module.remove.searchTerm();
2816
+ module.hide(function() {
2817
+ module.remove.filteredItem();
2818
+ });
2819
+ }
2820
+ else {
2821
+ module.hide();
2822
+ }
2823
+ },
2824
+
1486
2825
  delay: {
1487
2826
  show: function() {
1488
2827
  module.verbose('Delaying show event to ensure user intent');
@@ -1499,7 +2838,7 @@ $.fn.dropdown = function(parameters) {
1499
2838
  escape: {
1500
2839
  regExp: function(text) {
1501
2840
  text = String(text);
1502
- return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
2841
+ return text.replace(regExp.escape, '\\$&');
1503
2842
  }
1504
2843
  },
1505
2844
 
@@ -1682,75 +3021,137 @@ $.fn.dropdown = function(parameters) {
1682
3021
  $.fn.dropdown.settings = {
1683
3022
 
1684
3023
  debug : false,
1685
- verbose : true,
3024
+ verbose : false,
1686
3025
  performance : true,
1687
3026
 
1688
- on : 'click',
1689
- action : 'activate',
3027
+ on : 'click', // what event should show menu action on item selection
3028
+ action : 'activate', // action on item selection (nothing, activate, select, combo, hide, function(){})
3029
+
3030
+
3031
+ apiSettings : false,
3032
+ saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh
3033
+ throttle : 200, // How long to wait after last user input to search remotely
3034
+
3035
+ context : window, // Context to use when determining if on screen
3036
+ direction : 'auto', // Whether dropdown should always open in one direction
3037
+ keepOnScreen : true, // Whether dropdown should check whether it is on screen before showing
3038
+
3039
+ match : 'both', // what to match against with search selection (both, text, or label)
3040
+ fullTextSearch : false, // search anywhere in value
3041
+
3042
+ placeholder : 'auto', // whether to convert blank <select> values to placeholder text
3043
+ preserveHTML : true, // preserve html when selecting value
3044
+ sortSelect : false, // sort selection on init
3045
+
3046
+ forceSelection : true, // force a choice on blur with search selection
3047
+ allowAdditions : false, // whether multiple select should allow user added values
3048
+
3049
+ maxSelections : false, // When set to a number limits the number of selections to this count
3050
+ useLabels : true, // whether multiple select should filter currently active selections from choices
3051
+ delimiter : ',', // when multiselect uses normal <input> the values will be delmited with this character
1690
3052
 
1691
- allowTab : true,
1692
- fullTextSearch : false,
1693
- preserveHTML : true,
1694
- sortSelect : false,
3053
+ showOnFocus : true, // show menu on focus
3054
+ allowTab : true, // add tabindex to element
3055
+ allowCategorySelection : false, // allow elements with sub-menus to be selected
1695
3056
 
1696
- allowCategorySelection : false,
3057
+ fireOnInit : false, // Whether callbacks should fire when initializing dropdown values
1697
3058
 
1698
- delay : {
3059
+ transition : 'auto', // auto transition will slide down or up based on direction
3060
+ duration : 200, // duration of transition
3061
+
3062
+ glyphWidth : 1.0714, // widest glyph width in em (W is 1.0714 em) used to calculate multiselect input width
3063
+
3064
+ // label settings on multi-select
3065
+ label: {
3066
+ transition : 'scale',
3067
+ duration : 200,
3068
+ variation : false
3069
+ },
3070
+
3071
+ // delay before event
3072
+ delay : {
1699
3073
  hide : 300,
1700
3074
  show : 200,
1701
- search : 50,
3075
+ search : 20,
1702
3076
  touch : 50
1703
3077
  },
1704
3078
 
1705
- forceSelection: true,
1706
-
1707
- transition : 'auto',
1708
- duration : 250,
1709
-
1710
3079
  /* Callbacks */
1711
- onNoResults : function(searchTerm){},
1712
- onChange : function(value, text){},
1713
- onShow : function(){},
1714
- onHide : function(){},
3080
+ onChange : function(value, text, $selected){},
3081
+ onAdd : function(value, text, $selected){},
3082
+ onRemove : function(value, text, $selected){},
1715
3083
 
1716
- /* Component */
3084
+ onLabelSelect : function($selectedLabels){},
3085
+ onLabelCreate : function(value, text) { return $(this); },
3086
+ onNoResults : function(searchTerm) { return true; },
3087
+ onShow : function(){},
3088
+ onHide : function(){},
1717
3089
 
3090
+ /* Component */
1718
3091
  name : 'Dropdown',
1719
3092
  namespace : 'dropdown',
1720
3093
 
1721
- error : {
3094
+ message: {
3095
+ addResult : 'Add <b>{term}</b>',
3096
+ count : '{count} selected',
3097
+ maxSelections : 'Max {maxCount} selections',
3098
+ noResults : 'No results found.',
3099
+ serverError : 'There was an error contacting the server'
3100
+ },
3101
+
3102
+ error : {
1722
3103
  action : 'You called a dropdown action that was not defined',
1723
3104
  alreadySetup : 'Once a select has been initialized behaviors must be called on the created ui dropdown',
3105
+ labels : 'Allowing user additions currently requires the use of labels.',
1724
3106
  method : 'The method you called is not defined.',
1725
- transition : 'The requested transition was not found'
3107
+ noAPI : 'The API module is required to load resources remotely',
3108
+ noStorage : 'Saving remote data requires session storage',
3109
+ noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>'
3110
+ },
3111
+
3112
+ regExp : {
3113
+ escape : /[-[\]{}()*+?.,\\^$|#\s]/g,
1726
3114
  },
1727
3115
 
1728
- metadata: {
3116
+ metadata : {
1729
3117
  defaultText : 'defaultText',
1730
3118
  defaultValue : 'defaultValue',
1731
- placeholderText : 'placeholderText',
3119
+ placeholderText : 'placeholder',
1732
3120
  text : 'text',
1733
3121
  value : 'value'
1734
3122
  },
1735
3123
 
1736
3124
  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)'
3125
+ addition : '.addition',
3126
+ dropdown : '.ui.dropdown',
3127
+ icon : '> .dropdown.icon',
3128
+ input : '> input[type="hidden"], > select',
3129
+ item : '.item',
3130
+ label : '> .label',
3131
+ remove : '> .label > .delete.icon',
3132
+ siblingLabel : '.label',
3133
+ menu : '.menu',
3134
+ message : '.message',
3135
+ menuIcon : '.dropdown.icon',
3136
+ search : 'input.search, .menu > .search > input',
3137
+ text : '> .text:not(.icon)',
3138
+ unselectable : '.disabled, .filtered'
1744
3139
  },
1745
3140
 
1746
3141
  className : {
1747
3142
  active : 'active',
3143
+ addition : 'addition',
1748
3144
  animating : 'animating',
1749
3145
  disabled : 'disabled',
1750
3146
  dropdown : 'ui dropdown',
1751
3147
  filtered : 'filtered',
3148
+ hidden : 'hidden transition',
3149
+ item : 'item',
3150
+ label : 'ui label',
1752
3151
  loading : 'loading',
1753
3152
  menu : 'menu',
3153
+ message : 'message',
3154
+ multiple : 'multiple',
1754
3155
  placeholder : 'default',
1755
3156
  search : 'search',
1756
3157
  selected : 'selected',
@@ -1763,17 +3164,8 @@ $.fn.dropdown.settings = {
1763
3164
 
1764
3165
  /* Templates */
1765
3166
  $.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
- },
3167
+
3168
+ // generates dropdown from select values
1777
3169
  dropdown: function(select) {
1778
3170
  var
1779
3171
  placeholder = select.placeholder || false,
@@ -1789,20 +3181,43 @@ $.fn.dropdown.settings.templates = {
1789
3181
  }
1790
3182
  html += '<div class="menu">';
1791
3183
  $.each(select.values, function(index, option) {
1792
- html += '<div class="item" data-value="' + option.value + '">' + option.name + '</div>';
3184
+ html += (option.disabled)
3185
+ ? '<div class="disabled item" data-value="' + option.value + '">' + option.name + '</div>'
3186
+ : '<div class="item" data-value="' + option.value + '">' + option.name + '</div>'
3187
+ ;
1793
3188
  });
1794
3189
  html += '</div>';
1795
3190
  return html;
1796
- }
1797
- };
3191
+ },
3192
+
3193
+ // generates just menu from select
3194
+ menu: function(response) {
3195
+ var
3196
+ values = response.values || {},
3197
+ html = ''
3198
+ ;
3199
+ $.each(response.values, function(index, option) {
3200
+ html += '<div class="item" data-value="' + option.value + '">' + option.name + '</div>';
3201
+ });
3202
+ return html;
3203
+ },
3204
+
3205
+ // generates label for multiselect
3206
+ label: function(value, text) {
3207
+ return text + '<i class="delete icon"></i>';
3208
+ },
1798
3209
 
1799
3210
 
1800
- /* Dependencies */
1801
- $.extend( $.easing, {
1802
- easeOutQuad: function (x, t, b, c, d) {
1803
- return -c *(t/=d)*(t-2) + b;
3211
+ // generates messages like "No results"
3212
+ message: function(message) {
3213
+ return message;
1804
3214
  },
1805
- });
1806
3215
 
3216
+ // generates user addition to selection menu
3217
+ addition: function(choice) {
3218
+ return choice;
3219
+ }
3220
+
3221
+ };
1807
3222
 
1808
3223
  })( jQuery, window , document );