// Native Javascript for Bootstrap 4 v2.0.23 | © dnp_theme | MIT-License (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD support: define([], factory); } else if (typeof module === 'object' && module.exports) { // CommonJS-like: module.exports = factory(); } else { // Browser globals (root is window) var bsn = factory(); root.Alert = bsn.Alert; root.Button = bsn.Button; root.Carousel = bsn.Carousel; root.Collapse = bsn.Collapse; root.Dropdown = bsn.Dropdown; root.Modal = bsn.Modal; root.Popover = bsn.Popover; root.ScrollSpy = bsn.ScrollSpy; root.Tab = bsn.Tab; root.Tooltip = bsn.Tooltip; } }(this, function () { /* Native Javascript for Bootstrap 4 | Internal Utility Functions ----------------------------------------------------------------*/ "use strict"; // globals var globalObject = typeof global !== 'undefined' ? global : this||window, DOC = document, HTML = DOC.documentElement, body = 'body', // allow the library to be used in <head> // Native Javascript for Bootstrap Global Object BSN = globalObject.BSN = {}, supports = BSN.supports = [], // function toggle attributes dataToggle = 'data-toggle', dataDismiss = 'data-dismiss', dataSpy = 'data-spy', dataRide = 'data-ride', // components stringAlert = 'Alert', stringButton = 'Button', stringCarousel = 'Carousel', stringCollapse = 'Collapse', stringDropdown = 'Dropdown', stringModal = 'Modal', stringPopover = 'Popover', stringScrollSpy = 'ScrollSpy', stringTab = 'Tab', stringTooltip = 'Tooltip', // options DATA API databackdrop = 'data-backdrop', dataKeyboard = 'data-keyboard', dataTarget = 'data-target', dataInterval = 'data-interval', dataHeight = 'data-height', dataPause = 'data-pause', dataTitle = 'data-title', dataOriginalTitle = 'data-original-title', dataOriginalText = 'data-original-text', dataDismissible = 'data-dismissible', dataTrigger = 'data-trigger', dataAnimation = 'data-animation', dataContainer = 'data-container', dataPlacement = 'data-placement', dataDelay = 'data-delay', dataOffsetTop = 'data-offset-top', dataOffsetBottom = 'data-offset-bottom', // option keys backdrop = 'backdrop', keyboard = 'keyboard', delay = 'delay', content = 'content', target = 'target', interval = 'interval', pause = 'pause', animation = 'animation', placement = 'placement', container = 'container', // box model offsetTop = 'offsetTop', offsetBottom = 'offsetBottom', offsetLeft = 'offsetLeft', scrollTop = 'scrollTop', scrollLeft = 'scrollLeft', clientWidth = 'clientWidth', clientHeight = 'clientHeight', offsetWidth = 'offsetWidth', offsetHeight = 'offsetHeight', innerWidth = 'innerWidth', innerHeight = 'innerHeight', scrollHeight = 'scrollHeight', height = 'height', // aria ariaExpanded = 'aria-expanded', ariaHidden = 'aria-hidden', // event names clickEvent = 'click', hoverEvent = 'hover', keydownEvent = 'keydown', keyupEvent = 'keyup', resizeEvent = 'resize', scrollEvent = 'scroll', // originalEvents showEvent = 'show', shownEvent = 'shown', hideEvent = 'hide', hiddenEvent = 'hidden', closeEvent = 'close', closedEvent = 'closed', slidEvent = 'slid', slideEvent = 'slide', changeEvent = 'change', // other getAttribute = 'getAttribute', setAttribute = 'setAttribute', hasAttribute = 'hasAttribute', createElement = 'createElement', appendChild = 'appendChild', innerHTML = 'innerHTML', getElementsByTagName = 'getElementsByTagName', preventDefault = 'preventDefault', getBoundingClientRect = 'getBoundingClientRect', querySelectorAll = 'querySelectorAll', getElementsByCLASSNAME = 'getElementsByClassName', indexOf = 'indexOf', parentNode = 'parentNode', length = 'length', toLowerCase = 'toLowerCase', Transition = 'Transition', Webkit = 'Webkit', style = 'style', push = 'push', tabindex = 'tabindex', contains = 'contains', active = 'active', showClass = 'show', collapsing = 'collapsing', disabled = 'disabled', loading = 'loading', left = 'left', right = 'right', top = 'top', bottom = 'bottom', // tooltip / popover mouseHover = ('onmouseleave' in DOC) ? [ 'mouseenter', 'mouseleave'] : [ 'mouseover', 'mouseout' ], tipPositions = /\b(top|bottom|left|right)+/, // modal modalOverlay = 0, fixedTop = 'fixed-top', fixedBottom = 'fixed-bottom', // transitionEnd since 2.0.4 supportTransitions = Webkit+Transition in HTML[style] || Transition[toLowerCase]() in HTML[style], transitionEndEvent = Webkit+Transition in HTML[style] ? Webkit[toLowerCase]()+Transition+'End' : Transition[toLowerCase]()+'end', // set new focus element since 2.0.3 setFocus = function(element){ element.focus ? element.focus() : element.setActive(); }, // class manipulation, since 2.0.0 requires polyfill.js addClass = function(element,classNAME) { element.classList.add(classNAME); }, removeClass = function(element,classNAME) { element.classList.remove(classNAME); }, hasClass = function(element,classNAME){ // since 2.0.0 return element.classList[contains](classNAME); }, // selection methods getElementsByClassName = function(element,classNAME) { // returns Array return [].slice.call(element[getElementsByCLASSNAME]( classNAME )); }, queryElement = function (selector, parent) { var lookUp = parent ? parent : DOC; return typeof selector === 'object' ? selector : lookUp.querySelector(selector); }, getClosest = function (element, selector) { //element is the element and selector is for the closest parent element to find // source http://gomakethings.com/climbing-up-and-down-the-dom-tree-with-vanilla-javascript/ var firstChar = selector.charAt(0), selectorSubstring = selector.substr(1); if ( firstChar === '.' ) {// If selector is a class for ( ; element && element !== DOC; element = element[parentNode] ) { // Get closest match if ( queryElement(selector,element[parentNode]) !== null && hasClass(element,selectorSubstring) ) { return element; } } } else if ( firstChar === '#' ) { // If selector is an ID for ( ; element && element !== DOC; element = element[parentNode] ) { // Get closest match if ( element.id === selectorSubstring ) { return element; } } } return false; }, // event attach jQuery style / trigger since 1.2.0 on = function (element, event, handler) { element.addEventListener(event, handler, false); }, off = function(element, event, handler) { element.removeEventListener(event, handler, false); }, one = function (element, event, handler) { // one since 2.0.4 on(element, event, function handlerWrapper(e){ handler(e); off(element, event, handlerWrapper); }); }, emulateTransitionEnd = function(element,handler){ // emulateTransitionEnd since 2.0.4 if (supportTransitions) { one(element, transitionEndEvent, function(e){ handler(e); }); } else { handler(); } }, bootstrapCustomEvent = function (eventName, componentName, related) { var OriginalCustomEvent = new CustomEvent( eventName + '.bs.' + componentName); OriginalCustomEvent.relatedTarget = related; this.dispatchEvent(OriginalCustomEvent); }, // tooltip / popover stuff getScroll = function() { // also Affix and ScrollSpy uses it return { y : globalObject.pageYOffset || HTML[scrollTop], x : globalObject.pageXOffset || HTML[scrollLeft] } }, styleTip = function(link,element,position,parent) { // both popovers and tooltips (target,tooltip,placement,elementToAppendTo) var elementDimensions = { w : element[offsetWidth], h: element[offsetHeight] }, windowWidth = (HTML[clientWidth] || DOC[body][clientWidth]), windowHeight = (HTML[clientHeight] || DOC[body][clientHeight]), rect = link[getBoundingClientRect](), scroll = parent === DOC[body] ? getScroll() : { x: parent[offsetLeft] + parent[scrollLeft], y: parent[offsetTop] + parent[scrollTop] }, linkDimensions = { w: rect[right] - rect[left], h: rect[bottom] - rect[top] }, isPopover = hasClass(element,'popover'), topPosition, leftPosition, arrow = queryElement('.arrow',element), arrowTop, arrowLeft, arrowWidth, arrowHeight, halfTopExceed = rect[top] + linkDimensions.h/2 - elementDimensions.h/2 < 0, halfLeftExceed = rect[left] + linkDimensions.w/2 - elementDimensions.w/2 < 0, halfRightExceed = rect[left] + elementDimensions.w/2 + linkDimensions.w/2 >= windowWidth, halfBottomExceed = rect[top] + elementDimensions.h/2 + linkDimensions.h/2 >= windowHeight, topExceed = rect[top] - elementDimensions.h < 0, leftExceed = rect[left] - elementDimensions.w < 0, bottomExceed = rect[top] + elementDimensions.h + linkDimensions.h >= windowHeight, rightExceed = rect[left] + elementDimensions.w + linkDimensions.w >= windowWidth; // recompute position position = (position === left || position === right) && leftExceed && rightExceed ? top : position; // first, when both left and right limits are exceeded, we fall back to top|bottom position = position === top && topExceed ? bottom : position; position = position === bottom && bottomExceed ? top : position; position = position === left && leftExceed ? right : position; position = position === right && rightExceed ? left : position; // update tooltip/popover class element.className[indexOf](position) === -1 && (element.className = element.className.replace(tipPositions,position)); // we check the computed width & height and update here arrowWidth = arrow[offsetWidth]; arrowHeight = arrow[offsetHeight]; // apply styling to tooltip or popover if ( position === left || position === right ) { // secondary|side positions if ( position === left ) { // LEFT leftPosition = rect[left] + scroll.x - elementDimensions.w - ( isPopover ? arrowWidth : 0 ); } else { // RIGHT leftPosition = rect[left] + scroll.x + linkDimensions.w; } // adjust top and arrow if (halfTopExceed) { topPosition = rect[top] + scroll.y; arrowTop = linkDimensions.h/2 - arrowWidth; } else if (halfBottomExceed) { topPosition = rect[top] + scroll.y - elementDimensions.h + linkDimensions.h; arrowTop = elementDimensions.h - linkDimensions.h/2 - arrowWidth; } else { topPosition = rect[top] + scroll.y - elementDimensions.h/2 + linkDimensions.h/2; arrowTop = elementDimensions.h/2 - (isPopover ? arrowHeight*0.9 : arrowHeight/2); } } else if ( position === top || position === bottom ) { // primary|vertical positions if ( position === top) { // TOP topPosition = rect[top] + scroll.y - elementDimensions.h - ( isPopover ? arrowHeight : 0 ); } else { // BOTTOM topPosition = rect[top] + scroll.y + linkDimensions.h; } // adjust left | right and also the arrow if (halfLeftExceed) { leftPosition = 0; arrowLeft = rect[left] + linkDimensions.w/2 - arrowWidth; } else if (halfRightExceed) { leftPosition = windowWidth - elementDimensions.w*1.01; arrowLeft = elementDimensions.w - ( windowWidth - rect[left] ) + linkDimensions.w/2 - arrowWidth/2; } else { leftPosition = rect[left] + scroll.x - elementDimensions.w/2 + linkDimensions.w/2; arrowLeft = elementDimensions.w/2 - arrowWidth/2; } } // apply style to tooltip/popover and its arrow element[style][top] = topPosition + 'px'; element[style][left] = leftPosition + 'px'; arrowTop && (arrow[style][top] = arrowTop + 'px'); arrowLeft && (arrow[style][left] = arrowLeft + 'px'); }; BSN.version = '2.0.23'; /* Native Javascript for Bootstrap 4 | Alert -------------------------------------------*/ // ALERT DEFINITION // ================ var Alert = function( element ) { // initialization element element = queryElement(element); // bind, target alert, duration and stuff var self = this, component = 'alert', alert = getClosest(element,'.'+component), triggerHandler = function(){ hasClass(alert,'fade') ? emulateTransitionEnd(alert,transitionEndHandler) : transitionEndHandler(); }, // handlers clickHandler = function(e){ alert = getClosest(e[target],'.'+component); element = queryElement('['+dataDismiss+'="'+component+'"]',alert); element && alert && (element === e[target] || element[contains](e[target])) && self.close(); }, transitionEndHandler = function(){ bootstrapCustomEvent.call(alert, closedEvent, component); off(element, clickEvent, clickHandler); // detach it's listener alert[parentNode].removeChild(alert); }; // public method this.close = function() { if ( alert && element && hasClass(alert,showClass) ) { bootstrapCustomEvent.call(alert, closeEvent, component); removeClass(alert,showClass); alert && triggerHandler(); } }; // init if ( !(stringAlert in element ) ) { // prevent adding event handlers twice on(element, clickEvent, clickHandler); } element[stringAlert] = self; }; // ALERT DATA API // ============== supports[push]([stringAlert, Alert, '['+dataDismiss+'="alert"]']); /* Native Javascript for Bootstrap 4 | Button ---------------------------------------------*/ // BUTTON DEFINITION // =================== var Button = function( element ) { // initialization element element = queryElement(element); // constant var toggled = false, // toggled makes sure to prevent triggering twice the change.bs.button events // strings component = 'button', checked = 'checked', reset = 'reset', LABEL = 'LABEL', INPUT = 'INPUT', // private methods keyHandler = function(e){ var key = e.which || e.keyCode; key === 32 && e[target] === DOC.activeElement && toggle(e); }, preventScroll = function(e){ var key = e.which || e.keyCode; key === 32 && e[preventDefault](); }, toggle = function(e) { var label = e[target].tagName === LABEL ? e[target] : e[target][parentNode].tagName === LABEL ? e[target][parentNode] : null; // the .btn label if ( !label ) return; //react if a label or its immediate child is clicked var eventTarget = e[target], // the button itself, the target of the handler function labels = getElementsByClassName(eventTarget[parentNode],'btn'), // all the button group buttons input = label[getElementsByTagName](INPUT)[0]; if ( !input ) return; //return if no input found // manage the dom manipulation if ( input.type === 'checkbox' ) { //checkboxes if ( !input[checked] ) { addClass(label,active); input[getAttribute](checked); input[setAttribute](checked,checked); input[checked] = true; } else { removeClass(label,active); input[getAttribute](checked); input.removeAttribute(checked); input[checked] = false; } if (!toggled) { // prevent triggering the event twice toggled = true; bootstrapCustomEvent.call(input, changeEvent, component); //trigger the change for the input bootstrapCustomEvent.call(element, changeEvent, component); //trigger the change for the btn-group } } if ( input.type === 'radio' && !toggled ) { // radio buttons if ( !input[checked] ) { // don't trigger if already active addClass(label,active); input[setAttribute](checked,checked); input[checked] = true; bootstrapCustomEvent.call(input, changeEvent, component); //trigger the change for the input bootstrapCustomEvent.call(element, changeEvent, component); //trigger the change for the btn-group toggled = true; for (var i = 0, ll = labels[length]; i<ll; i++) { var otherLabel = labels[i], otherInput = otherLabel[getElementsByTagName](INPUT)[0]; if ( otherLabel !== label && hasClass(otherLabel,active) ) { removeClass(otherLabel,active); otherInput.removeAttribute(checked); otherInput[checked] = false; bootstrapCustomEvent.call(otherInput, changeEvent, component); // trigger the change } } } } setTimeout( function() { toggled = false; }, 50 ); }; // init if ( !( stringButton in element ) ) { // prevent adding event handlers twice on( element, clickEvent, toggle ); queryElement('['+tabindex+']',element) && on( element, keyupEvent, keyHandler ), on( element, keydownEvent, preventScroll ); } // activate items on load var labelsToACtivate = getElementsByClassName(element, 'btn'), lbll = labelsToACtivate[length]; for (var i=0; i<lbll; i++) { !hasClass(labelsToACtivate[i],active) && queryElement('input:checked',labelsToACtivate[i]) && addClass(labelsToACtivate[i],active); } element[stringButton] = this; }; // BUTTON DATA API // ================= supports[push]( [ stringButton, Button, '['+dataToggle+'="buttons"]' ] ); /* Native Javascript for Bootstrap 4 | Carousel ----------------------------------------------*/ // CAROUSEL DEFINITION // =================== var Carousel = function( element, options ) { // initialization element element = queryElement( element ); // set options options = options || {}; // DATA API var intervalAttribute = element[getAttribute](dataInterval), intervalOption = options[interval], intervalData = intervalAttribute === 'false' ? 0 : parseInt(intervalAttribute), pauseData = element[getAttribute](dataPause) === hoverEvent || false, keyboardData = element[getAttribute](dataKeyboard) === 'true' || false, // strings component = 'carousel', paused = 'paused', direction = 'direction', carouselItem = 'carousel-item', dataSlideTo = 'data-slide-to'; this[keyboard] = options[keyboard] === true || keyboardData; this[pause] = (options[pause] === hoverEvent || pauseData) ? hoverEvent : false; // false / hover this[interval] = typeof intervalOption === 'number' ? intervalOption : intervalData === 0 || intervalData === false ? 0 : 5000; // bootstrap carousel default interval // bind, event targets var self = this, index = element.index = 0, timer = element.timer = 0, isSliding = false, // isSliding prevents click event handlers when animation is running slides = getElementsByClassName(element,carouselItem), total = slides[length], slideDirection = this[direction] = left, leftArrow = getElementsByClassName(element,component+'-control-prev')[0], rightArrow = getElementsByClassName(element,component+'-control-next')[0], indicator = queryElement( '.'+component+'-indicators', element ), indicators = indicator && indicator[getElementsByTagName]( "LI" ) || []; // handlers var pauseHandler = function () { if ( self[interval] !==false && !hasClass(element,paused) ) { addClass(element,paused); !isSliding && clearInterval( timer ); } }, resumeHandler = function() { if ( self[interval] !== false && hasClass(element,paused) ) { removeClass(element,paused); !isSliding && clearInterval( timer ); !isSliding && self.cycle(); } }, indicatorHandler = function(e) { e[preventDefault](); if (isSliding) return; var eventTarget = e[target]; // event target | the current active item if ( eventTarget && !hasClass(eventTarget,active) && eventTarget[getAttribute](dataSlideTo) ) { index = parseInt( eventTarget[getAttribute](dataSlideTo), 10 ); } else { return false; } self.slideTo( index ); //Do the slide }, controlsHandler = function (e) { e[preventDefault](); if (isSliding) return; var eventTarget = e.currentTarget || e.srcElement; if ( eventTarget === rightArrow ) { index++; } else if ( eventTarget === leftArrow ) { index--; } self.slideTo( index ); //Do the slide }, keyHandler = function (e) { if (isSliding) return; switch (e.which) { case 39: index++; break; case 37: index--; break; default: return; } self.slideTo( index ); //Do the slide }, // private methods isElementInScrollRange = function () { var rect = element[getBoundingClientRect](), viewportHeight = globalObject[innerHeight] || HTML[clientHeight] return rect[top] <= viewportHeight && rect[bottom] >= 0; // bottom && top }, setActivePage = function( pageIndex ) { //indicators for ( var i = 0, icl = indicators[length]; i < icl; i++ ) { removeClass(indicators[i],active); } if (indicators[pageIndex]) addClass(indicators[pageIndex], active); }; // public methods this.cycle = function() { timer = setInterval(function() { isElementInScrollRange() && (index++, self.slideTo( index ) ); }, this[interval]); }; this.slideTo = function( next ) { if (isSliding) return; // when controled via methods, make sure to check again var activeItem = this.getActiveIndex(), // the current active orientation; // determine slideDirection first if ( (activeItem < next ) || (activeItem === 0 && next === total -1 ) ) { slideDirection = self[direction] = left; // next } else if ( (activeItem > next) || (activeItem === total - 1 && next === 0 ) ) { slideDirection = self[direction] = right; // prev } // find the right next index if ( next < 0 ) { next = total - 1; } else if ( next === total ){ next = 0; } // update index index = next; orientation = slideDirection === left ? 'next' : 'prev'; //determine type bootstrapCustomEvent.call(element, slideEvent, component, slides[next]); // here we go with the slide isSliding = true; clearInterval(timer); setActivePage( next ); if ( supportTransitions && hasClass(element,'slide') ) { addClass(slides[next],carouselItem +'-'+ orientation); slides[next][offsetWidth]; addClass(slides[next],carouselItem +'-'+ slideDirection); addClass(slides[activeItem],carouselItem +'-'+ slideDirection); one(slides[activeItem], transitionEndEvent, function(e) { var timeout = e[target] !== slides[activeItem] ? e.elapsedTime*1000 : 0; setTimeout(function(){ isSliding = false; addClass(slides[next],active); removeClass(slides[activeItem],active); removeClass(slides[next],carouselItem +'-'+ orientation); removeClass(slides[next],carouselItem +'-'+ slideDirection); removeClass(slides[activeItem],carouselItem +'-'+ slideDirection); bootstrapCustomEvent.call(element, slidEvent, component, slides[next]); if ( !DOC.hidden && self[interval] && !hasClass(element,paused) ) { self.cycle(); } },timeout+100); }); } else { addClass(slides[next],active); slides[next][offsetWidth]; removeClass(slides[activeItem],active); setTimeout(function() { isSliding = false; if ( self[interval] && !hasClass(element,paused) ) { self.cycle(); } bootstrapCustomEvent.call(element, slidEvent, component, slides[next]); }, 100 ); } }; this.getActiveIndex = function () { return slides[indexOf](getElementsByClassName(element,carouselItem+' active')[0]) || 0; }; // init if ( !(stringCarousel in element ) ) { // prevent adding event handlers twice if ( self[pause] && self[interval] ) { on( element, mouseHover[0], pauseHandler ); on( element, mouseHover[1], resumeHandler ); on( element, 'touchstart', pauseHandler ); on( element, 'touchend', resumeHandler ); } rightArrow && on( rightArrow, clickEvent, controlsHandler ); leftArrow && on( leftArrow, clickEvent, controlsHandler ); indicator && on( indicator, clickEvent, indicatorHandler ); self[keyboard] === true && on( globalObject, keydownEvent, keyHandler ); } if (self.getActiveIndex()<0) { slides[length] && addClass(slides[0],active); indicators[length] && setActivePage(0); } if ( self[interval] ){ self.cycle(); } element[stringCarousel] = self; }; // CAROUSEL DATA API // ================= supports[push]( [ stringCarousel, Carousel, '['+dataRide+'="carousel"]' ] ); /* Native Javascript for Bootstrap 4 | Collapse -----------------------------------------------*/ // COLLAPSE DEFINITION // =================== var Collapse = function( element, options ) { // initialization element element = queryElement(element); // set options options = options || {}; // event targets and constants var accordion = null, collapse = null, self = this, isAnimating = false, // when true it will prevent click handlers accordionData = element[getAttribute]('data-parent'), // component strings component = 'collapse', collapsed = 'collapsed', // private methods openAction = function(collapseElement,toggle) { bootstrapCustomEvent.call(collapseElement, showEvent, component); isAnimating = true; addClass(collapseElement,collapsing); removeClass(collapseElement,component); collapseElement[style][height] = collapseElement[scrollHeight] + 'px'; emulateTransitionEnd(collapseElement, function() { isAnimating = false; collapseElement[setAttribute](ariaExpanded,'true'); toggle[setAttribute](ariaExpanded,'true'); removeClass(collapseElement,collapsing); addClass(collapseElement, component); addClass(collapseElement,showClass); collapseElement[style][height] = ''; bootstrapCustomEvent.call(collapseElement, shownEvent, component); }); }, closeAction = function(collapseElement,toggle) { bootstrapCustomEvent.call(collapseElement, hideEvent, component); isAnimating = true; collapseElement[style][height] = collapseElement[scrollHeight] + 'px'; // set height first removeClass(collapseElement,component); removeClass(collapseElement,showClass); addClass(collapseElement,collapsing); collapseElement[offsetWidth]; // force reflow to enable transition collapseElement[style][height] = '0px'; emulateTransitionEnd(collapseElement, function() { isAnimating = false; collapseElement[setAttribute](ariaExpanded,'false'); toggle[setAttribute](ariaExpanded,'false'); removeClass(collapseElement,collapsing); addClass(collapseElement,component); collapseElement[style][height] = ''; bootstrapCustomEvent.call(collapseElement, hiddenEvent, component); }); }, getTarget = function() { var href = element.href && element[getAttribute]('href'), parent = element[getAttribute](dataTarget), id = href || ( parent && parent.charAt(0) === '#' ) && parent; return id && queryElement(id); }; // public methods this.toggle = function(e) { e[preventDefault](); if (isAnimating) return; if (!hasClass(collapse,showClass)) { self.show(); } else { self.hide(); } }; this.hide = function() { closeAction(collapse,element); addClass(element,collapsed); }; this.show = function() { if ( accordion ) { var activeCollapse = queryElement('.'+component+'.'+showClass,accordion), toggle = activeCollapse && (queryElement('['+dataToggle+'="'+component+'"]['+dataTarget+'="#'+activeCollapse.id+'"]',accordion) || queryElement('['+dataToggle+'="'+component+'"][href="#'+activeCollapse.id+'"]',accordion) ), correspondingCollapse = toggle && (toggle[getAttribute](dataTarget) || toggle.href); if ( activeCollapse && toggle && activeCollapse !== collapse ) { closeAction(activeCollapse,toggle); if ( correspondingCollapse.split('#')[1] !== collapse.id ) { addClass(toggle,collapsed); } else { removeClass(toggle,collapsed); } } } openAction(collapse,element); removeClass(element,collapsed); }; // init if ( !(stringCollapse in element ) ) { // prevent adding event handlers twice on(element, clickEvent, self.toggle); } collapse = getTarget(); accordion = queryElement(options.parent) || accordionData && getClosest(element, accordionData); element[stringCollapse] = self; }; // COLLAPSE DATA API // ================= supports[push]( [ stringCollapse, Collapse, '['+dataToggle+'="collapse"]' ] ); /* Native Javascript for Bootstrap 4 | Dropdown ----------------------------------------------*/ // DROPDOWN DEFINITION // =================== var Dropdown = function( element, option ) { // initialization element element = queryElement(element); // set option this.persist = option === true || element[getAttribute]('data-persist') === 'true' || false; // constants, event targets, strings var self = this, children = 'children', parent = element[parentNode], component = 'dropdown', open = 'open', relatedTarget = null, menu = queryElement('.dropdown-menu', parent), menuItems = (function(){ var set = menu[children], newSet = []; for ( var i=0; i<set[length]; i++ ){ set[i][children][length] && (set[i][children][0].tagName === 'A' && newSet[push](set[i][children][0])); set[i].tagName === 'A' && newSet[push](set[i]); } return newSet; })(), // preventDefault on empty anchor links preventEmptyAnchor = function(anchor){ (anchor.href && anchor.href.slice(-1) === '#' || anchor[parentNode] && anchor[parentNode].href && anchor[parentNode].href.slice(-1) === '#') && this[preventDefault](); }, // toggle dismissible events toggleDismiss = function(){ var type = element[open] ? on : off; type(DOC, clickEvent, dismissHandler); type(DOC, keydownEvent, preventScroll); type(DOC, keyupEvent, keyHandler); }, // handlers dismissHandler = function(e) { var eventTarget = e[target], hasData = eventTarget && (stringDropdown in eventTarget || stringDropdown in eventTarget[parentNode]); if ( (eventTarget === menu || menu[contains](eventTarget)) && (self.persist || hasData) ) { return; } else { relatedTarget = eventTarget === element || element[contains](eventTarget) ? element : null; hide(); } preventEmptyAnchor.call(e,eventTarget); }, clickHandler = function(e) { relatedTarget = element; show(); preventEmptyAnchor.call(e,e[target]); }, preventScroll = function(e){ var key = e.which || e.keyCode; if( key === 38 || key === 40 ) { e[preventDefault](); } }, keyHandler = function(e){ var key = e.which || e.keyCode, activeItem = DOC.activeElement, idx = menuItems[indexOf](activeItem), isSameElement = activeItem === element, isInsideMenu = menu[contains](activeItem), isMenuItem = activeItem[parentNode] === menu || activeItem[parentNode][parentNode] === menu; if ( isMenuItem || isSameElement ) { // navigate up | down idx = isSameElement ? 0 : key === 38 ? (idx>1?idx-1:0) : key === 40 ? (idx<menuItems[length]-1?idx+1:idx) : idx; menuItems[idx] && setFocus(menuItems[idx]); } if ( (menuItems[length] && isMenuItem // menu has items || !menuItems[length] && (isInsideMenu || isSameElement) // menu might be a form || !isInsideMenu ) // or the focused element is not in the menu at all && element[open] && key === 27 // menu must be open ) { self.toggle(); relatedTarget = null; } }, // private methods show = function() { bootstrapCustomEvent.call(parent, showEvent, component, relatedTarget); addClass(menu,showClass); addClass(parent,showClass); menu[setAttribute](ariaExpanded,true); bootstrapCustomEvent.call(parent, shownEvent, component, relatedTarget); element[open] = true; off(element, clickEvent, clickHandler); setTimeout(function(){ setFocus( menu[getElementsByTagName]('INPUT')[0] || element ); // focus the first input item | element toggleDismiss(); },1); }, hide = function() { bootstrapCustomEvent.call(parent, hideEvent, component, relatedTarget); removeClass(menu,showClass); removeClass(parent,showClass); menu[setAttribute](ariaExpanded,false); bootstrapCustomEvent.call(parent, hiddenEvent, component, relatedTarget); element[open] = false; toggleDismiss(); setFocus(element); setTimeout(function(){ on(element, clickEvent, clickHandler); },1); }; // set initial state to closed element[open] = false; // public methods this.toggle = function() { if (hasClass(parent,showClass) && element[open]) { hide(); } else { show(); } }; // init if ( !(stringDropdown in element) ) { // prevent adding event handlers twice !tabindex in menu && menu[setAttribute](tabindex, '0'); // Fix onblur on Chrome | Safari on(element, clickEvent, clickHandler); } element[stringDropdown] = self; }; // DROPDOWN DATA API // ================= supports[push]( [stringDropdown, Dropdown, '['+dataToggle+'="dropdown"]'] ); /* Native Javascript for Bootstrap 4 | Modal -------------------------------------------*/ // MODAL DEFINITION // =============== var Modal = function(element, options) { // element can be the modal/triggering button // the modal (both JavaScript / DATA API init) / triggering button element (DATA API) element = queryElement(element); // determine modal, triggering element var btnCheck = element[getAttribute](dataTarget)||element[getAttribute]('href'), checkModal = queryElement( btnCheck ), modal = hasClass(element,'modal') ? element : checkModal, // strings component = 'modal', staticString = 'static', paddingLeft = 'paddingLeft', paddingRight = 'paddingRight', modalBackdropString = 'modal-backdrop'; if ( hasClass(element,'modal') ) { element = null; } // modal is now independent of it's triggering element if ( !modal ) { return; } // invalidate // set options options = options || {}; this[keyboard] = options[keyboard] === false || modal[getAttribute](dataKeyboard) === 'false' ? false : true; this[backdrop] = options[backdrop] === staticString || modal[getAttribute](databackdrop) === staticString ? staticString : true; this[backdrop] = options[backdrop] === false || modal[getAttribute](databackdrop) === 'false' ? false : this[backdrop]; this[content] = options[content]; // JavaScript only // bind, constants, event targets and other vars var self = this, relatedTarget = null, bodyIsOverflowing, modalIsOverflowing, scrollbarWidth, overlay, // also find fixed-top / fixed-bottom items fixedItems = getElementsByClassName(HTML,fixedTop).concat(getElementsByClassName(HTML,fixedBottom)), // private methods getWindowWidth = function() { var htmlRect = HTML[getBoundingClientRect](); return globalObject[innerWidth] || (htmlRect[right] - Math.abs(htmlRect[left])); }, setScrollbar = function () { var bodyStyle = globalObject.getComputedStyle(DOC[body]), bodyPad = parseInt((bodyStyle[paddingRight]), 10), itemPad; if (bodyIsOverflowing) { DOC[body][style][paddingRight] = (bodyPad + scrollbarWidth) + 'px'; if (fixedItems[length]){ for (var i = 0; i < fixedItems[length]; i++) { itemPad = globalObject.getComputedStyle(fixedItems[i])[paddingRight]; fixedItems[i][style][paddingRight] = ( parseInt(itemPad) + scrollbarWidth) + 'px'; } } } }, resetScrollbar = function () { DOC[body][style][paddingRight] = ''; if (fixedItems[length]){ for (var i = 0; i < fixedItems[length]; i++) { fixedItems[i][style][paddingRight] = ''; } } }, measureScrollbar = function () { // thx walsh var scrollDiv = DOC[createElement]('div'), scrollBarWidth; scrollDiv.className = component+'-scrollbar-measure'; // this is here to stay DOC[body][appendChild](scrollDiv); scrollBarWidth = scrollDiv[offsetWidth] - scrollDiv[clientWidth]; DOC[body].removeChild(scrollDiv); return scrollBarWidth; }, checkScrollbar = function () { bodyIsOverflowing = DOC[body][clientWidth] < getWindowWidth(); modalIsOverflowing = modal[scrollHeight] > HTML[clientHeight]; scrollbarWidth = measureScrollbar(); }, adjustDialog = function () { modal[style][paddingLeft] = !bodyIsOverflowing && modalIsOverflowing ? scrollbarWidth + 'px' : ''; modal[style][paddingRight] = bodyIsOverflowing && !modalIsOverflowing ? scrollbarWidth + 'px' : ''; }, resetAdjustments = function () { modal[style][paddingLeft] = ''; modal[style][paddingRight] = ''; }, createOverlay = function() { modalOverlay = 1; var newOverlay = DOC[createElement]('div'); overlay = queryElement('.'+modalBackdropString); if ( overlay === null ) { newOverlay[setAttribute]('class',modalBackdropString+' fade'); overlay = newOverlay; DOC[body][appendChild](overlay); } }, removeOverlay = function() { overlay = queryElement('.'+modalBackdropString); if ( overlay && overlay !== null && typeof overlay === 'object' ) { modalOverlay = 0; DOC[body].removeChild(overlay); overlay = null; } bootstrapCustomEvent.call(modal, hiddenEvent, component); }, keydownHandlerToggle = function() { if (hasClass(modal,showClass)) { on(DOC, keydownEvent, keyHandler); } else { off(DOC, keydownEvent, keyHandler); } }, resizeHandlerToggle = function() { if (hasClass(modal,showClass)) { on(globalObject, resizeEvent, self.update); } else { off(globalObject, resizeEvent, self.update); } }, dismissHandlerToggle = function() { if (hasClass(modal,showClass)) { on(modal, clickEvent, dismissHandler); } else { off(modal, clickEvent, dismissHandler); } }, // triggers triggerShow = function() { setFocus(modal); bootstrapCustomEvent.call(modal, shownEvent, component, relatedTarget); }, triggerHide = function() { modal[style].display = ''; element && (setFocus(element)); (function(){ if (!getElementsByClassName(DOC,component+' '+showClass)[0]) { resetAdjustments(); resetScrollbar(); removeClass(DOC[body],component+'-open'); overlay && hasClass(overlay,'fade') ? (removeClass(overlay,showClass), emulateTransitionEnd(overlay,removeOverlay)) : removeOverlay(); resizeHandlerToggle(); dismissHandlerToggle(); keydownHandlerToggle(); } }()); }, // handlers clickHandler = function(e) { var clickTarget = e[target]; clickTarget = clickTarget[hasAttribute](dataTarget) || clickTarget[hasAttribute]('href') ? clickTarget : clickTarget[parentNode]; if ( clickTarget === element && !hasClass(modal,showClass) ) { modal.modalTrigger = element; relatedTarget = element; self.show(); e[preventDefault](); } }, keyHandler = function(e) { if (self[keyboard] && e.which == 27 && hasClass(modal,showClass)) { self.hide(); } }, dismissHandler = function(e) { var clickTarget = e[target]; if ( hasClass(modal,showClass) && (clickTarget[parentNode][getAttribute](dataDismiss) === component || clickTarget[getAttribute](dataDismiss) === component || (clickTarget === modal && self[backdrop] !== staticString) ) ) { self.hide(); relatedTarget = null; e[preventDefault](); } }; // public methods this.toggle = function() { if ( hasClass(modal,showClass) ) {this.hide();} else {this.show();} }; this.show = function() { bootstrapCustomEvent.call(modal, showEvent, component, relatedTarget); // we elegantly hide any opened modal var currentOpen = getElementsByClassName(DOC,component+' '+showClass)[0]; currentOpen && currentOpen !== modal && currentOpen.modalTrigger[stringModal].hide(); if ( this[backdrop] ) { !modalOverlay && createOverlay(); } if ( overlay && modalOverlay && !hasClass(overlay,showClass)) { overlay[offsetWidth]; // force reflow to enable trasition addClass(overlay, showClass); } setTimeout( function() { modal[style].display = 'block'; checkScrollbar(); setScrollbar(); adjustDialog(); addClass(DOC[body],component+'-open'); addClass(modal,showClass); modal[setAttribute](ariaHidden, false); resizeHandlerToggle(); dismissHandlerToggle(); keydownHandlerToggle(); hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerShow) : triggerShow(); }, supportTransitions ? 150 : 0); }; this.hide = function() { bootstrapCustomEvent.call(modal, hideEvent, component); overlay = queryElement('.'+modalBackdropString); removeClass(modal,showClass); modal[setAttribute](ariaHidden, true); (function(){ hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerHide) : triggerHide(); }()); }; this.setContent = function( content ) { queryElement('.'+component+'-content',modal)[innerHTML] = content; }; this.update = function() { if (hasClass(modal,showClass)) { checkScrollbar(); setScrollbar(); adjustDialog(); } }; // init // prevent adding event handlers over and over // modal is independent of a triggering element if ( !!element && !(stringModal in element) ) { on(element, clickEvent, clickHandler); } if ( !!self[content] ) { self.setContent( self[content] ); } !!element && (element[stringModal] = self); }; // DATA API supports[push]( [ stringModal, Modal, '['+dataToggle+'="modal"]' ] ); /* Native Javascript for Bootstrap 4 | Popover ----------------------------------------------*/ // POPOVER DEFINITION // ================== var Popover = function( element, options ) { // initialization element element = queryElement(element); // set options options = options || {}; // DATA API var triggerData = element[getAttribute](dataTrigger), // click / hover / focus animationData = element[getAttribute](dataAnimation), // true / false placementData = element[getAttribute](dataPlacement), dismissibleData = element[getAttribute](dataDismissible), delayData = element[getAttribute](dataDelay), containerData = element[getAttribute](dataContainer), // internal strings component = 'popover', template = 'template', trigger = 'trigger', classString = 'class', div = 'div', fade = 'fade', content = 'content', dataContent = 'data-content', dismissible = 'dismissible', closeBtn = '<button type="button" class="close">×</button>', // check container containerElement = queryElement(options[container]), containerDataElement = queryElement(containerData), // maybe the element is inside a modal modal = getClosest(element,'.modal'), // maybe the element is inside a fixed navbar navbarFixedTop = getClosest(element,'.'+fixedTop), navbarFixedBottom = getClosest(element,'.'+fixedBottom); // set instance options this[template] = options[template] ? options[template] : null; // JavaScript only this[trigger] = options[trigger] ? options[trigger] : triggerData || hoverEvent; this[animation] = options[animation] && options[animation] !== fade ? options[animation] : animationData || fade; this[placement] = options[placement] ? options[placement] : placementData || top; this[delay] = parseInt(options[delay] || delayData) || 200; this[dismissible] = options[dismissible] || dismissibleData === 'true' ? true : false; this[container] = containerElement ? containerElement : containerDataElement ? containerDataElement : navbarFixedTop ? navbarFixedTop : navbarFixedBottom ? navbarFixedBottom : modal ? modal : DOC[body]; // bind, content var self = this, titleString = element[getAttribute](dataTitle) || null, contentString = element[getAttribute](dataContent) || null; if ( !contentString && !this[template] ) return; // invalidate // constants, vars var popover = null, timer = 0, placementSetting = this[placement], // handlers dismissibleHandler = function(e) { if (popover !== null && e[target] === queryElement('.close',popover)) { self.hide(); } }, // private methods removePopover = function() { self[container].removeChild(popover); timer = null; popover = null; }, createPopover = function() { titleString = element[getAttribute](dataTitle); // check content again contentString = element[getAttribute](dataContent); popover = DOC[createElement](div); // popover arrow var popoverArrow = DOC[createElement](div); popoverArrow[setAttribute](classString,'arrow'); popover[appendChild](popoverArrow); if ( contentString !== null && self[template] === null ) { //create the popover from data attributes popover[setAttribute]('role','tooltip'); if (titleString !== null) { var popoverTitle = DOC[createElement]('h3'); popoverTitle[setAttribute](classString,component+'-header'); popoverTitle[innerHTML] = self[dismissible] ? titleString + closeBtn : titleString; popover[appendChild](popoverTitle); } //set popover content var popoverContent = DOC[createElement](div); popoverContent[setAttribute](classString,component+'-body'); popoverContent[innerHTML] = self[dismissible] && titleString === null ? contentString + closeBtn : contentString; popover[appendChild](popoverContent); } else { // or create the popover from template var popoverTemplate = DOC[createElement](div); popoverTemplate[innerHTML] = self[template]; popover[innerHTML] = popoverTemplate.firstChild[innerHTML]; } //append to the container self[container][appendChild](popover); popover[style].display = 'block'; popover[setAttribute](classString, component+ ' bs-' + component+'-'+placementSetting + ' ' + self[animation]); }, showPopover = function () { !hasClass(popover,showClass) && ( addClass(popover,showClass) ); }, updatePopover = function() { styleTip(element,popover,placementSetting,self[container]); }, // event toggle dismissHandlerToggle = function(type){ if (clickEvent == self[trigger] || 'focus' == self[trigger]) { !self[dismissible] && type( element, 'blur', self.hide ); } self[dismissible] && type( DOC, clickEvent, dismissibleHandler ); type( globalObject, resizeEvent, self.hide ); }, // triggers showTrigger = function() { dismissHandlerToggle(on); bootstrapCustomEvent.call(element, shownEvent, component); }, hideTrigger = function() { dismissHandlerToggle(off); removePopover(); bootstrapCustomEvent.call(element, hiddenEvent, component); }; // public methods / handlers this.toggle = function() { if (popover === null) { self.show(); } else { self.hide(); } }; this.show = function() { clearTimeout(timer); timer = setTimeout( function() { if (popover === null) { placementSetting = self[placement]; // we reset placement in all cases createPopover(); updatePopover(); showPopover(); bootstrapCustomEvent.call(element, showEvent, component); !!self[animation] ? emulateTransitionEnd(popover, showTrigger) : showTrigger(); } }, 20 ); }; this.hide = function() { clearTimeout(timer); timer = setTimeout( function() { if (popover && popover !== null && hasClass(popover,showClass)) { bootstrapCustomEvent.call(element, hideEvent, component); removeClass(popover,showClass); !!self[animation] ? emulateTransitionEnd(popover, hideTrigger) : hideTrigger(); } }, self[delay] ); }; // init if ( !(stringPopover in element) ) { // prevent adding event handlers twice if (self[trigger] === hoverEvent) { on( element, mouseHover[0], self.show ); if (!self[dismissible]) { on( element, mouseHover[1], self.hide ); } } else if (clickEvent == self[trigger] || 'focus' == self[trigger]) { on( element, self[trigger], self.toggle ); } } element[stringPopover] = self; }; // POPOVER DATA API // ================ supports[push]( [ stringPopover, Popover, '['+dataToggle+'="popover"]' ] ); /* Native Javascript for Bootstrap 4 | ScrollSpy -----------------------------------------------*/ // SCROLLSPY DEFINITION // ==================== var ScrollSpy = function(element, options) { // initialization element, the element we spy on element = queryElement(element); // DATA API var targetData = queryElement(element[getAttribute](dataTarget)), offsetData = element[getAttribute]('data-offset'); // set options options = options || {}; if ( !options[target] && !targetData ) { return; } // invalidate // event targets, constants var self = this, spyTarget = options[target] && queryElement(options[target]) || targetData, links = spyTarget && spyTarget[getElementsByTagName]('A'), offset = parseInt(offsetData || options['offset']) || 10, items = [], targetItems = [], scrollOffset, scrollTarget = element[offsetHeight] < element[scrollHeight] ? element : globalObject, // determine which is the real scrollTarget isWindow = scrollTarget === globalObject; // populate items and targets for (var i=0, il=links[length]; i<il; i++) { var href = links[i][getAttribute]('href'), targetItem = href && href.charAt(0) === '#' && href.slice(-1) !== '#' && queryElement(href); if ( !!targetItem ) { items[push](links[i]); targetItems[push](targetItem); } } // private methods var updateItem = function(index) { var item = items[index], targetItem = targetItems[index], // the menu item targets this element dropdown = item[parentNode][parentNode], dropdownLink = hasClass(dropdown,'dropdown') && dropdown[getElementsByTagName]('A')[0], targetRect = isWindow && targetItem[getBoundingClientRect](), isActive = hasClass(item,active) || false, topEdge = (isWindow ? targetRect[top] + scrollOffset : targetItem[offsetTop]) - offset, bottomEdge = isWindow ? targetRect[bottom] + scrollOffset - offset : targetItems[index+1] ? targetItems[index+1][offsetTop] - offset : element[scrollHeight], inside = scrollOffset >= topEdge && bottomEdge > scrollOffset; if ( !isActive && inside ) { if ( !hasClass(item,active) ) { addClass(item,active); if (dropdownLink && !hasClass(dropdownLink,active) ) { addClass(dropdownLink,active); } bootstrapCustomEvent.call(element, 'activate', 'scrollspy', items[index]); } } else if ( !inside ) { if ( hasClass(item,active) ) { removeClass(item,active); if (dropdownLink && hasClass(dropdownLink,active) && !getElementsByClassName(item[parentNode],active).length ) { removeClass(dropdownLink,active); } } } else if ( !inside && !isActive || isActive && inside ) { return; } }, updateItems = function(){ scrollOffset = isWindow ? getScroll().y : element[scrollTop]; for (var index=0, itl=items[length]; index<itl; index++) { updateItem(index) } }; // public method this.refresh = function () { updateItems(); } // init if ( !(stringScrollSpy in element) ) { // prevent adding event handlers twice on( scrollTarget, scrollEvent, self.refresh ); on( globalObject, resizeEvent, self.refresh ); } self.refresh(); element[stringScrollSpy] = self; }; // SCROLLSPY DATA API // ================== supports[push]( [ stringScrollSpy, ScrollSpy, '['+dataSpy+'="scroll"]' ] ); /* Native Javascript for Bootstrap 4 | Tab -----------------------------------------*/ // TAB DEFINITION // ============== var Tab = function( element, options ) { // initialization element element = queryElement(element); // DATA API var heightData = element[getAttribute](dataHeight), // strings component = 'tab', height = 'height', float = 'float', isAnimating = 'isAnimating'; // set options options = options || {}; this[height] = supportTransitions ? (options[height] || heightData === 'true') : false; // bind, event targets var self = this, next, tabs = getClosest(element,'.nav'), tabsContentContainer = false, dropdown = tabs && queryElement('.dropdown-toggle',tabs), activeTab, activeContent, nextContent, containerHeight, equalContents, nextHeight, // trigger triggerEnd = function(){ tabsContentContainer[style][height] = ''; removeClass(tabsContentContainer,collapsing); tabs[isAnimating] = false; }, triggerShow = function() { if (tabsContentContainer) { // height animation if ( equalContents ) { triggerEnd(); } else { setTimeout(function(){ // enables height animation tabsContentContainer[style][height] = nextHeight + 'px'; // height animation tabsContentContainer[offsetWidth]; emulateTransitionEnd(tabsContentContainer, triggerEnd); },1); } } else { tabs[isAnimating] = false; } bootstrapCustomEvent.call(next, shownEvent, component, activeTab); }, triggerHide = function() { if (tabsContentContainer) { activeContent[style][float] = left; nextContent[style][float] = left; containerHeight = activeContent[scrollHeight]; } addClass(nextContent,active); bootstrapCustomEvent.call(next, showEvent, component, activeTab); removeClass(activeContent,active); bootstrapCustomEvent.call(activeTab, hiddenEvent, component, next); if (tabsContentContainer) { nextHeight = nextContent[scrollHeight]; equalContents = nextHeight === containerHeight; addClass(tabsContentContainer,collapsing); tabsContentContainer[style][height] = containerHeight + 'px'; // height animation tabsContentContainer[offsetHeight]; activeContent[style][float] = ''; nextContent[style][float] = ''; } if ( hasClass(nextContent, 'fade') ) { setTimeout(function(){ addClass(nextContent,showClass); emulateTransitionEnd(nextContent,triggerShow); },20); } else { triggerShow(); } }; if (!tabs) return; // invalidate // set default animation state tabs[isAnimating] = false; // private methods var getActiveTab = function() { var activeTabs = getElementsByClassName(tabs,active), activeTab; if ( activeTabs[length] === 1 && !hasClass(activeTabs[0][parentNode],'dropdown') ) { activeTab = activeTabs[0]; } else if ( activeTabs[length] > 1 ) { activeTab = activeTabs[activeTabs[length]-1]; } return activeTab; }, getActiveContent = function() { return queryElement(getActiveTab()[getAttribute]('href')); }, // handler clickHandler = function(e) { var href = e[target][getAttribute]('href'); e[preventDefault](); next = e[target][getAttribute](dataToggle) === component || (href && href.charAt(0) === '#') ? e[target] : e[target][parentNode]; // allow for child elements like icons to use the handler !tabs[isAnimating] && !hasClass(next,active) && self.show(); }; // public method this.show = function() { // the tab we clicked is now the next tab next = next || element; nextContent = queryElement(next[getAttribute]('href')); //this is the actual object, the next tab content to activate activeTab = getActiveTab(); activeContent = getActiveContent(); tabs[isAnimating] = true; removeClass(activeTab,active); addClass(next,active); if ( dropdown ) { if ( !hasClass(element[parentNode],'dropdown-menu') ) { if (hasClass(dropdown,active)) removeClass(dropdown,active); } else { if (!hasClass(dropdown,active)) addClass(dropdown,active); } } bootstrapCustomEvent.call(activeTab, hideEvent, component, next); if (hasClass(activeContent, 'fade')) { removeClass(activeContent,showClass); emulateTransitionEnd(activeContent, triggerHide); } else { triggerHide(); } }; // init if ( !(stringTab in element) ) { // prevent adding event handlers twice on(element, clickEvent, clickHandler); } if (self[height]) { tabsContentContainer = getActiveContent()[parentNode]; } element[stringTab] = self; }; // TAB DATA API // ============ supports[push]( [ stringTab, Tab, '['+dataToggle+'="tab"]' ] ); /* Native Javascript for Bootstrap 4 | Tooltip ---------------------------------------------*/ // TOOLTIP DEFINITION // ================== var Tooltip = function( element,options ) { // initialization element element = queryElement(element); // set options options = options || {}; // DATA API var animationData = element[getAttribute](dataAnimation), placementData = element[getAttribute](dataPlacement), delayData = element[getAttribute](dataDelay), containerData = element[getAttribute](dataContainer), // strings component = 'tooltip', classString = 'class', title = 'title', fade = 'fade', div = 'div', // check container containerElement = queryElement(options[container]), containerDataElement = queryElement(containerData), // maybe the element is inside a modal modal = getClosest(element,'.modal'), // maybe the element is inside a fixed navbar navbarFixedTop = getClosest(element,'.'+fixedTop), navbarFixedBottom = getClosest(element,'.'+fixedBottom); // set instance options this[animation] = options[animation] && options[animation] !== fade ? options[animation] : animationData || fade; this[placement] = options[placement] ? options[placement] : placementData || top; this[delay] = parseInt(options[delay] || delayData) || 200; this[container] = containerElement ? containerElement : containerDataElement ? containerDataElement : navbarFixedTop ? navbarFixedTop : navbarFixedBottom ? navbarFixedBottom : modal ? modal : DOC[body]; // bind, event targets, title and constants var self = this, timer = 0, placementSetting = this[placement], tooltip = null, titleString = element[getAttribute](title) || element[getAttribute](dataTitle) || element[getAttribute](dataOriginalTitle); if ( !titleString || titleString == "" ) return; // invalidate // private methods var removeToolTip = function() { self[container].removeChild(tooltip); tooltip = null; timer = null; }, createToolTip = function() { titleString = element[getAttribute](title) || element[getAttribute](dataTitle) || element[getAttribute](dataOriginalTitle); // read the title again if ( !titleString || titleString == "" ) return false; // invalidate tooltip = DOC[createElement](div); tooltip[setAttribute]('role',component); // tooltip arrow var tooltipArrow = DOC[createElement](div); tooltipArrow[setAttribute](classString,'arrow'); tooltip[appendChild](tooltipArrow); var tooltipInner = DOC[createElement](div); tooltipInner[setAttribute](classString,component+'-inner'); tooltip[appendChild](tooltipInner); tooltipInner[innerHTML] = titleString; self[container][appendChild](tooltip); tooltip[setAttribute](classString, component + ' bs-' + component+'-'+placementSetting + ' ' + self[animation]); }, updateTooltip = function () { styleTip(element,tooltip,placementSetting,self[container]); }, showTooltip = function () { !hasClass(tooltip,showClass) && ( addClass(tooltip,showClass) ); }, // triggers showTrigger = function() { on( globalObject, resizeEvent, self.hide ); bootstrapCustomEvent.call(element, shownEvent, component); }, hideTrigger = function() { off( globalObject, resizeEvent, self.hide ); removeToolTip(); bootstrapCustomEvent.call(element, hiddenEvent, component); }; // public methods this.show = function() { clearTimeout(timer); timer = setTimeout( function() { if (tooltip === null) { placementSetting = self[placement]; // we reset placement in all cases if(createToolTip() == false) return; updateTooltip(); showTooltip(); bootstrapCustomEvent.call(element, showEvent, component); !!self[animation] ? emulateTransitionEnd(tooltip, showTrigger) : showTrigger(); } }, 20 ); }; this.hide = function() { clearTimeout(timer); timer = setTimeout( function() { if (tooltip && hasClass(tooltip,showClass)) { bootstrapCustomEvent.call(element, hideEvent, component); removeClass(tooltip,showClass); !!self[animation] ? emulateTransitionEnd(tooltip, hideTrigger) : hideTrigger(); } }, self[delay]); }; this.toggle = function() { if (!tooltip) { self.show(); } else { self.hide(); } }; // init if ( !(stringTooltip in element) ) { // prevent adding event handlers twice element[setAttribute](dataOriginalTitle,titleString); element.removeAttribute(title); on(element, mouseHover[0], self.show); on(element, mouseHover[1], self.hide); } element[stringTooltip] = self; }; // TOOLTIP DATA API // ================= supports[push]( [ stringTooltip, Tooltip, '['+dataToggle+'="tooltip"]' ] ); /* Native Javascript for Bootstrap 4 | Initialize Data API --------------------------------------------------------*/ var initializeDataAPI = function( constructor, collection ){ for (var i=0, l=collection[length]; i<l; i++) { new constructor(collection[i]); } }, initCallback = BSN.initCallback = function(lookUp){ lookUp = lookUp || DOC; for (var i=0, l=supports[length]; i<l; i++) { initializeDataAPI( supports[i][1], lookUp[querySelectorAll] (supports[i][2]) ); } }; // bulk initialize all components DOC[body] ? initCallback() : on( DOC, 'DOMContentLoaded', function(){ initCallback(); } ); return { Alert: Alert, Button: Button, Carousel: Carousel, Collapse: Collapse, Dropdown: Dropdown, Modal: Modal, Popover: Popover, ScrollSpy: ScrollSpy, Tab: Tab, Tooltip: Tooltip }; }));