2
* # Semantic - Dropdown
3
* http://github.com/jlukic/semantic-ui/
6
* Copyright 2014 Contributors
7
* Released under the MIT license
8
* http://opensource.org/licenses/MIT
11
;(function ( $, window, document, undefined ) {
13
$.fn.dropdown = function(parameters) {
15
$allModules = $(this),
16
$document = $(document),
18
moduleSelector = $allModules.selector || '',
20
hasTouch = ('ontouchstart' in document.documentElement),
21
time = new Date().getTime(),
25
methodInvoked = (typeof query == 'string'),
26
queryArguments = [].slice.call(arguments, 1),
33
settings = ( $.isPlainObject(parameters) )
34
? $.extend(true, {}, $.fn.dropdown.settings, parameters)
35
: $.extend({}, $.fn.dropdown.settings),
37
className = settings.className,
38
metadata = settings.metadata,
39
namespace = settings.namespace,
40
selector = settings.selector,
41
error = settings.error,
43
eventNamespace = '.' + namespace,
44
moduleNamespace = 'module-' + namespace,
47
$item = $module.find(selector.item),
48
$text = $module.find(selector.text),
49
$input = $module.find(selector.input),
51
$menu = $module.children(selector.menu),
55
instance = $module.data(moduleNamespace),
61
initialize: function() {
62
module.debug('Initializing dropdown', settings);
64
module.save.defaults();
65
module.set.selected();
68
module.bind.touchEvents();
70
module.bind.mouseEvents();
71
module.bind.keyboardEvents();
75
instantiate: function() {
76
module.verbose('Storing instance of dropdown', module);
79
.data(moduleNamespace, module)
84
module.verbose('Destroying previous dropdown for', $module);
90
.removeData(moduleNamespace)
95
keyboardEvents: function() {
96
module.debug('Binding keyboard events');
98
.on('keydown' + eventNamespace, module.handleKeyboard)
101
.on('focus' + eventNamespace, module.show)
104
touchEvents: function() {
105
module.debug('Touch device detected binding touch events');
107
.on('touchstart' + eventNamespace, module.event.test.toggle)
110
.on('touchstart' + eventNamespace, module.event.item.mouseenter)
111
.on('touchstart' + eventNamespace, module.event.item.click)
114
mouseEvents: function() {
115
module.verbose('Mouse detected binding mouse events');
116
if(settings.on == 'click') {
118
.on('click' + eventNamespace, module.event.test.toggle)
121
else if(settings.on == 'hover') {
123
.on('mouseenter' + eventNamespace, module.delay.show)
124
.on('mouseleave' + eventNamespace, module.delay.hide)
129
.on(settings.on + eventNamespace, module.toggle)
133
.on('mouseenter' + eventNamespace, module.event.item.mouseenter)
134
.on('mouseleave' + eventNamespace, module.event.item.mouseleave)
135
.on('click' + eventNamespace, module.event.item.click)
139
module.verbose('Binding hide intent event to document');
142
.on('touchstart' + eventNamespace, module.event.test.touch)
143
.on('touchmove' + eventNamespace, module.event.test.touch)
147
.on('click' + eventNamespace, module.event.test.hide)
154
module.verbose('Removing hide intent event from document');
157
.off('touchstart' + eventNamespace)
158
.off('touchmove' + eventNamespace)
162
.off('click' + eventNamespace)
167
handleKeyboard: function(event) {
169
$selectedItem = $item.filter('.' + className.selected),
170
pressedKey = event.which,
177
selectedClass = className.selected,
178
currentIndex = $item.index( $selectedItem ),
179
hasSelectedItem = ($selectedItem.size() > 0),
180
resultSize = $item.size(),
184
if(pressedKey == keys.escape) {
185
module.verbose('Escape key pressed, closing dropdown');
189
if(module.is.visible()) {
190
if(pressedKey == keys.enter && hasSelectedItem) {
191
module.verbose('Enter key pressed, choosing selected item');
192
$.proxy(module.event.item.click, $item.filter('.' + selectedClass) )(event);
193
event.preventDefault();
196
else if(pressedKey == keys.upArrow) {
197
module.verbose('Up key pressed, changing active item');
198
newIndex = (currentIndex - 1 < 0)
203
.removeClass(selectedClass)
205
.addClass(selectedClass)
207
event.preventDefault();
209
else if(pressedKey == keys.downArrow) {
210
module.verbose('Down key pressed, changing active item');
211
newIndex = (currentIndex + 1 >= resultSize)
216
.removeClass(selectedClass)
218
.addClass(selectedClass)
220
event.preventDefault();
224
if(pressedKey == keys.enter) {
232
toggle: function(event) {
233
if( module.determine.intent(event, module.toggle) ) {
234
event.preventDefault();
237
touch: function(event) {
238
module.determine.intent(event, function() {
239
if(event.type == 'touchstart') {
240
module.timer = setTimeout(module.hide, settings.delay.touch);
242
else if(event.type == 'touchmove') {
243
clearTimeout(module.timer);
246
event.stopPropagation();
248
hide: function(event) {
249
module.determine.intent(event, module.hide);
255
mouseenter: function(event) {
257
$currentMenu = $(this).find(selector.submenu),
258
$otherMenus = $(this).siblings(selector.item).children(selector.menu)
260
if($currentMenu.length > 0 || $otherMenus.length > 0) {
261
clearTimeout(module.itemTimer);
262
module.itemTimer = setTimeout(function() {
263
if($otherMenus.length > 0) {
264
module.animate.hide(false, $otherMenus.filter(':visible'));
266
if($currentMenu.length > 0) {
267
module.verbose('Showing sub-menu', $currentMenu);
268
module.animate.show(false, $currentMenu);
270
}, settings.delay.show * 2);
271
event.preventDefault();
272
event.stopPropagation();
276
mouseleave: function(event) {
278
$currentMenu = $(this).find(selector.menu)
280
if($currentMenu.size() > 0) {
281
clearTimeout(module.itemTimer);
282
module.itemTimer = setTimeout(function() {
283
module.verbose('Hiding sub-menu', $currentMenu);
284
module.animate.hide(false, $currentMenu);
285
}, settings.delay.hide);
289
click: function (event) {
292
text = ( $choice.data(metadata.text) !== undefined )
293
? $choice.data(metadata.text)
295
value = ( $choice.data(metadata.value) !== undefined)
296
? $choice.data(metadata.value)
297
: (typeof text === 'string')
300
callback = function() {
301
module.determine.selectAction(text, value);
302
$.proxy(settings.onChange, element)(value, text);
305
if( $choice.find(selector.menu).size() === 0 ) {
306
if(event.type == 'touchstart') {
307
$choice.one('click', callback);
317
resetStyle: function() {
318
$(this).removeAttr('style');
324
selectAction: function(text, value) {
325
module.verbose('Determining action', settings.action);
326
if( $.isFunction( module.action[settings.action] ) ) {
327
module.verbose('Triggering preset action', settings.action, text, value);
328
module.action[ settings.action ](text, value);
330
else if( $.isFunction(settings.action) ) {
331
module.verbose('Triggering user action', settings.action, text, value);
332
settings.action(text, value);
335
module.error(error.action, settings.action);
338
intent: function(event, callback) {
339
module.debug('Determining whether event occurred in dropdown', event.target);
340
callback = callback || function(){};
341
if( $(event.target).closest($menu).size() === 0 ) {
342
module.verbose('Triggering event', callback);
347
module.verbose('Event occurred in dropdown, canceling callback');
355
nothing: function() {},
361
activate: function(text, value) {
362
value = (value !== undefined)
366
module.set.selected(value);
367
module.set.value(value);
372
auto: function(text, value) {
373
value = (value !== undefined)
377
module.set.selected(value);
378
module.set.value(value);
383
changeText: function(text, value) {
384
value = (value !== undefined)
388
module.set.selected(value);
393
updateForm: function(text, value) {
394
value = (value !== undefined)
398
module.set.selected(value);
399
module.set.value(value);
410
return ($input.size() > 0)
412
: $module.data(metadata.value)
415
item: function(value, strict) {
417
$selectedItem = false
419
value = (value !== undefined)
421
: ( module.get.value() !== undefined)
425
if(strict === undefined && value === '') {
426
module.debug('Ambiguous dropdown value using strict type check', value);
430
strict = strict || false;
432
if(value !== undefined) {
437
optionText = ( $choice.data(metadata.text) !== undefined )
438
? $choice.data(metadata.text)
440
optionValue = ( $choice.data(metadata.value) !== undefined )
441
? $choice.data(metadata.value)
442
: (typeof optionText === 'string')
443
? optionText.toLowerCase()
447
if( optionValue === value ) {
448
$selectedItem = $(this);
450
else if( !$selectedItem && optionText === value ) {
451
$selectedItem = $(this);
455
if( optionValue == value ) {
456
$selectedItem = $(this);
458
else if( !$selectedItem && optionText == value ) {
459
$selectedItem = $(this);
466
value = module.get.text();
468
return $selectedItem || false;
473
defaults: function() {
474
module.restore.defaultText();
475
module.restore.defaultValue();
477
defaultText: function() {
479
defaultText = $module.data(metadata.defaultText)
481
module.debug('Restoring default text', defaultText);
482
module.set.text(defaultText);
484
defaultValue: function() {
486
defaultValue = $module.data(metadata.defaultValue)
488
if(defaultValue !== undefined) {
489
module.debug('Restoring default value', defaultValue);
490
module.set.selected(defaultValue);
491
module.set.value(defaultValue);
497
defaults: function() {
498
module.save.defaultText();
499
module.save.defaultValue();
501
defaultValue: function() {
502
$module.data(metadata.defaultValue, module.get.value() );
504
defaultText: function() {
505
$module.data(metadata.defaultText, $text.text() );
510
text: function(text) {
511
module.debug('Changing text', text, $text);
512
$text.removeClass(className.placeholder);
515
value: function(value) {
516
module.debug('Adding selected value to hidden input', value, $input);
517
if($input.size() > 0) {
524
$module.data(metadata.value, value);
528
$module.addClass(className.active);
530
visible: function() {
531
$module.addClass(className.visible);
533
selected: function(value) {
535
$selectedItem = module.get.item(value),
539
module.debug('Setting selected menu item to', $selectedItem);
540
selectedText = ($selectedItem.data(metadata.text) !== undefined)
541
? $selectedItem.data(metadata.text)
542
: $selectedItem.text()
545
.removeClass(className.active)
548
.addClass(className.active)
550
module.set.text(selectedText);
557
$module.removeClass(className.active);
559
visible: function() {
560
$module.removeClass(className.visible);
565
selection: function() {
566
return $module.hasClass(className.selection);
568
animated: function($subMenu) {
570
? $subMenu.is(':animated') || $subMenu.transition && $subMenu.transition('is animating')
571
: $menu.is(':animated') || $menu.transition && $menu.transition('is animating')
574
visible: function($subMenu) {
576
? $subMenu.is(':visible')
577
: $menu.is(':visible')
580
hidden: function($subMenu) {
582
? $subMenu.is(':not(:visible)')
583
: $menu.is(':not(:visible)')
590
return (hasTouch || settings.on == 'click');
593
return !$module.hasClass(className.disabled);
598
show: function(callback, $subMenu) {
600
$currentMenu = $subMenu || $menu
602
callback = callback || function(){};
603
if( module.is.hidden($currentMenu) ) {
604
module.verbose('Doing menu show animation', $currentMenu);
605
if(settings.transition == 'none') {
608
else if($.fn.transition !== undefined && $module.transition('is supported')) {
611
animation : settings.transition + ' in',
612
duration : settings.duration,
618
else if(settings.transition == 'slide down') {
628
}, settings.duration, 'easeOutQuad', module.event.resetStyle)
630
.slideDown(100, 'easeOutQuad', function() {
631
$.proxy(module.event.resetStyle, this)();
636
else if(settings.transition == 'fade') {
640
.fadeIn(settings.duration, function() {
641
$.proxy(module.event.resetStyle, this)();
647
module.error(error.transition, settings.transition);
651
hide: function(callback, $subMenu) {
653
$currentMenu = $subMenu || $menu
655
callback = callback || function(){};
656
if(module.is.visible($currentMenu) ) {
657
module.verbose('Doing menu hide animation', $currentMenu);
658
if($.fn.transition !== undefined && $module.transition('is supported')) {
661
animation : settings.transition + ' out',
662
duration : settings.duration,
668
else if(settings.transition == 'none') {
671
else if(settings.transition == 'slide down') {
680
}, 100, 'easeOutQuad', module.event.resetStyle)
683
.slideUp(100, 'easeOutQuad', function() {
684
$.proxy(module.event.resetStyle, this)();
689
else if(settings.transition == 'fade') {
693
.fadeOut(150, function() {
694
$.proxy(module.event.resetStyle, this)();
700
module.error(error.transition);
707
module.debug('Checking if dropdown can show');
708
if( module.is.hidden() ) {
711
module.animate.show(function() {
712
if( module.can.click() ) {
713
module.bind.intent();
715
module.set.visible();
717
$.proxy(settings.onShow, element)();
722
if( !module.is.animated() && module.is.visible() ) {
723
module.debug('Hiding dropdown');
724
if( module.can.click() ) {
725
module.unbind.intent();
727
module.remove.active();
728
module.animate.hide(module.remove.visible);
729
$.proxy(settings.onHide, element)();
735
module.verbose('Delaying show event to ensure user intent');
736
clearTimeout(module.timer);
737
module.timer = setTimeout(module.show, settings.delay.show);
740
module.verbose('Delaying hide event to ensure user intent');
741
clearTimeout(module.timer);
742
module.timer = setTimeout(module.hide, settings.delay.hide);
746
hideOthers: function() {
747
module.verbose('Finding other dropdowns to hide');
750
.has(selector.menu + ':visible')
756
module.verbose('Toggling menu visibility');
757
if( module.is.hidden() ) {
765
setting: function(name, value) {
766
if( $.isPlainObject(name) ) {
767
$.extend(true, settings, name);
769
else if(value !== undefined) {
770
settings[name] = value;
773
return settings[name];
776
internal: function(name, value) {
777
if( $.isPlainObject(name) ) {
778
$.extend(true, module, name);
780
else if(value !== undefined) {
781
module[name] = value;
789
if(settings.performance) {
790
module.performance.log(arguments);
793
module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
794
module.debug.apply(console, arguments);
798
verbose: function() {
799
if(settings.verbose && settings.debug) {
800
if(settings.performance) {
801
module.performance.log(arguments);
804
module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
805
module.verbose.apply(console, arguments);
810
module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
811
module.error.apply(console, arguments);
814
log: function(message) {
820
if(settings.performance) {
821
currentTime = new Date().getTime();
822
previousTime = time || currentTime;
823
executionTime = currentTime - previousTime;
828
'Arguments' : [].slice.call(message, 1) || '',
829
'Execution Time' : executionTime
832
clearTimeout(module.performance.timer);
833
module.performance.timer = setTimeout(module.performance.display, 100);
835
display: function() {
837
title = settings.name + ':',
841
clearTimeout(module.performance.timer);
842
$.each(performance, function(index, data) {
843
totalTime += data['Execution Time'];
845
title += ' ' + totalTime + 'ms';
847
title += ' \'' + moduleSelector + '\'';
849
if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
850
console.groupCollapsed(title);
852
console.table(performance);
855
$.each(performance, function(index, data) {
856
console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
864
invoke: function(query, passedArguments, context) {
871
passedArguments = passedArguments || queryArguments;
872
context = element || context;
873
if(typeof query == 'string' && object !== undefined) {
874
query = query.split(/[\. ]/);
875
maxDepth = query.length - 1;
876
$.each(query, function(depth, value) {
877
var camelCaseValue = (depth != maxDepth)
878
? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
881
if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
882
object = object[camelCaseValue];
884
else if( object[camelCaseValue] !== undefined ) {
885
found = object[camelCaseValue];
888
else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
889
object = object[value];
891
else if( object[value] !== undefined ) {
892
found = object[value];
896
module.error(error.method, query);
901
if ( $.isFunction( found ) ) {
902
response = found.apply(context, passedArguments);
904
else if(found !== undefined) {
907
if($.isArray(returnedValue)) {
908
returnedValue.push(response);
910
else if(returnedValue !== undefined) {
911
returnedValue = [returnedValue, response];
913
else if(response !== undefined) {
914
returnedValue = response;
921
if(instance === undefined) {
924
module.invoke(query);
927
if(instance !== undefined) {
935
return (returnedValue !== undefined)
941
$.fn.dropdown.settings = {
944
namespace : 'dropdown',
959
transition : 'slide down',
962
onChange : function(value, text){},
963
onShow : function(){},
964
onHide : function(){},
967
action : 'You called a dropdown action that was not defined',
968
method : 'The method you called is not defined.',
969
transition : 'The requested transition was not found'
973
defaultText : 'defaultText',
974
defaultValue : 'defaultValue',
982
item : '.menu > .item',
984
input : '> input[type="hidden"]'
989
placeholder : 'default',
990
disabled : 'disabled',
992
selected : 'selected',
993
selection : 'selection'
999
$.extend( $.easing, {
1000
easeOutQuad: function (x, t, b, c, d) {
1001
return -c *(t/=d)*(t-2) + b;
1006
})( jQuery, window , document );