popover-native.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /* Native Javascript for Bootstrap 4 | Popover
  2. ----------------------------------------------*/
  3. // POPOVER DEFINITION
  4. // ==================
  5. var Popover = function( element, options ) {
  6. // initialization element
  7. element = queryElement(element);
  8. // set options
  9. options = options || {};
  10. // DATA API
  11. var triggerData = element[getAttribute](dataTrigger), // click / hover / focus
  12. animationData = element[getAttribute](dataAnimation), // true / false
  13. placementData = element[getAttribute](dataPlacement),
  14. dismissibleData = element[getAttribute](dataDismissible),
  15. delayData = element[getAttribute](dataDelay),
  16. containerData = element[getAttribute](dataContainer),
  17. // internal strings
  18. component = 'popover',
  19. template = 'template',
  20. trigger = 'trigger',
  21. classString = 'class',
  22. div = 'div',
  23. fade = 'fade',
  24. dataContent = 'data-content',
  25. dismissible = 'dismissible',
  26. closeBtn = '<button type="button" class="close">×</button>',
  27. // check container
  28. containerElement = queryElement(options[container]),
  29. containerDataElement = queryElement(containerData),
  30. // maybe the element is inside a modal
  31. modal = getClosest(element,'.modal'),
  32. // maybe the element is inside a fixed navbar
  33. navbarFixedTop = getClosest(element,'.'+fixedTop),
  34. navbarFixedBottom = getClosest(element,'.'+fixedBottom);
  35. // set instance options
  36. this[template] = options[template] ? options[template] : null; // JavaScript only
  37. this[trigger] = options[trigger] ? options[trigger] : triggerData || hoverEvent;
  38. this[animation] = options[animation] && options[animation] !== fade ? options[animation] : animationData || fade;
  39. this[placement] = options[placement] ? options[placement] : placementData || top;
  40. this[delay] = parseInt(options[delay] || delayData) || 200;
  41. this[dismissible] = options[dismissible] || dismissibleData === 'true' ? true : false;
  42. this[container] = containerElement ? containerElement
  43. : containerDataElement ? containerDataElement
  44. : navbarFixedTop ? navbarFixedTop
  45. : navbarFixedBottom ? navbarFixedBottom
  46. : modal ? modal : DOC[body];
  47. // bind, content
  48. var self = this,
  49. titleString = options.title || element[getAttribute](dataTitle) || null,
  50. contentString = options.content || element[getAttribute](dataContent) || null;
  51. if ( !contentString && !this[template] ) return; // invalidate
  52. // constants, vars
  53. var popover = null, timer = 0, placementSetting = this[placement],
  54. // handlers
  55. dismissibleHandler = function(e) {
  56. if (popover !== null && e[target] === queryElement('.close',popover)) {
  57. self.hide();
  58. }
  59. },
  60. // private methods
  61. removePopover = function() {
  62. self[container].removeChild(popover);
  63. timer = null; popover = null;
  64. },
  65. createPopover = function() {
  66. titleString = options.title || element[getAttribute](dataTitle);
  67. contentString = options.content || element[getAttribute](dataContent);
  68. // fixing https://github.com/thednp/bootstrap.native/issues/233
  69. contentString = !!contentString ? contentString.trim() : null;
  70. popover = DOC[createElement](div);
  71. // popover arrow
  72. var popoverArrow = DOC[createElement](div);
  73. popoverArrow[setAttribute](classString,'arrow');
  74. popover[appendChild](popoverArrow);
  75. if ( contentString !== null && self[template] === null ) { //create the popover from data attributes
  76. popover[setAttribute]('role','tooltip');
  77. if (titleString !== null) {
  78. var popoverTitle = DOC[createElement]('h3');
  79. popoverTitle[setAttribute](classString,component+'-header');
  80. popoverTitle[innerHTML] = self[dismissible] ? titleString + closeBtn : titleString;
  81. popover[appendChild](popoverTitle);
  82. }
  83. //set popover content
  84. var popoverContent = DOC[createElement](div);
  85. popoverContent[setAttribute](classString,component+'-body');
  86. popoverContent[innerHTML] = self[dismissible] && titleString === null ? contentString + closeBtn : contentString;
  87. popover[appendChild](popoverContent);
  88. } else { // or create the popover from template
  89. var popoverTemplate = DOC[createElement](div);
  90. self[template] = self[template].trim();
  91. popoverTemplate[innerHTML] = self[template];
  92. popover[innerHTML] = popoverTemplate.firstChild[innerHTML];
  93. }
  94. //append to the container
  95. self[container][appendChild](popover);
  96. popover[style].display = 'block';
  97. popover[setAttribute](classString, component+ ' bs-' + component+'-'+placementSetting + ' ' + self[animation]);
  98. },
  99. showPopover = function () {
  100. !hasClass(popover,showClass) && ( addClass(popover,showClass) );
  101. },
  102. updatePopover = function() {
  103. styleTip(element, popover, placementSetting, self[container]);
  104. },
  105. // event toggle
  106. dismissHandlerToggle = function(type){
  107. if (clickEvent == self[trigger] || 'focus' == self[trigger]) {
  108. !self[dismissible] && type( element, 'blur', self.hide );
  109. }
  110. self[dismissible] && type( DOC, clickEvent, dismissibleHandler );
  111. type( globalObject, resizeEvent, self.hide, passiveHandler );
  112. },
  113. // triggers
  114. showTrigger = function() {
  115. dismissHandlerToggle(on);
  116. bootstrapCustomEvent.call(element, shownEvent, component);
  117. },
  118. hideTrigger = function() {
  119. dismissHandlerToggle(off);
  120. removePopover();
  121. bootstrapCustomEvent.call(element, hiddenEvent, component);
  122. };
  123. // public methods / handlers
  124. this.toggle = function() {
  125. if (popover === null) { self.show(); }
  126. else { self.hide(); }
  127. };
  128. this.show = function() {
  129. clearTimeout(timer);
  130. timer = setTimeout( function() {
  131. if (popover === null) {
  132. placementSetting = self[placement]; // we reset placement in all cases
  133. createPopover();
  134. updatePopover();
  135. showPopover();
  136. bootstrapCustomEvent.call(element, showEvent, component);
  137. !!self[animation] ? emulateTransitionEnd(popover, showTrigger) : showTrigger();
  138. }
  139. }, 20 );
  140. };
  141. this.hide = function() {
  142. clearTimeout(timer);
  143. timer = setTimeout( function() {
  144. if (popover && popover !== null && hasClass(popover,showClass)) {
  145. bootstrapCustomEvent.call(element, hideEvent, component);
  146. removeClass(popover,showClass);
  147. !!self[animation] ? emulateTransitionEnd(popover, hideTrigger) : hideTrigger();
  148. }
  149. }, self[delay] );
  150. };
  151. // init
  152. if ( !(stringPopover in element) ) { // prevent adding event handlers twice
  153. if (self[trigger] === hoverEvent) {
  154. on( element, mouseHover[0], self.show );
  155. if (!self[dismissible]) { on( element, mouseHover[1], self.hide ); }
  156. } else if (clickEvent == self[trigger] || 'focus' == self[trigger]) {
  157. on( element, self[trigger], self.toggle );
  158. }
  159. }
  160. element[stringPopover] = self;
  161. };
  162. // POPOVER DATA API
  163. // ================
  164. supports[push]( [ stringPopover, Popover, '['+dataToggle+'="popover"]' ] );