dropdown-native.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. /* Native Javascript for Bootstrap 3 | Dropdown
  2. ----------------------------------------------*/
  3. // DROPDOWN DEFINITION
  4. // ===================
  5. var Dropdown = function( element, option ) {
  6. // initialization element
  7. element = queryElement(element);
  8. // set option
  9. this.persist = option === true || element[getAttribute]('data-persist') === 'true' || false;
  10. // constants, event targets, strings
  11. var self = this, children = 'children',
  12. parent = element[parentNode],
  13. component = 'dropdown', open = 'open',
  14. relatedTarget = null,
  15. menu = queryElement('.dropdown-menu', parent),
  16. menuItems = (function(){
  17. var set = menu[children], newSet = [];
  18. for ( var i=0; i<set[length]; i++ ){
  19. set[i][children][length] && (set[i][children][0].tagName === 'A' && newSet[push](set[i]));
  20. }
  21. return newSet;
  22. })(),
  23. // preventDefault on empty anchor links
  24. preventEmptyAnchor = function(anchor){
  25. (anchor.href && anchor.href.slice(-1) === '#' || anchor[parentNode] && anchor[parentNode].href
  26. && anchor[parentNode].href.slice(-1) === '#') && this[preventDefault]();
  27. },
  28. // toggle dismissible events
  29. toggleDismiss = function(){
  30. var type = element[open] ? on : off;
  31. type(DOC, clickEvent, dismissHandler);
  32. type(DOC, keydownEvent, preventScroll);
  33. type(DOC, keyupEvent, keyHandler);
  34. type(DOC, focusEvent, dismissHandler, true);
  35. },
  36. // handlers
  37. dismissHandler = function(e) {
  38. var eventTarget = e[target], hasData = eventTarget && (eventTarget[getAttribute](dataToggle)
  39. || eventTarget[parentNode] && getAttribute in eventTarget[parentNode]
  40. && eventTarget[parentNode][getAttribute](dataToggle));
  41. if ( e.type === focusEvent && (eventTarget === element || eventTarget === menu || menu[contains](eventTarget) ) ) {
  42. return;
  43. }
  44. if ( (eventTarget === menu || menu[contains](eventTarget)) && (self.persist || hasData) ) { return; }
  45. else {
  46. relatedTarget = eventTarget === element || element[contains](eventTarget) ? element : null;
  47. hide();
  48. }
  49. preventEmptyAnchor.call(e,eventTarget);
  50. },
  51. clickHandler = function(e) {
  52. relatedTarget = element;
  53. show();
  54. preventEmptyAnchor.call(e,e[target]);
  55. },
  56. preventScroll = function(e){
  57. var key = e.which || e.keyCode;
  58. if( key === 38 || key === 40 ) { e[preventDefault](); }
  59. },
  60. keyHandler = function(e){
  61. var key = e.which || e.keyCode,
  62. activeItem = DOC.activeElement,
  63. idx = menuItems[indexOf](activeItem[parentNode]),
  64. isSameElement = activeItem === element,
  65. isInsideMenu = menu[contains](activeItem),
  66. isMenuItem = activeItem[parentNode][parentNode] === menu;
  67. if ( isMenuItem ) { // navigate up | down
  68. idx = isSameElement ? 0
  69. : key === 38 ? (idx>1?idx-1:0)
  70. : key === 40 ? (idx<menuItems[length]-1?idx+1:idx) : idx;
  71. menuItems[idx] && setFocus(menuItems[idx][children][0]);
  72. }
  73. if ( (menuItems[length] && isMenuItem // menu has items
  74. || !menuItems[length] && (isInsideMenu || isSameElement) // menu might be a form
  75. || !isInsideMenu ) // or the focused element is not in the menu at all
  76. && element[open] && key === 27 // menu must be open
  77. ) {
  78. self.toggle();
  79. relatedTarget = null;
  80. }
  81. },
  82. // private methods
  83. show = function() {
  84. bootstrapCustomEvent.call(parent, showEvent, component, relatedTarget);
  85. addClass(parent,open);
  86. element[setAttribute](ariaExpanded,true);
  87. bootstrapCustomEvent.call(parent, shownEvent, component, relatedTarget);
  88. element[open] = true;
  89. off(element, clickEvent, clickHandler);
  90. setTimeout(function(){
  91. setFocus( menu[getElementsByTagName]('INPUT')[0] || element ); // focus the first input item | element
  92. toggleDismiss();
  93. },1);
  94. },
  95. hide = function() {
  96. bootstrapCustomEvent.call(parent, hideEvent, component, relatedTarget);
  97. removeClass(parent,open);
  98. element[setAttribute](ariaExpanded,false);
  99. bootstrapCustomEvent.call(parent, hiddenEvent, component, relatedTarget);
  100. element[open] = false;
  101. toggleDismiss();
  102. setFocus(element);
  103. setTimeout(function(){ on(element, clickEvent, clickHandler); },1);
  104. };
  105. // set initial state to closed
  106. element[open] = false;
  107. // public methods
  108. this.toggle = function() {
  109. if (hasClass(parent,open) && element[open]) { hide(); }
  110. else { show(); }
  111. };
  112. // init
  113. if (!(stringDropdown in element)) { // prevent adding event handlers twice
  114. !tabindex in menu && menu[setAttribute](tabindex, '0'); // Fix onblur on Chrome | Safari
  115. on(element, clickEvent, clickHandler);
  116. }
  117. element[stringDropdown] = self;
  118. };
  119. // DROPDOWN DATA API
  120. // =================
  121. supports[push]( [stringDropdown, Dropdown, '['+dataToggle+'="dropdown"]'] );