modal-native.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. /* Native Javascript for Bootstrap 3 | Modal
  2. -------------------------------------------*/
  3. // MODAL DEFINITION
  4. // ===============
  5. var Modal = function(element, options) { // element can be the modal/triggering button
  6. // the modal (both JavaScript / DATA API init) / triggering button element (DATA API)
  7. element = queryElement(element);
  8. // strings
  9. var component = 'modal',
  10. staticString = 'static',
  11. modalTrigger = 'modalTrigger',
  12. paddingRight = 'paddingRight',
  13. modalBackdropString = 'modal-backdrop',
  14. isAnimating = 'isAnimating',
  15. // determine modal, triggering element
  16. btnCheck = element[getAttribute](dataTarget)||element[getAttribute]('href'),
  17. checkModal = queryElement( btnCheck ),
  18. modal = hasClass(element,component) ? element : checkModal;
  19. if ( hasClass(element, component) ) { element = null; } // modal is now independent of it's triggering element
  20. if ( !modal ) { return; } // invalidate
  21. // set options
  22. options = options || {};
  23. this[keyboard] = options[keyboard] === false || modal[getAttribute](dataKeyboard) === 'false' ? false : true;
  24. this[backdrop] = options[backdrop] === staticString || modal[getAttribute](databackdrop) === staticString ? staticString : true;
  25. this[backdrop] = options[backdrop] === false || modal[getAttribute](databackdrop) === 'false' ? false : this[backdrop];
  26. this[animation] = hasClass(modal, 'fade') ? true : false;
  27. this[content] = options[content]; // JavaScript only
  28. // set an initial state of the modal
  29. modal[isAnimating] = false;
  30. // bind, constants, event targets and other vars
  31. var self = this, relatedTarget = null,
  32. bodyIsOverflowing, scrollBarWidth, overlay, overlayDelay, modalTimer,
  33. // also find fixed-top / fixed-bottom items
  34. fixedItems = getElementsByClassName(HTML,fixedTop).concat(getElementsByClassName(HTML,fixedBottom)),
  35. // private methods
  36. getWindowWidth = function() {
  37. var htmlRect = HTML[getBoundingClientRect]();
  38. return globalObject[innerWidth] || (htmlRect[right] - Math.abs(htmlRect[left]));
  39. },
  40. setScrollbar = function () {
  41. var bodyStyle = DOC[body].currentStyle || globalObject[getComputedStyle](DOC[body]),
  42. bodyPad = parseInt((bodyStyle[paddingRight]), 10), itemPad;
  43. if (bodyIsOverflowing) {
  44. DOC[body][style][paddingRight] = (bodyPad + scrollBarWidth) + 'px';
  45. modal[style][paddingRight] = scrollBarWidth+'px';
  46. if (fixedItems[length]){
  47. for (var i = 0; i < fixedItems[length]; i++) {
  48. itemPad = (fixedItems[i].currentStyle || globalObject[getComputedStyle](fixedItems[i]))[paddingRight];
  49. fixedItems[i][style][paddingRight] = ( parseInt(itemPad) + scrollBarWidth) + 'px';
  50. }
  51. }
  52. }
  53. },
  54. resetScrollbar = function () {
  55. DOC[body][style][paddingRight] = '';
  56. modal[style][paddingRight] = '';
  57. if (fixedItems[length]){
  58. for (var i = 0; i < fixedItems[length]; i++) {
  59. fixedItems[i][style][paddingRight] = '';
  60. }
  61. }
  62. },
  63. measureScrollbar = function () { // thx walsh
  64. var scrollDiv = DOC[createElement]('div'), widthValue;
  65. scrollDiv.className = component+'-scrollbar-measure'; // this is here to stay
  66. DOC[body][appendChild](scrollDiv);
  67. widthValue = scrollDiv[offsetWidth] - scrollDiv[clientWidth];
  68. DOC[body].removeChild(scrollDiv);
  69. return widthValue;
  70. },
  71. checkScrollbar = function () {
  72. bodyIsOverflowing = DOC[body][clientWidth] < getWindowWidth();
  73. scrollBarWidth = measureScrollbar();
  74. },
  75. createOverlay = function() {
  76. var newOverlay = DOC[createElement]('div');
  77. overlay = queryElement('.'+modalBackdropString);
  78. if ( overlay === null ) {
  79. newOverlay[setAttribute]('class', modalBackdropString + (self[animation] ? ' fade' : ''));
  80. overlay = newOverlay;
  81. DOC[body][appendChild](overlay);
  82. }
  83. modalOverlay = 1;
  84. },
  85. removeOverlay = function() {
  86. overlay = queryElement('.'+modalBackdropString);
  87. if ( overlay && overlay !== null && typeof overlay === 'object' ) {
  88. modalOverlay = 0;
  89. DOC[body].removeChild(overlay); overlay = null;
  90. }
  91. },
  92. // triggers
  93. triggerShow = function() {
  94. setFocus(modal);
  95. modal[isAnimating] = false;
  96. bootstrapCustomEvent.call(modal, shownEvent, component, relatedTarget);
  97. on(globalObject, resizeEvent, self.update, passiveHandler);
  98. on(modal, clickEvent, dismissHandler);
  99. on(DOC, keydownEvent, keyHandler);
  100. },
  101. triggerHide = function() {
  102. modal[style].display = '';
  103. element && (setFocus(element));
  104. bootstrapCustomEvent.call(modal, hiddenEvent, component);
  105. (function(){
  106. if (!getElementsByClassName(DOC,component+' '+inClass)[0]) {
  107. resetScrollbar();
  108. removeClass(DOC[body],component+'-open');
  109. overlay && hasClass(overlay,'fade') ? (removeClass(overlay,inClass), emulateTransitionEnd(overlay,removeOverlay))
  110. : removeOverlay();
  111. off(globalObject, resizeEvent, self.update, passiveHandler);
  112. off(modal, clickEvent, dismissHandler);
  113. off(DOC, keydownEvent, keyHandler);
  114. }
  115. }());
  116. modal[isAnimating] = false;
  117. },
  118. // handlers
  119. clickHandler = function(e) {
  120. if ( modal[isAnimating] ) return;
  121. var clickTarget = e[target];
  122. clickTarget = clickTarget[hasAttribute](dataTarget) || clickTarget[hasAttribute]('href') ? clickTarget : clickTarget[parentNode];
  123. if ( clickTarget === element && !hasClass(modal,inClass) ) {
  124. modal[modalTrigger] = element;
  125. relatedTarget = element;
  126. self.show();
  127. e[preventDefault]();
  128. }
  129. },
  130. keyHandler = function(e) {
  131. if ( modal[isAnimating] ) return;
  132. var key = e.which || e.keyCode; // keyCode for IE8
  133. if (self[keyboard] && key == 27 && hasClass(modal,inClass)) {
  134. self.hide();
  135. }
  136. },
  137. dismissHandler = function(e) {
  138. if ( modal[isAnimating] ) return;
  139. var clickTarget = e[target];
  140. if ( hasClass(modal,inClass) && (clickTarget[parentNode][getAttribute](dataDismiss) === component
  141. || clickTarget[getAttribute](dataDismiss) === component
  142. || clickTarget === modal && self[backdrop] !== staticString) ) {
  143. self.hide(); relatedTarget = null;
  144. e[preventDefault]();
  145. }
  146. };
  147. // public methods
  148. this.toggle = function() {
  149. if ( hasClass(modal,inClass) ) {this.hide();} else {this.show();}
  150. };
  151. this.show = function() {
  152. if ( hasClass(modal,inClass) || modal[isAnimating] ) {return}
  153. clearTimeout(modalTimer);
  154. modalTimer = setTimeout(function(){
  155. modal[isAnimating] = true;
  156. bootstrapCustomEvent.call(modal, showEvent, component, relatedTarget);
  157. // we elegantly hide any opened modal
  158. var currentOpen = getElementsByClassName(DOC,component+' in')[0];
  159. if (currentOpen && currentOpen !== modal) {
  160. modalTrigger in currentOpen && currentOpen[modalTrigger][stringModal].hide();
  161. stringModal in currentOpen && currentOpen[stringModal].hide();
  162. }
  163. if ( self[backdrop] ) {
  164. !modalOverlay && !overlay && createOverlay();
  165. }
  166. if ( overlay && !hasClass(overlay,inClass)) {
  167. overlay[offsetWidth]; // force reflow to enable trasition
  168. overlayDelay = getTransitionDurationFromElement(overlay);
  169. addClass(overlay,inClass);
  170. }
  171. setTimeout( function() {
  172. modal[style].display = 'block';
  173. checkScrollbar();
  174. setScrollbar();
  175. addClass(DOC[body],component+'-open');
  176. addClass(modal,inClass);
  177. modal[setAttribute](ariaHidden, false);
  178. hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerShow) : triggerShow();
  179. }, supportTransitions && overlay && overlayDelay ? overlayDelay : 1);
  180. },1);
  181. };
  182. this.hide = function() {
  183. if ( modal[isAnimating] || !hasClass(modal,inClass) ) {return}
  184. clearTimeout(modalTimer);
  185. modalTimer = setTimeout(function(){
  186. modal[isAnimating] = true;
  187. bootstrapCustomEvent.call(modal, hideEvent, component);
  188. overlay = queryElement('.'+modalBackdropString);
  189. overlayDelay = overlay && getTransitionDurationFromElement(overlay);
  190. removeClass(modal,inClass);
  191. modal[setAttribute](ariaHidden, true);
  192. setTimeout(function(){
  193. hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerHide) : triggerHide();
  194. }, supportTransitions && overlay && overlayDelay ? overlayDelay : 2);
  195. },2)
  196. };
  197. this.setContent = function( content ) {
  198. queryElement('.'+component+'-content',modal)[innerHTML] = content;
  199. };
  200. this.update = function() {
  201. if (hasClass(modal,inClass)) {
  202. checkScrollbar();
  203. setScrollbar();
  204. }
  205. };
  206. // init
  207. // prevent adding event handlers over and over
  208. // modal is independent of a triggering element
  209. if ( !!element && !(stringModal in element) ) {
  210. on(element, clickEvent, clickHandler);
  211. }
  212. if ( !!self[content] ) { self.setContent( self[content] ); }
  213. if (element) { element[stringModal] = self; modal[modalTrigger] = element; }
  214. else { modal[stringModal] = self; }
  215. };
  216. // DATA API
  217. supports[push]( [ stringModal, Modal, '['+dataToggle+'="modal"]' ] );