├── favicon-16x16.ico ├── sample └── serotonin.png ├── css ├── main.css └── offcanvas.css ├── js ├── ie10-viewport-bug-workaround.js ├── main.js ├── polyfill.js └── bootstrap-native-v4.js ├── README.md └── index.html /favicon-16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pietroppeter/HTML5-Image-Display/main/favicon-16x16.ico -------------------------------------------------------------------------------- /sample/serotonin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pietroppeter/HTML5-Image-Display/main/sample/serotonin.png -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | html,body { 2 | padding:0; 3 | margin:0; 4 | } 5 | body { 6 | padding: 0.25rem 0.5rem; 7 | height:100vh; 8 | width:calc(100vw - 1.0rem); 9 | } 10 | input[type=file] { 11 | display:none; 12 | } 13 | .table td, .table th { 14 | padding: 0.25rem; 15 | } 16 | #canvasDisplay { 17 | height: calc(100% - 3.0rem); 18 | } -------------------------------------------------------------------------------- /js/ie10-viewport-bug-workaround.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * IE10 viewport hack for Surface/desktop Windows 8 bug 3 | * Copyright 2014-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | !function() { 7 | "use strict" 8 | if (navigator.userAgent.match(/IEMobile\/10\.0/)) { 9 | var e = document.createElement("style") 10 | e.appendChild(document.createTextNode("@-ms-viewport{width:auto!important}")), 11 | document.querySelector("head").appendChild(e) 12 | } 13 | }() 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTML5-Image-Display 2 | 3 | HTML5 Image Display 4 | 5 | 🧰 Web application was built with HTML5 and Client-Side JavaScript only. Rationale of development was for convenience and attempts at creating tools to boost overall work productivity. 6 | 7 | ## ℹ Implementation Overview 8 | 9 | ### ❶ Image 10 | 11 | Image Display 12 | 13 | ### ❷ Assign CSS Property — background-image: url(…) 14 | 15 | CSS Display 16 | 17 | ### ❸ Canvas Element 18 | 19 | CANVAS Display 20 | 21 | --- 22 | 23 | ## 📌 Related Content 24 | 25 | * [3 Ways To Display An Image onto Browser With Client-Side JavaScript](https://geek-cc.medium.com/3-ways-to-display-an-image-onto-browser-with-client-side-javascript-6653d94680a7) ✍ 26 | * Feel free to follow me on 🔗 [Medium](https://geek-cc.medium.com/) if you are interested in this piece of work or are interested in Data Analytics (including Tableau Dashboarding), 🌐 Geospatial Intelligence & GIS or other web-related content -------------------------------------------------------------------------------- /css/offcanvas.css: -------------------------------------------------------------------------------- 1 | /* Prevent scroll on narrow devices */ 2 | /* html, 3 | body { 4 | overflow-x: hidden; 5 | } 6 | 7 | body { 8 | padding-top: 62px; 9 | } */ 10 | @media (max-width: 768px) { 11 | .offcanvas-collapse { 12 | position: fixed; 13 | top: 58px; 14 | /* Height of navbar */ 15 | bottom: 0; 16 | left: 100%; 17 | width: 100%; 18 | padding-right: 1rem; 19 | padding-left: 1rem; 20 | overflow-y: auto; 21 | visibility: hidden; 22 | background-color: #5d4482; 23 | transition: visibility .3s ease-in-out, -webkit-transform .3s ease-in-out; 24 | transition: transform .3s ease-in-out, visibility .3s ease-in-out; 25 | transition: transform .3s ease-in-out, visibility .3s ease-in-out, -webkit-transform .3s ease-in-out; 26 | z-index: 10; 27 | } 28 | 29 | .offcanvas-collapse.open { 30 | visibility: visible; 31 | -webkit-transform: translateX(-100%); 32 | transform: translateX(-100%); 33 | } 34 | } 35 | 36 | .nav-scroller { 37 | position: relative; 38 | z-index: 2; 39 | height: 2.75rem; 40 | overflow-y: hidden; 41 | } 42 | 43 | .nav-scroller .nav { 44 | display: -ms-flexbox; 45 | display: flex; 46 | -ms-flex-wrap: nowrap; 47 | flex-wrap: nowrap; 48 | padding-bottom: 1rem; 49 | margin-top: -1px; 50 | overflow-x: auto; 51 | color: rgba(255, 255, 255, .75); 52 | text-align: center; 53 | white-space: nowrap; 54 | -webkit-overflow-scrolling: touch; 55 | } 56 | 57 | .nav-underline .nav-link { 58 | padding-top: .75rem; 59 | padding-bottom: .75rem; 60 | font-size: .875rem; 61 | color: #6c757d; 62 | } 63 | 64 | .nav-underline .nav-link:hover { 65 | color: #007bff; 66 | } 67 | 68 | .nav-underline .active { 69 | font-weight: 500; 70 | color: #343a40; 71 | } 72 | 73 | .text-white-50 { 74 | color: rgba(255, 255, 255, .5); 75 | } 76 | 77 | .lh-100 { 78 | line-height: 1; 79 | } 80 | 81 | .lh-125 { 82 | line-height: 1.25; 83 | } 84 | 85 | .lh-150 { 86 | line-height: 1.5; 87 | } 88 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | function resizeImgRow() { 2 | let detailsCard=document.getElementById('detailsCard'); 3 | let imgRow=document.getElementById('imgRow'); 4 | imgRow['style']['height']=`${(529-detailsCard.clientHeight)}px`; 5 | } 6 | resizeImgRow(); 7 | window.addEventListener('resize', () => { 8 | resizeImgRow(); 9 | }, false); 10 | 11 | var colorDepth=document.getElementById('colorDepth'); 12 | colorDepth.innerHTML=screen.colorDepth; 13 | var colorResolution=document.getElementById('colorResolution'); 14 | colorResolution.innerHTML=screen.pixelDepth; 15 | 16 | var availDimensions=document.getElementById('availDimensions'); 17 | availDimensions.innerHTML=`${screen.availWidth} × ${screen.availHeight}`; 18 | 19 | var screenDimensions=document.getElementById('screenDimensions'); 20 | screenDimensions.innerHTML=`${screen.width} × ${screen.height}`; 21 | 22 | window.devicePixelRatio = 3.0; 23 | var scale = window.devicePixelRatio; 24 | 25 | var _ZOOM_FACTOR=1.0; 26 | 27 | var imgH = 0; 28 | var imgW = 0; 29 | 30 | const byteToKBScale = 0.0009765625; 31 | 32 | var pixelDensity=document.getElementById('pixelDensity'); 33 | pixelDensity.innerHTML=scale; 34 | 35 | var uploadImgBtn=document.getElementById('uploadImgBtn'); 36 | var uploadImg=document.getElementById('uploadImg'); 37 | 38 | 39 | var imgCard=document.getElementById('imgCard'); 40 | var canvasCard=document.getElementById('canvasCard'); 41 | var cssDIVCard=document.getElementById('cssDIVCard'); 42 | 43 | uploadImgBtn.addEventListener('click', (evt)=> { 44 | uploadImg.click(); 45 | }, false); 46 | 47 | const monthsAbbr=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; 48 | const millisecondsToDateStr = (milliseconds) => { // in milliseconds 49 | const dateObject = new Date(milliseconds); 50 | 51 | const dateYear= dateObject.getFullYear(); 52 | const dateMth= monthsAbbr[dateObject.getMonth()]; 53 | const dateDay= ((dateObject.getDate())<10) ? `0${(dateObject.getDate())}` : (dateObject.getDate()); 54 | 55 | const humanDateFormat = `${dateDay} ${dateMth} ${dateYear}`; 56 | 57 | return humanDateFormat; 58 | }; 59 | 60 | const loadImage = (url) => new Promise((resolve, reject) => { 61 | const img = new Image(); 62 | img.addEventListener('load', () => resolve(img)); 63 | img.addEventListener('error', (err) => reject(err)); 64 | img.src = url; 65 | }); 66 | 67 | function readFileAsDataURL(file) { 68 | return new Promise((resolve,reject) => { 69 | let fileredr = new FileReader(); 70 | fileredr.onload = () => resolve(fileredr.result); 71 | fileredr.onerror = () => reject(fileredr); 72 | fileredr.readAsDataURL(file); 73 | }); 74 | } 75 | 76 | var fileName=document.getElementById('fileName'); 77 | var fileSize=document.getElementById('fileSize'); 78 | var imgDimensions=document.getElementById('imgDimensions'); 79 | var fileType=document.getElementById('fileType'); 80 | var lastModified=document.getElementById('lastModified'); 81 | 82 | function scaleCanvas(_CANVAS, _IMG, _ZOOM_FACTOR,imgH, imgW, scale) { 83 | _CANVAS['style']['height'] = `${imgH}px`; 84 | _CANVAS['style']['width'] = `${imgW}px`; 85 | 86 | let cWidth=_ZOOM_FACTOR*imgW*scale; 87 | let cHeight=_ZOOM_FACTOR*imgH*scale; 88 | 89 | _CANVAS.width=cWidth; 90 | _CANVAS.height=cHeight; 91 | 92 | _CANVAS.getContext('2d').scale(scale, scale); 93 | _CANVAS.getContext('2d').drawImage(_IMG, 0, 0, imgW*_ZOOM_FACTOR, imgH*_ZOOM_FACTOR); 94 | } 95 | 96 | uploadImg.addEventListener('change', async(evt) => { 97 | let file = evt.target.files[0]; 98 | if(!file) return; 99 | 100 | fileName.innerHTML=file.name; 101 | fileSize.innerHTML=`${(parseFloat(file.size) * byteToKBScale).toFixed(2)} 🇰🇧`; 102 | fileType.innerHTML=file.type; 103 | lastModified.innerHTML=millisecondsToDateStr(file.lastModified); 104 | 105 | let b64str = await readFileAsDataURL(file); 106 | let _IMG=await loadImage(b64str); 107 | 108 | // set sizes in memory 109 | imgH=_IMG.naturalHeight; 110 | imgW=_IMG.naturalWidth; 111 | imgDimensions.innerHTML=`${imgW}px × ${imgH}px`; 112 | 113 | _IMG['style']['height']=`${imgH}px`; 114 | _IMG['style']['width']=`${imgW}px`; 115 | _IMG['style']['border'] ='1px solid #d3d3d3'; 116 | _IMG.id='imgUpload'; 117 | imgCard.appendChild(_IMG); 118 | 119 | let _CANVAS=document.createElement('canvas'); 120 | _CANVAS['style']['border'] ='1px solid #d3d3d3'; 121 | _CANVAS.id='imgCanvas'; 122 | scaleCanvas(_CANVAS,_IMG,_ZOOM_FACTOR,imgH,imgW,scale); 123 | canvasCard.appendChild(_CANVAS); 124 | 125 | // display size 126 | let requiredWidth=(canvasCard.clientWidth-16); 127 | _ZOOM_FACTOR=requiredWidth/parseFloat(_CANVAS['style']['width'].replace('px','')); 128 | 129 | imgH =_ZOOM_FACTOR*parseFloat(_IMG['style']['height'].replace('px','')); 130 | imgW =_ZOOM_FACTOR*parseFloat(_IMG['style']['width'].replace('px','')); 131 | 132 | _IMG['style']['height'] = `${imgH}px`; 133 | _IMG['style']['width'] = `${imgW}px`; 134 | 135 | cssDIVCard['style']['height'] = `${imgH}px`; 136 | cssDIVCard['style']['width'] = `${imgW}px`; 137 | cssDIVCard['style']['border'] ='1px solid #d3d3d3'; 138 | 139 | cssDIVCard['style']['background-image']='url('+b64str+')'; 140 | cssDIVCard['style']['background-position']='center'; 141 | cssDIVCard['style']['background-repeat']='no-repeat'; 142 | cssDIVCard['style']['background-size']='contain'; 143 | 144 | scaleCanvas(_CANVAS,_IMG,_ZOOM_FACTOR,imgH,imgW,scale); 145 | }, false); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ʀᴇɴᴅᴇʀ ᴀɴ ɪᴍᴀɢᴇ ɪɴ ʜᴛᴍʟ5 | sʜᴏᴡᴄᴀsᴇs ᴍᴜʟᴛɪᴘʟᴇ ᴜɴɪɋᴜᴇ ɪᴍᴘʟᴇᴍᴇɴᴛᴀᴛɪᴏɴs 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 |
31 |
32 |
Window Screen Properties
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
Dimensions Width × Height
Available Dimensions Width × Height
Color Depth Bit Depth
Color Resolution
Device to CSS Pixel Ratio
56 |
57 |
58 |
59 | 60 |
61 |
62 |
63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
Name
Size
Type
Dimensions Width × Height
Last Modified
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
❶ Image Tag <img />
95 |
96 |
97 |
98 | 99 |
100 |
101 |
❷ Assign CSS Property background-image:url(…)
102 |
103 |
104 |
105 | 106 |
107 |
108 |
❸ Canvas Element <canvas><canvas/>
109 |
110 |
111 |
112 |
113 |
114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /js/polyfill.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Native JavaScript for Bootstrap Polyfill v4.0.8 (https://thednp.github.io/bootstrap.native/) 3 | * Copyright 2015-2021 © dnp_theme 4 | * Licensed under MIT (https://github.com/thednp/bootstrap.native/blob/master/LICENSE) 5 | */ 6 | "use strict"; 7 | if (!Array.prototype.some) { 8 | Array.prototype.some = function(fun, thisArg) { 9 | 10 | if (this == null) { 11 | throw new TypeError('Array.prototype.some called on null or undefined'); 12 | } 13 | 14 | if (typeof fun !== 'function') { 15 | throw new TypeError(); 16 | } 17 | 18 | var t = Object(this); 19 | var len = t.length >>> 0; 20 | 21 | for (var i = 0; i < len; i++) { 22 | if (i in t && fun.call(thisArg, t[i], i, t)) { 23 | return true; 24 | } 25 | } 26 | 27 | return false; 28 | }; 29 | } 30 | 31 | if (!Array.prototype.find) { 32 | Object.defineProperty(Array.prototype, 'find', { 33 | value: function(predicate) { 34 | if (this == null) { 35 | throw TypeError('"this" is null or not defined'); 36 | } 37 | 38 | var o = Object(this), len = o.length >>> 0; 39 | 40 | if (typeof predicate !== 'function') { 41 | throw TypeError('predicate must be a function'); 42 | } 43 | 44 | var thisArg = arguments[1], k = 0; 45 | while (k < len) { 46 | var kValue = o[k]; 47 | if (predicate.call(thisArg, kValue, k, o)) { 48 | return kValue; 49 | } 50 | k++; 51 | } 52 | 53 | return undefined; 54 | }, 55 | configurable: true, 56 | writable: true 57 | }); 58 | } 59 | 60 | if (!Array.prototype.includes) { 61 | Array.prototype.includes = function(searchElement /*, fromIndex*/ ) { 62 | var O = Object(this); 63 | var len = parseInt(O.length) || 0; 64 | if (len === 0) { 65 | return false; 66 | } 67 | var n = parseInt(arguments[1]) || 0; 68 | var k; 69 | if (n >= 0) { 70 | k = n; 71 | } else { 72 | k = len + n; 73 | if (k < 0) {k = 0;} 74 | } 75 | var currentElement; 76 | while (k < len) { 77 | currentElement = O[k]; 78 | if (searchElement === currentElement || 79 | (searchElement !== searchElement && currentElement !== currentElement)) { 80 | return true; 81 | } 82 | k++; 83 | } 84 | return false; 85 | }; 86 | } 87 | 88 | if (!Array.from) { 89 | Array.from = (function () { 90 | var toStr = Object.prototype.toString; 91 | var isCallable = function (fn) { 92 | return typeof fn === 'function' || toStr.call(fn) === '[object Function]'; 93 | }; 94 | var toInteger = function (value) { 95 | var number = Number(value); 96 | if (isNaN(number)) { return 0; } 97 | if (number === 0 || !isFinite(number)) { return number; } 98 | return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); 99 | }; 100 | var maxSafeInteger = Math.pow(2, 53) - 1; 101 | var toLength = function (value) { 102 | var len = toInteger(value); 103 | return Math.min(Math.max(len, 0), maxSafeInteger); 104 | }; 105 | 106 | return function from(arrayLike/*, mapFn, thisArg */) { 107 | var C = this, items = Object(arrayLike); 108 | if (arrayLike == null) { 109 | throw new TypeError('Array.from requires an array-like object - not null or undefined'); 110 | } 111 | var mapFn = arguments.length > 1 ? arguments[1] : void undefined, T; 112 | if (typeof mapFn !== 'undefined') { 113 | if (!isCallable(mapFn)) { 114 | throw new TypeError('Array.from: when provided, the second argument must be a function'); 115 | } 116 | 117 | if (arguments.length > 2) { 118 | T = arguments[2]; 119 | } 120 | } 121 | var len = toLength(items.length); 122 | var A = isCallable(C) ? Object(new C(len)) : new Array(len); 123 | 124 | var k = 0; 125 | var kValue; 126 | while (k < len) { 127 | kValue = items[k]; 128 | if (mapFn) { 129 | A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k); 130 | } else { 131 | A[k] = kValue; 132 | } 133 | k += 1; 134 | } 135 | A.length = len; 136 | return A; 137 | } 138 | }()); 139 | } 140 | 141 | if (!Object.keys) { 142 | Object.keys = function(obj) { 143 | var keys = []; 144 | 145 | for (var i in obj) { 146 | if (obj.hasOwnProperty(i)) { 147 | keys.push(i); 148 | } 149 | } 150 | 151 | return keys; 152 | }; 153 | } 154 | 155 | if (typeof Object.assign !== 'function') { 156 | Object.defineProperty(Object, "assign", { 157 | value: function assign(target, varArgs) { // .length of function is 2 158 | var arguments$1 = arguments; 159 | 160 | if (target === null || target === undefined) { 161 | throw new TypeError('Cannot convert undefined or null to object'); 162 | } 163 | 164 | var to = Object(target); 165 | 166 | for (var index = 1; index < arguments.length; index++) { 167 | var nextSource = arguments$1[index]; 168 | 169 | if (nextSource !== null && nextSource !== undefined) { 170 | for (var nextKey in nextSource) { 171 | if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { 172 | to[nextKey] = nextSource[nextKey]; 173 | } 174 | } 175 | } 176 | } 177 | return to; 178 | }, 179 | writable: true, 180 | configurable: true 181 | }); 182 | } 183 | 184 | if (!Element.prototype.matches) { 185 | Element.prototype.matches = Element.prototype.webkitMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector; 186 | } 187 | 188 | if (!Element.prototype.closest) { 189 | Element.prototype.closest = function closest(selector) { 190 | var node = this; 191 | 192 | while (node) { 193 | if (node.matches(selector)) { return node; } 194 | else { node = 'SVGElement' in window && node instanceof SVGElement ? node.parentNode : node.parentElement; } 195 | } 196 | 197 | return null; 198 | }; 199 | } 200 | 201 | if (!window.Event || !Window.prototype.Event) { 202 | window.Event = Window.prototype.Event = Document.prototype.Event = Element.prototype.Event = function Event(type, eventInitDict) { 203 | if (!type) { throw new Error('Not enough arguments'); } 204 | var event, 205 | bubbles = eventInitDict && eventInitDict.bubbles !== undefined ? eventInitDict.bubbles : false, 206 | cancelable = eventInitDict && eventInitDict.cancelable !== undefined ? eventInitDict.cancelable : false; 207 | if ( 'createEvent' in document ) { 208 | event = document.createEvent('Event'); 209 | event.initEvent(type, bubbles, cancelable); 210 | } else { 211 | event = document.createEventObject(); 212 | event.type = type; 213 | event.bubbles = bubbles; 214 | event.cancelable = cancelable; 215 | } 216 | return event; 217 | }; 218 | } 219 | 220 | if ( !window.CustomEvent || !Window.prototype.CustomEvent) { 221 | window.CustomEvent = Window.prototype.CustomEvent = Document.prototype.CustomEvent = Element.prototype.CustomEvent = function CustomEvent(type, eventInitDict) { 222 | if (!type) { 223 | throw Error('TypeError: Failed to construct "CustomEvent": An event name must be provided.'); 224 | } 225 | var event = new Event(type, eventInitDict); 226 | event.detail = eventInitDict && eventInitDict.detail || null; 227 | return event; 228 | }; 229 | } 230 | 231 | if (!Node.prototype.contains) { 232 | Node.prototype.contains = function (el) { 233 | while (el = el.parentNode) { 234 | if (el === this) { return true; } 235 | } 236 | return false; 237 | }; 238 | } 239 | 240 | if (!Number.isNaN) { 241 | Number.isNaN = function(value) { 242 | return typeof value === 'number' 243 | && value !== value; 244 | }; 245 | } 246 | 247 | if (!String.prototype.includes) { 248 | String.prototype.includes = function(search, start) { 249 | if (search instanceof RegExp) { 250 | throw TypeError('first argument must not be a RegExp'); 251 | } 252 | if (start === undefined) { start = 0; } 253 | return this.indexOf(search, start) !== -1; 254 | }; 255 | } 256 | -------------------------------------------------------------------------------- /js/bootstrap-native-v4.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Native JavaScript for Bootstrap v4.1.0 (https://thednp.github.io/bootstrap.native/) 3 | * Copyright 2015-2021 © dnp_theme 4 | * Licensed under MIT (https://github.com/thednp/bootstrap.native/blob/master/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 8 | typeof define === 'function' && define.amd ? define(factory) : 9 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.BSN = factory()); 10 | })(this, (function () { 'use strict'; 11 | 12 | /** 13 | * A global namespace for 'transitionend' string. 14 | * @type {string} 15 | */ 16 | var transitionEndEvent = 'webkitTransition' in document.head.style ? 'webkitTransitionEnd' : 'transitionend'; 17 | 18 | /** 19 | * A global namespace for CSS3 transition support. 20 | * @type {boolean} 21 | */ 22 | var supportTransition = 'webkitTransition' in document.head.style || 'transition' in document.head.style; 23 | 24 | /** 25 | * A global namespace for 'transitionDelay' string. 26 | * @type {string} 27 | */ 28 | var transitionDelay = 'webkitTransition' in document.head.style ? 'webkitTransitionDelay' : 'transitionDelay'; 29 | 30 | /** 31 | * A global namespace for 'transitionProperty' string. 32 | * @type {string} 33 | */ 34 | var transitionProperty = 'webkitTransition' in document.head.style ? 'webkitTransitionProperty' : 'transitionProperty'; 35 | 36 | /** 37 | * Utility to get the computed transitionDelay 38 | * from Element in miliseconds. 39 | * 40 | * @param {Element} element target 41 | * @return {number} the value in miliseconds 42 | */ 43 | function getElementTransitionDelay(element) { 44 | var computedStyle = getComputedStyle(element); 45 | var propertyValue = computedStyle[transitionProperty]; 46 | var delayValue = computedStyle[transitionDelay]; 47 | var delayScale = delayValue.includes('ms') ? 1 : 1000; 48 | var duration = supportTransition && propertyValue && propertyValue !== 'none' 49 | ? parseFloat(delayValue) * delayScale : 0; 50 | 51 | return !Number.isNaN(duration) ? duration : 0; 52 | } 53 | 54 | /** 55 | * A global namespace for 'transitionDuration' string. 56 | * @type {string} 57 | */ 58 | var transitionDuration = 'webkitTransition' in document.head.style ? 'webkitTransitionDuration' : 'transitionDuration'; 59 | 60 | /** 61 | * Utility to get the computed transitionDuration 62 | * from Element in miliseconds. 63 | * 64 | * @param {Element} element target 65 | * @return {number} the value in miliseconds 66 | */ 67 | function getElementTransitionDuration(element) { 68 | var computedStyle = getComputedStyle(element); 69 | var propertyValue = computedStyle[transitionProperty]; 70 | var durationValue = computedStyle[transitionDuration]; 71 | var durationScale = durationValue.includes('ms') ? 1 : 1000; 72 | var duration = supportTransition && propertyValue && propertyValue !== 'none' 73 | ? parseFloat(durationValue) * durationScale : 0; 74 | 75 | return !Number.isNaN(duration) ? duration : 0; 76 | } 77 | 78 | /** 79 | * Utility to make sure callbacks are consistently 80 | * called when transition ends. 81 | * 82 | * @param {Element} element target 83 | * @param {function} handler `transitionend` callback 84 | */ 85 | function emulateTransitionEnd(element, handler) { 86 | var called = 0; 87 | var endEvent = new Event(transitionEndEvent); 88 | var duration = getElementTransitionDuration(element); 89 | var delay = getElementTransitionDelay(element); 90 | 91 | if (duration) { 92 | /** 93 | * Wrap the handler in on -> off callback 94 | * @param {Event} e Event object 95 | * @callback 96 | */ 97 | var transitionEndWrapper = function (e) { 98 | if (e.target === element) { 99 | handler.apply(element, [e]); 100 | element.removeEventListener(transitionEndEvent, transitionEndWrapper); 101 | called = 1; 102 | } 103 | }; 104 | element.addEventListener(transitionEndEvent, transitionEndWrapper); 105 | setTimeout(function () { 106 | if (!called) { element.dispatchEvent(endEvent); } 107 | }, duration + delay + 17); 108 | } else { 109 | handler.apply(element, [endEvent]); 110 | } 111 | } 112 | 113 | /** 114 | * Checks if an element is an `Element`. 115 | * 116 | * @param {any} element the target element 117 | * @returns {boolean} the query result 118 | */ 119 | function isElement(element) { 120 | return element instanceof Element; 121 | } 122 | 123 | /** 124 | * Utility to check if target is typeof Element 125 | * or find one that matches a selector. 126 | * 127 | * @param {Element | string} selector the input selector or target element 128 | * @param {Element=} parent optional Element to look into 129 | * @return {Element?} the Element or `querySelector` result 130 | */ 131 | function queryElement(selector, parent) { 132 | var lookUp = parent && isElement(parent) ? parent : document; 133 | // @ts-ignore 134 | return isElement(selector) ? selector : lookUp.querySelector(selector); 135 | } 136 | 137 | /** BSN v4 custom event */ 138 | function bootstrapCustomEvent(eventType, componentName, eventProperties) { 139 | var OriginalCustomEvent = new CustomEvent((eventType + ".bs." + componentName), { cancelable: true }); 140 | 141 | if (typeof eventProperties !== 'undefined') { 142 | Object.keys(eventProperties).forEach(function (key) { 143 | Object.defineProperty(OriginalCustomEvent, key, { 144 | value: eventProperties[key], 145 | }); 146 | }); 147 | } 148 | return OriginalCustomEvent; 149 | } 150 | 151 | /** 152 | * A quick shortcut for `dispatchEvent` v4. 153 | * @param {CustomEvent} customEvent the event object 154 | */ 155 | function dispatchCustomEvent(customEvent) { 156 | if (this) { this.dispatchEvent(customEvent); } 157 | } 158 | 159 | /* Native JavaScript for Bootstrap 4 | Alert 160 | -------------------------------------------- */ 161 | 162 | // ALERT DEFINITION 163 | // ================ 164 | 165 | function Alert(elem) { 166 | var element; 167 | 168 | // bind 169 | var self = this; 170 | 171 | // the target alert 172 | var alert; 173 | 174 | // custom events 175 | var closeCustomEvent = bootstrapCustomEvent('close', 'alert'); 176 | var closedCustomEvent = bootstrapCustomEvent('closed', 'alert'); 177 | 178 | // private methods 179 | function triggerHandler() { 180 | if (alert.classList.contains('fade')) { emulateTransitionEnd(alert, transitionEndHandler); } 181 | else { transitionEndHandler(); } 182 | } 183 | function toggleEvents(add) { 184 | var action = add ? 'addEventListener' : 'removeEventListener'; 185 | element[action]('click', clickHandler, false); 186 | } 187 | 188 | // event handlers 189 | function clickHandler(e) { 190 | alert = e && e.target.closest('.alert'); 191 | element = queryElement('[data-dismiss="alert"]', alert); 192 | if (element && alert && (element === e.target || element.contains(e.target))) { self.close(); } 193 | } 194 | function transitionEndHandler() { 195 | toggleEvents(); 196 | alert.parentNode.removeChild(alert); 197 | dispatchCustomEvent.call(alert, closedCustomEvent); 198 | } 199 | 200 | // PUBLIC METHODS 201 | self.close = function () { 202 | if (alert && element && alert.classList.contains('show')) { 203 | dispatchCustomEvent.call(alert, closeCustomEvent); 204 | if (closeCustomEvent.defaultPrevented) { return; } 205 | self.dispose(); 206 | alert.classList.remove('show'); 207 | triggerHandler(); 208 | } 209 | }; 210 | 211 | self.dispose = function () { 212 | toggleEvents(); 213 | delete element.Alert; 214 | }; 215 | 216 | // INIT 217 | // initialization element 218 | element = queryElement(elem); 219 | 220 | // find the target alert 221 | alert = element.closest('.alert'); 222 | 223 | // reset on re-init 224 | if (element.Alert) { element.Alert.dispose(); } 225 | 226 | // prevent adding event handlers twice 227 | if (!element.Alert) { toggleEvents(1); } 228 | 229 | // store init object within target element 230 | self.element = element; 231 | element.Alert = self; 232 | } 233 | 234 | /* Native JavaScript for Bootstrap 4 | Button 235 | ---------------------------------------------*/ 236 | 237 | // BUTTON DEFINITION 238 | // ================= 239 | 240 | function Button(elem) { 241 | var element; 242 | 243 | // bind and labels 244 | var self = this; 245 | var labels; 246 | 247 | // changeEvent 248 | var changeCustomEvent = bootstrapCustomEvent('change', 'button'); 249 | 250 | // private methods 251 | function toggle(e) { 252 | var eTarget = e.target; 253 | var parentLabel = eTarget.closest('LABEL'); // the .btn label 254 | var label = null; 255 | 256 | if (eTarget.tagName === 'LABEL') { 257 | label = eTarget; 258 | } else if (parentLabel) { 259 | label = parentLabel; 260 | } 261 | 262 | // current input 263 | var input = label && label.getElementsByTagName('INPUT')[0]; 264 | 265 | // invalidate if no input 266 | if (!input) { return; } 267 | 268 | dispatchCustomEvent.call(input, changeCustomEvent); // trigger the change for the input 269 | dispatchCustomEvent.call(element, changeCustomEvent); // trigger the change for the btn-group 270 | 271 | // manage the dom manipulation 272 | if (input.type === 'checkbox') { // checkboxes 273 | if (changeCustomEvent.defaultPrevented) { return; } // discontinue when defaultPrevented is true 274 | 275 | if (!input.checked) { 276 | label.classList.add('active'); 277 | input.getAttribute('checked'); 278 | input.setAttribute('checked', 'checked'); 279 | input.checked = true; 280 | } else { 281 | label.classList.remove('active'); 282 | input.getAttribute('checked'); 283 | input.removeAttribute('checked'); 284 | input.checked = false; 285 | } 286 | 287 | if (!element.toggled) { // prevent triggering the event twice 288 | element.toggled = true; 289 | } 290 | } 291 | 292 | if (input.type === 'radio' && !element.toggled) { // radio buttons 293 | if (changeCustomEvent.defaultPrevented) { return; } 294 | // don't trigger if already active 295 | // (the OR condition is a hack to check if the buttons were selected 296 | // with key press and NOT mouse click) 297 | if (!input.checked || (e.screenX === 0 && e.screenY === 0)) { 298 | label.classList.add('active'); 299 | label.classList.add('focus'); 300 | input.setAttribute('checked', 'checked'); 301 | input.checked = true; 302 | 303 | element.toggled = true; 304 | Array.from(labels).forEach(function (otherLabel) { 305 | var otherInput = otherLabel.getElementsByTagName('INPUT')[0]; 306 | if (otherLabel !== label && otherLabel.classList.contains('active')) { 307 | dispatchCustomEvent.call(otherInput, changeCustomEvent); // trigger the change 308 | otherLabel.classList.remove('active'); 309 | otherInput.removeAttribute('checked'); 310 | otherInput.checked = false; 311 | } 312 | }); 313 | } 314 | } 315 | setTimeout(function () { element.toggled = false; }, 50); 316 | } 317 | 318 | // handlers 319 | function keyHandler(e) { 320 | var key = e.which || e.keyCode; 321 | if (key === 32 && e.target === document.activeElement) { toggle(e); } 322 | } 323 | function preventScroll(e) { 324 | var key = e.which || e.keyCode; 325 | if (key === 32) { e.preventDefault(); } 326 | } 327 | function focusToggle(e) { 328 | if (e.target.tagName === 'INPUT') { 329 | var action = e.type === 'focusin' ? 'add' : 'remove'; 330 | e.target.closest('.btn').classList[action]('focus'); 331 | } 332 | } 333 | function toggleEvents(add) { 334 | var action = add ? 'addEventListener' : 'removeEventListener'; 335 | element[action]('click', toggle, false); 336 | element[action]('keyup', keyHandler, false); 337 | element[action]('keydown', preventScroll, false); 338 | element[action]('focusin', focusToggle, false); 339 | element[action]('focusout', focusToggle, false); 340 | } 341 | 342 | // public method 343 | self.dispose = function () { 344 | toggleEvents(); 345 | delete element.Button; 346 | }; 347 | 348 | // init 349 | // initialization element 350 | element = queryElement(elem); 351 | 352 | // reset on re-init 353 | if (element.Button) { element.Button.dispose(); } 354 | 355 | labels = element.getElementsByClassName('btn'); 356 | 357 | // invalidate 358 | if (!labels.length) { return; } 359 | 360 | // prevent adding event handlers twice 361 | if (!element.Button) { toggleEvents(1); } 362 | 363 | // set initial toggled state 364 | // toggled makes sure to prevent triggering twice the change.bs.button events 365 | element.toggled = false; 366 | 367 | // associate target with init object 368 | element.Button = self; 369 | 370 | // activate items on load 371 | Array.from(labels).forEach(function (btn) { 372 | var hasChecked = queryElement('input:checked', btn); 373 | if (!btn.classList.contains('active') && hasChecked) { 374 | btn.classList.add('active'); 375 | } 376 | if (btn.classList.contains('active') && !hasChecked) { 377 | btn.classList.remove('active'); 378 | } 379 | }); 380 | } 381 | 382 | /** 383 | * A global namespace for mouse hover events. 384 | * @type {[string, string]} 385 | */ 386 | var mouseHoverEvents = ('onmouseleave' in document) ? ['mouseenter', 'mouseleave'] : ['mouseover', 'mouseout']; 387 | 388 | /** 389 | * A global namespace for 'addEventListener' string. 390 | * @type {string} 391 | */ 392 | var addEventListener = 'addEventListener'; 393 | 394 | /** 395 | * A global namespace for 'removeEventListener' string. 396 | * @type {string} 397 | */ 398 | var removeEventListener = 'removeEventListener'; 399 | 400 | /** 401 | * A global namespace for passive events support. 402 | * @type {boolean} 403 | */ 404 | var supportPassive = (function () { 405 | var result = false; 406 | try { 407 | var opts = Object.defineProperty({}, 'passive', { 408 | get: function get() { 409 | result = true; 410 | return result; 411 | }, 412 | }); 413 | document[addEventListener]('DOMContentLoaded', function wrap() { 414 | document[removeEventListener]('DOMContentLoaded', wrap, opts); 415 | }, opts); 416 | } catch (e) { 417 | throw Error('Passive events are not supported'); 418 | } 419 | 420 | return result; 421 | })(); 422 | 423 | // general event options 424 | 425 | /** 426 | * A global namespace for most scroll event listeners. 427 | */ 428 | var passiveHandler = supportPassive ? { passive: true } : false; 429 | 430 | /** 431 | * Utility to determine if an `Element` 432 | * is partially visible in viewport. 433 | * 434 | * @param {Element} element target 435 | * @return {boolean} Boolean 436 | */ 437 | function isElementInScrollRange(element) { 438 | var bcr = element.getBoundingClientRect(); 439 | var viewportHeight = window.innerHeight || document.documentElement.clientHeight; 440 | return bcr.top <= viewportHeight && bcr.bottom >= 0; // bottom && top 441 | } 442 | 443 | /** 444 | * Utility to force re-paint of an Element 445 | * 446 | * @param {Element | HTMLElement} element is the target 447 | * @return {number} the Element.offsetHeight value 448 | */ 449 | function reflow(element) { 450 | // @ts-ignore 451 | return element.offsetHeight; 452 | } 453 | 454 | /* Native JavaScript for Bootstrap 4 | Carousel 455 | ----------------------------------------------- */ 456 | 457 | // CAROUSEL DEFINITION 458 | // =================== 459 | 460 | function Carousel(elem, opsInput) { 461 | var assign, assign$1, assign$2; 462 | 463 | var element; 464 | 465 | // set options 466 | var options = opsInput || {}; 467 | 468 | // bind 469 | var self = this; 470 | 471 | // internal variables 472 | var vars; 473 | var ops; 474 | 475 | // custom events 476 | var slideCustomEvent; 477 | var slidCustomEvent; 478 | 479 | // carousel elements 480 | var slides; 481 | var leftArrow; 482 | var rightArrow; 483 | var indicator; 484 | var indicators; 485 | 486 | // handlers 487 | function pauseHandler() { 488 | if (ops.interval !== false && !element.classList.contains('paused')) { 489 | element.classList.add('paused'); 490 | if (!vars.isSliding) { 491 | clearInterval(vars.timer); 492 | vars.timer = null; 493 | } 494 | } 495 | } 496 | function resumeHandler() { 497 | if (ops.interval !== false && element.classList.contains('paused')) { 498 | element.classList.remove('paused'); 499 | if (!vars.isSliding) { 500 | clearInterval(vars.timer); 501 | vars.timer = null; 502 | self.cycle(); 503 | } 504 | } 505 | } 506 | function indicatorHandler(e) { 507 | e.preventDefault(); 508 | if (vars.isSliding) { return; } 509 | 510 | var eventTarget = e.target; // event target | the current active item 511 | 512 | if (eventTarget && !eventTarget.classList.contains('active') && eventTarget.getAttribute('data-slide-to')) { 513 | vars.index = +(eventTarget.getAttribute('data-slide-to')); 514 | } else { return; } 515 | 516 | self.slideTo(vars.index); // Do the slide 517 | } 518 | function controlsHandler(e) { 519 | e.preventDefault(); 520 | if (vars.isSliding) { return; } 521 | 522 | var eventTarget = e.currentTarget || e.srcElement; 523 | 524 | if (eventTarget === rightArrow) { 525 | vars.index += 1; 526 | } else if (eventTarget === leftArrow) { 527 | vars.index -= 1; 528 | } 529 | 530 | self.slideTo(vars.index); // Do the slide 531 | } 532 | function keyHandler(ref) { 533 | var which = ref.which; 534 | 535 | if (vars.isSliding) { return; } 536 | switch (which) { 537 | case 39: 538 | vars.index += 1; 539 | break; 540 | case 37: 541 | vars.index -= 1; 542 | break; 543 | default: return; 544 | } 545 | self.slideTo(vars.index); // Do the slide 546 | } 547 | function toggleEvents(add) { 548 | var action = add ? 'addEventListener' : 'removeEventListener'; 549 | if (ops.pause && ops.interval) { 550 | element[action](mouseHoverEvents[0], pauseHandler, false); 551 | element[action](mouseHoverEvents[1], resumeHandler, false); 552 | element[action]('touchstart', pauseHandler, passiveHandler); 553 | element[action]('touchend', resumeHandler, passiveHandler); 554 | } 555 | 556 | if (ops.touch && slides.length > 1) { element[action]('touchstart', touchDownHandler, passiveHandler); } 557 | 558 | if (rightArrow) { rightArrow[action]('click', controlsHandler, false); } 559 | if (leftArrow) { leftArrow[action]('click', controlsHandler, false); } 560 | 561 | if (indicator) { indicator[action]('click', indicatorHandler, false); } 562 | if (ops.keyboard) { window[action]('keydown', keyHandler, false); } 563 | } 564 | // touch events 565 | function toggleTouchEvents(add) { 566 | var action = add ? 'addEventListener' : 'removeEventListener'; 567 | element[action]('touchmove', touchMoveHandler, passiveHandler); 568 | element[action]('touchend', touchEndHandler, passiveHandler); 569 | } 570 | function touchDownHandler(e) { 571 | if (vars.isTouch) { return; } 572 | 573 | vars.touchPosition.startX = e.changedTouches[0].pageX; 574 | 575 | if (element.contains(e.target)) { 576 | vars.isTouch = true; 577 | toggleTouchEvents(1); 578 | } 579 | } 580 | function touchMoveHandler(e) { 581 | if (!vars.isTouch) { e.preventDefault(); return; } 582 | 583 | vars.touchPosition.currentX = e.changedTouches[0].pageX; 584 | 585 | // cancel touch if more than one changedTouches detected 586 | if (e.type === 'touchmove' && e.changedTouches.length > 1) { 587 | e.preventDefault(); 588 | } 589 | } 590 | function touchEndHandler(e) { 591 | if (!vars.isTouch || vars.isSliding) { return; } 592 | 593 | vars.touchPosition.endX = vars.touchPosition.currentX || e.changedTouches[0].pageX; 594 | 595 | if (vars.isTouch) { 596 | if ((!element.contains(e.target) || !element.contains(e.relatedTarget)) 597 | && Math.abs(vars.touchPosition.startX - vars.touchPosition.endX) < 75) { 598 | return; 599 | } 600 | if (vars.touchPosition.currentX < vars.touchPosition.startX) { 601 | vars.index += 1; 602 | } else if (vars.touchPosition.currentX > vars.touchPosition.startX) { 603 | vars.index -= 1; 604 | } 605 | vars.isTouch = false; 606 | self.slideTo(vars.index); 607 | 608 | toggleTouchEvents(); // remove 609 | } 610 | } 611 | // private methods 612 | function setActivePage(pageIndex) { // indicators 613 | Array.from(indicators).forEach(function (x) { return x.classList.remove('active'); }); 614 | if (indicators[pageIndex]) { indicators[pageIndex].classList.add('active'); } 615 | } 616 | function transitionEndHandler(e) { 617 | if (vars.touchPosition) { 618 | var next = vars.index; 619 | var timeout = e && e.target !== slides[next] ? e.elapsedTime * 1000 + 100 : 20; 620 | var activeItem = self.getActiveIndex(); 621 | var orientation = vars.direction === 'left' ? 'next' : 'prev'; 622 | 623 | if (vars.isSliding) { 624 | setTimeout(function () { 625 | if (vars.touchPosition) { 626 | vars.isSliding = false; 627 | 628 | slides[next].classList.add('active'); 629 | slides[activeItem].classList.remove('active'); 630 | 631 | slides[next].classList.remove(("carousel-item-" + orientation)); 632 | slides[next].classList.remove(("carousel-item-" + (vars.direction))); 633 | slides[activeItem].classList.remove(("carousel-item-" + (vars.direction))); 634 | 635 | dispatchCustomEvent.call(element, slidCustomEvent); 636 | // check for element, might have been disposed 637 | if (!document.hidden && ops.interval && !element.classList.contains('paused')) { 638 | self.cycle(); 639 | } 640 | } 641 | }, timeout); 642 | } 643 | } 644 | } 645 | 646 | // public methods 647 | self.cycle = function () { 648 | if (vars.timer) { 649 | clearInterval(vars.timer); 650 | vars.timer = null; 651 | } 652 | 653 | vars.timer = setInterval(function () { 654 | var idx = vars.index || self.getActiveIndex(); 655 | if (isElementInScrollRange(element)) { 656 | idx += 1; 657 | self.slideTo(idx); 658 | } 659 | }, ops.interval); 660 | }; 661 | self.slideTo = function (idx) { 662 | if (vars.isSliding) { return; } // when controled via methods, make sure to check again 663 | 664 | // the current active, orientation, event eventProperties 665 | var activeItem = self.getActiveIndex(); 666 | var next = idx; 667 | 668 | // first return if we're on the same item #227 669 | if (activeItem === next) { 670 | return; 671 | // or determine slide direction 672 | } if ((activeItem < next) || (activeItem === 0 && next === slides.length - 1)) { 673 | vars.direction = 'left'; // next 674 | } else if ((activeItem > next) || (activeItem === slides.length - 1 && next === 0)) { 675 | vars.direction = 'right'; // prev 676 | } 677 | 678 | // find the right next index 679 | if (next < 0) { next = slides.length - 1; } 680 | else if (next >= slides.length) { next = 0; } 681 | 682 | var orientation = vars.direction === 'left' ? 'next' : 'prev'; // determine type 683 | 684 | var eventProperties = { 685 | relatedTarget: slides[next], direction: vars.direction, from: activeItem, to: next, 686 | }; 687 | slideCustomEvent = bootstrapCustomEvent('slide', 'carousel', eventProperties); 688 | slidCustomEvent = bootstrapCustomEvent('slid', 'carousel', eventProperties); 689 | dispatchCustomEvent.call(element, slideCustomEvent); // here we go with the slide 690 | if (slideCustomEvent.defaultPrevented) { return; } // discontinue when prevented 691 | 692 | // update index 693 | vars.index = next; 694 | 695 | vars.isSliding = true; 696 | clearInterval(vars.timer); 697 | vars.timer = null; 698 | setActivePage(next); 699 | 700 | if (getElementTransitionDuration(slides[next]) && element.classList.contains('slide')) { 701 | slides[next].classList.add(("carousel-item-" + orientation)); 702 | reflow(slides[next]); 703 | slides[next].classList.add(("carousel-item-" + (vars.direction))); 704 | slides[activeItem].classList.add(("carousel-item-" + (vars.direction))); 705 | 706 | emulateTransitionEnd(slides[next], transitionEndHandler); 707 | } else { 708 | slides[next].classList.add('active'); 709 | reflow(slides[next]); 710 | slides[activeItem].classList.remove('active'); 711 | setTimeout(function () { 712 | vars.isSliding = false; 713 | // check for element, might have been disposed 714 | if (ops.interval && element && !element.classList.contains('paused')) { 715 | self.cycle(); 716 | } 717 | dispatchCustomEvent.call(element, slidCustomEvent); 718 | }, 100); 719 | } 720 | }; 721 | 722 | self.getActiveIndex = function () { return Array.from(slides).indexOf(element.getElementsByClassName('carousel-item active')[0]) || 0; }; 723 | 724 | self.dispose = function () { 725 | var itemClasses = ['left', 'right', 'prev', 'next']; 726 | 727 | Array.from(slides).forEach(function (slide, idx) { 728 | if (slide.classList.contains('active')) { setActivePage(idx); } 729 | itemClasses.forEach(function (cls) { return slide.classList.remove(("carousel-item-" + cls)); }); 730 | }); 731 | clearInterval(vars.timer); 732 | 733 | toggleEvents(); 734 | vars = {}; 735 | ops = {}; 736 | delete element.Carousel; 737 | }; 738 | 739 | // init 740 | 741 | // initialization element 742 | element = queryElement(elem); 743 | 744 | // reset on re-init 745 | if (element.Carousel) { element.Carousel.dispose(); } 746 | 747 | // carousel elements 748 | slides = element.getElementsByClassName('carousel-item'); 749 | (assign = element.getElementsByClassName('carousel-control-prev'), leftArrow = assign[0]); 750 | (assign$1 = element.getElementsByClassName('carousel-control-next'), rightArrow = assign$1[0]); 751 | (assign$2 = element.getElementsByClassName('carousel-indicators'), indicator = assign$2[0]); 752 | indicators = (indicator && indicator.getElementsByTagName('LI')) || []; 753 | 754 | // invalidate when not enough items 755 | if (slides.length < 2) { return; } 756 | 757 | // check options 758 | // DATA API 759 | var intervalAttribute = element.getAttribute('data-interval'); 760 | var intervalData = intervalAttribute === 'false' ? 0 : +(intervalAttribute); 761 | var touchData = element.getAttribute('data-touch') === 'false' ? 0 : 1; 762 | var pauseData = element.getAttribute('data-pause') === 'hover' || false; 763 | var keyboardData = element.getAttribute('data-keyboard') === 'true' || false; 764 | 765 | // JS options 766 | var intervalOption = options.interval; 767 | var touchOption = options.touch; 768 | 769 | // set instance options 770 | ops = {}; 771 | ops.keyboard = options.keyboard === true || keyboardData; 772 | ops.pause = (options.pause === 'hover' || pauseData) ? 'hover' : false; // false / hover 773 | ops.touch = touchOption || touchData; 774 | 775 | ops.interval = 5000; // bootstrap carousel default interval 776 | 777 | if (typeof intervalOption === 'number') { ops.interval = intervalOption; } 778 | else if (intervalOption === false || intervalData === 0 || intervalData === false) { 779 | ops.interval = 0; 780 | } else if (!Number.isNaN(intervalData)) { ops.interval = intervalData; } 781 | 782 | // set first slide active if none 783 | if (self.getActiveIndex() < 0) { 784 | if (slides.length) { slides[0].classList.add('active'); } 785 | if (indicators.length) { setActivePage(0); } 786 | } 787 | 788 | // set initial state 789 | vars = {}; 790 | vars.direction = 'left'; 791 | vars.index = 0; 792 | vars.timer = null; 793 | vars.isSliding = false; 794 | vars.isTouch = false; 795 | vars.touchPosition = { 796 | startX: 0, 797 | currentX: 0, 798 | endX: 0, 799 | }; 800 | 801 | // attach event handlers 802 | toggleEvents(1); 803 | 804 | // start to cycle if interval is set 805 | if (ops.interval) { self.cycle(); } 806 | 807 | // associate init object to target 808 | element.Carousel = self; 809 | } 810 | 811 | /* Native JavaScript for Bootstrap 4 | Collapse 812 | ----------------------------------------------- */ 813 | 814 | // COLLAPSE DEFINITION 815 | // =================== 816 | 817 | function Collapse(elem, opsInput) { 818 | var element; 819 | // set options 820 | var options = opsInput || {}; 821 | 822 | // bind 823 | var self = this; 824 | 825 | // target practice 826 | var accordion = null; 827 | var collapse = null; 828 | var activeCollapse; 829 | var activeElement; 830 | // custom events 831 | var showCustomEvent; 832 | var shownCustomEvent; 833 | var hideCustomEvent; 834 | var hiddenCustomEvent; 835 | 836 | // private methods 837 | function openAction(collapseElement, toggle) { 838 | dispatchCustomEvent.call(collapseElement, showCustomEvent); 839 | if (showCustomEvent.defaultPrevented) { return; } 840 | collapseElement.isAnimating = true; 841 | collapseElement.classList.add('collapsing'); 842 | collapseElement.classList.remove('collapse'); 843 | collapseElement.style.height = (collapseElement.scrollHeight) + "px"; 844 | 845 | emulateTransitionEnd(collapseElement, function () { 846 | collapseElement.isAnimating = false; 847 | collapseElement.setAttribute('aria-expanded', 'true'); 848 | toggle.setAttribute('aria-expanded', 'true'); 849 | collapseElement.classList.remove('collapsing'); 850 | collapseElement.classList.add('collapse'); 851 | collapseElement.classList.add('show'); 852 | collapseElement.style.height = ''; 853 | dispatchCustomEvent.call(collapseElement, shownCustomEvent); 854 | }); 855 | } 856 | function closeAction(collapseElement, toggle) { 857 | dispatchCustomEvent.call(collapseElement, hideCustomEvent); 858 | if (hideCustomEvent.defaultPrevented) { return; } 859 | collapseElement.isAnimating = true; 860 | collapseElement.style.height = (collapseElement.scrollHeight) + "px"; // set height first 861 | collapseElement.classList.remove('collapse'); 862 | collapseElement.classList.remove('show'); 863 | collapseElement.classList.add('collapsing'); 864 | reflow(collapseElement); // force reflow to enable transition 865 | collapseElement.style.height = '0px'; 866 | 867 | emulateTransitionEnd(collapseElement, function () { 868 | collapseElement.isAnimating = false; 869 | collapseElement.setAttribute('aria-expanded', 'false'); 870 | toggle.setAttribute('aria-expanded', 'false'); 871 | collapseElement.classList.remove('collapsing'); 872 | collapseElement.classList.add('collapse'); 873 | collapseElement.style.height = ''; 874 | dispatchCustomEvent.call(collapseElement, hiddenCustomEvent); 875 | }); 876 | } 877 | 878 | // public methods 879 | self.toggle = function (e) { 880 | if ((e && e.target.tagName === 'A') || element.tagName === 'A') { e.preventDefault(); } 881 | if (element.contains(e.target) || e.target === element) { 882 | if (!collapse.classList.contains('show')) { self.show(); } 883 | else { self.hide(); } 884 | } 885 | }; 886 | self.hide = function () { 887 | if (collapse.isAnimating) { return; } 888 | closeAction(collapse, element); 889 | element.classList.add('collapsed'); 890 | }; 891 | self.show = function () { 892 | var assign; 893 | 894 | if (accordion) { 895 | (assign = accordion.getElementsByClassName('collapse show'), activeCollapse = assign[0]); 896 | activeElement = activeCollapse && (queryElement(("[data-target=\"#" + (activeCollapse.id) + "\"]"), accordion) 897 | || queryElement(("[href=\"#" + (activeCollapse.id) + "\"]"), accordion)); 898 | } 899 | 900 | if (!collapse.isAnimating) { 901 | if (activeElement && activeCollapse !== collapse) { 902 | closeAction(activeCollapse, activeElement); 903 | activeElement.classList.add('collapsed'); 904 | } 905 | openAction(collapse, element); 906 | element.classList.remove('collapsed'); 907 | } 908 | }; 909 | self.dispose = function () { 910 | element.removeEventListener('click', self.toggle, false); 911 | delete element.Collapse; 912 | }; 913 | 914 | // init 915 | 916 | // initialization element 917 | element = queryElement(elem); 918 | 919 | // reset on re-init 920 | if (element.Collapse) { element.Collapse.dispose(); } 921 | 922 | // DATA API 923 | var accordionData = element.getAttribute('data-parent'); 924 | 925 | // custom events 926 | showCustomEvent = bootstrapCustomEvent('show', 'collapse'); 927 | shownCustomEvent = bootstrapCustomEvent('shown', 'collapse'); 928 | hideCustomEvent = bootstrapCustomEvent('hide', 'collapse'); 929 | hiddenCustomEvent = bootstrapCustomEvent('hidden', 'collapse'); 930 | 931 | // determine targets 932 | collapse = queryElement(options.target || element.getAttribute('data-target') || element.getAttribute('href')); 933 | 934 | if (collapse !== null) { collapse.isAnimating = false; } 935 | var accordionSelector = options.parent || accordionData; 936 | if (accordionSelector) { 937 | accordion = element.closest(accordionSelector); 938 | } else { 939 | accordion = null; 940 | } 941 | 942 | // prevent adding event handlers twice 943 | if (!element.Collapse) { 944 | element.addEventListener('click', self.toggle, false); 945 | } 946 | 947 | // associate target to init object 948 | element.Collapse = self; 949 | } 950 | 951 | /** 952 | * Points the focus to a specific element. 953 | * @param {Element} element target 954 | */ 955 | function setFocus(element) { 956 | element.focus(); 957 | } 958 | 959 | /* Native JavaScript for Bootstrap 4 | Dropdown 960 | ----------------------------------------------- */ 961 | 962 | // DROPDOWN DEFINITION 963 | // =================== 964 | 965 | function Dropdown(elem, option) { 966 | var element; 967 | 968 | // bind 969 | var self = this; 970 | 971 | // custom events 972 | var showCustomEvent; 973 | var shownCustomEvent; 974 | var hideCustomEvent; 975 | var hiddenCustomEvent; 976 | // targets 977 | var relatedTarget = null; 978 | var parent; var menu; var menuItems = []; 979 | // option 980 | var persist; 981 | 982 | // preventDefault on empty anchor links 983 | function preventEmptyAnchor(anchor) { 984 | if ((anchor.hasAttribute('href') && anchor.href.slice(-1) === '#') || (anchor.parentNode 985 | && anchor.hasAttribute('href') 986 | && anchor.parentNode.href.slice(-1) === '#')) { this.preventDefault(); } 987 | } 988 | // toggle dismissible events 989 | function toggleDismiss() { 990 | var action = element.open ? 'addEventListener' : 'removeEventListener'; 991 | document[action]('click', dismissHandler, false); 992 | document[action]('keydown', preventScroll, false); 993 | document[action]('keyup', keyHandler, false); 994 | document[action]('focus', dismissHandler, false); 995 | } 996 | // handlers 997 | function dismissHandler(e) { 998 | var eventTarget = e.target; 999 | if (!eventTarget.getAttribute) { return; } // some weird FF bug #409 1000 | var hasData = ((eventTarget && (eventTarget.getAttribute('data-toggle'))) 1001 | || (eventTarget.parentNode && eventTarget.parentNode.getAttribute 1002 | && eventTarget.parentNode.getAttribute('data-toggle'))); 1003 | if (e.type === 'focus' && (eventTarget === element || eventTarget === menu || menu.contains(eventTarget))) { 1004 | return; 1005 | } 1006 | if ((eventTarget === menu || menu.contains(eventTarget)) && (persist || hasData)) { return; } 1007 | 1008 | relatedTarget = eventTarget === element || element.contains(eventTarget) ? element : null; 1009 | self.hide(); 1010 | 1011 | preventEmptyAnchor.call(e, eventTarget); 1012 | } 1013 | function clickHandler(e) { 1014 | relatedTarget = element; 1015 | self.show(); 1016 | preventEmptyAnchor.call(e, e.target); 1017 | } 1018 | function preventScroll(e) { 1019 | var key = e.which || e.keyCode; 1020 | if (key === 38 || key === 40) { e.preventDefault(); } 1021 | } 1022 | function keyHandler(e) { 1023 | var key = e.which || e.keyCode; 1024 | var activeItem = document.activeElement; 1025 | var isSameElement = activeItem === element; 1026 | var isInsideMenu = menu.contains(activeItem); 1027 | var isMenuItem = activeItem.parentNode === menu || activeItem.parentNode.parentNode === menu; 1028 | var idx = menuItems.indexOf(activeItem); 1029 | 1030 | if (isMenuItem) { // navigate up | down 1031 | if (isSameElement) { 1032 | idx = 0; 1033 | } else if (key === 38) { 1034 | idx = idx > 1 ? idx - 1 : 0; 1035 | } else if (key === 40) { 1036 | idx = idx < menuItems.length - 1 ? idx + 1 : idx; 1037 | } 1038 | 1039 | if (menuItems[idx]) { setFocus(menuItems[idx]); } 1040 | } 1041 | if (((menuItems.length && isMenuItem) // menu has items 1042 | || (!menuItems.length && (isInsideMenu || isSameElement)) // menu might be a form 1043 | || !isInsideMenu) // or the focused element is not in the menu at all 1044 | && element.open && key === 27 // menu must be open 1045 | ) { 1046 | self.toggle(); 1047 | relatedTarget = null; 1048 | } 1049 | } 1050 | 1051 | // public methods 1052 | self.show = function () { 1053 | showCustomEvent = bootstrapCustomEvent('show', 'dropdown', { relatedTarget: relatedTarget }); 1054 | dispatchCustomEvent.call(parent, showCustomEvent); 1055 | if (showCustomEvent.defaultPrevented) { return; } 1056 | 1057 | menu.classList.add('show'); 1058 | parent.classList.add('show'); 1059 | element.setAttribute('aria-expanded', true); 1060 | element.open = true; 1061 | element.removeEventListener('click', clickHandler, false); 1062 | setTimeout(function () { 1063 | setFocus(menu.getElementsByTagName('INPUT')[0] || element); // focus the first input item | element 1064 | toggleDismiss(); 1065 | shownCustomEvent = bootstrapCustomEvent('shown', 'dropdown', { relatedTarget: relatedTarget }); 1066 | dispatchCustomEvent.call(parent, shownCustomEvent); 1067 | }, 1); 1068 | }; 1069 | self.hide = function () { 1070 | hideCustomEvent = bootstrapCustomEvent('hide', 'dropdown', { relatedTarget: relatedTarget }); 1071 | dispatchCustomEvent.call(parent, hideCustomEvent); 1072 | if (hideCustomEvent.defaultPrevented) { return; } 1073 | 1074 | menu.classList.remove('show'); 1075 | parent.classList.remove('show'); 1076 | element.setAttribute('aria-expanded', false); 1077 | element.open = false; 1078 | toggleDismiss(); 1079 | setFocus(element); 1080 | setTimeout(function () { 1081 | // only re-attach handler if the init is not disposed 1082 | if (element.Dropdown) { element.addEventListener('click', clickHandler, false); } 1083 | }, 1); 1084 | 1085 | hiddenCustomEvent = bootstrapCustomEvent('hidden', 'dropdown', { relatedTarget: relatedTarget }); 1086 | dispatchCustomEvent.call(parent, hiddenCustomEvent); 1087 | }; 1088 | self.toggle = function () { 1089 | if (parent.classList.contains('show') && element.open) { self.hide(); } else { self.show(); } 1090 | }; 1091 | self.dispose = function () { 1092 | if (parent.classList.contains('show') && element.open) { self.hide(); } 1093 | element.removeEventListener('click', clickHandler, false); 1094 | delete element.Dropdown; 1095 | }; 1096 | 1097 | // init 1098 | 1099 | // initialization element 1100 | element = queryElement(elem); 1101 | 1102 | // reset on re-init 1103 | if (element.Dropdown) { element.Dropdown.dispose(); } 1104 | 1105 | // set targets 1106 | parent = element.parentNode; 1107 | menu = queryElement('.dropdown-menu', parent); 1108 | 1109 | Array.from(menu.children).forEach(function (child) { 1110 | if (child.children.length && child.children[0].tagName === 'A') { 1111 | menuItems.push(child.children[0]); 1112 | } 1113 | if (child.tagName === 'A') { menuItems.push(child); } 1114 | }); 1115 | 1116 | // prevent adding event handlers twice 1117 | if (!element.Dropdown) { 1118 | if (!('tabindex' in menu)) { menu.setAttribute('tabindex', '0'); } // Fix onblur on Chrome | Safari 1119 | element.addEventListener('click', clickHandler, false); 1120 | } 1121 | 1122 | // set option 1123 | persist = option === true || element.getAttribute('data-persist') === 'true' || false; 1124 | 1125 | // set initial state to closed 1126 | element.open = false; 1127 | 1128 | // associate element with init object 1129 | element.Dropdown = self; 1130 | } 1131 | 1132 | /* Native JavaScript for Bootstrap 4 | Modal 1133 | -------------------------------------------- */ 1134 | 1135 | // MODAL DEFINITION 1136 | // ================ 1137 | 1138 | function Modal(elem, opsInput) { // element can be the modal/triggering button 1139 | var element; 1140 | 1141 | // set options 1142 | var options = opsInput || {}; 1143 | 1144 | // bind, modal 1145 | var self = this; 1146 | var modal; 1147 | 1148 | // custom events 1149 | var showCustomEvent; 1150 | var shownCustomEvent; 1151 | var hideCustomEvent; 1152 | var hiddenCustomEvent; 1153 | // event targets and other 1154 | var relatedTarget = null; 1155 | var scrollBarWidth; 1156 | var overlay; 1157 | var overlayDelay; 1158 | 1159 | // also find fixed-top / fixed-bottom items 1160 | var fixedItems; 1161 | var ops = {}; 1162 | 1163 | // private methods 1164 | function setScrollbar() { 1165 | var bodyClassList = document.body.classList; 1166 | var openModal = bodyClassList.contains('modal-open'); 1167 | var bodyPad = parseInt(getComputedStyle(document.body).paddingRight, 10); 1168 | var docClientHeight = document.documentElement.clientHeight; 1169 | var docScrollHeight = document.documentElement.scrollHeight; 1170 | var bodyClientHeight = document.body.clientHeight; 1171 | var bodyScrollHeight = document.body.scrollHeight; 1172 | var bodyOverflow = docClientHeight !== docScrollHeight 1173 | || bodyClientHeight !== bodyScrollHeight; 1174 | var modalOverflow = modal.clientHeight !== modal.scrollHeight; 1175 | 1176 | scrollBarWidth = measureScrollbar(); 1177 | 1178 | modal.style.paddingRight = !modalOverflow && scrollBarWidth ? (scrollBarWidth + "px") : ''; 1179 | document.body.style.paddingRight = modalOverflow || bodyOverflow 1180 | ? ((bodyPad + (openModal ? 0 : scrollBarWidth)) + "px") : ''; 1181 | 1182 | if (fixedItems.length) { 1183 | fixedItems.forEach(function (fixed) { 1184 | var itemPad = getComputedStyle(fixed).paddingRight; 1185 | fixed.style.paddingRight = modalOverflow || bodyOverflow 1186 | ? ((parseInt(itemPad, 10) + (openModal ? 0 : scrollBarWidth)) + "px") 1187 | : ((parseInt(itemPad, 10)) + "px"); 1188 | }); 1189 | } 1190 | } 1191 | function resetScrollbar() { 1192 | document.body.style.paddingRight = ''; 1193 | modal.style.paddingRight = ''; 1194 | if (fixedItems.length) { 1195 | fixedItems.forEach(function (fixed) { 1196 | fixed.style.paddingRight = ''; 1197 | }); 1198 | } 1199 | } 1200 | function measureScrollbar() { 1201 | var scrollDiv = document.createElement('div'); 1202 | scrollDiv.className = 'modal-scrollbar-measure'; // this is here to stay 1203 | document.body.appendChild(scrollDiv); 1204 | var widthValue = scrollDiv.offsetWidth - scrollDiv.clientWidth; 1205 | document.body.removeChild(scrollDiv); 1206 | return widthValue; 1207 | } 1208 | function createOverlay() { 1209 | var newOverlay = document.createElement('div'); 1210 | overlay = queryElement('.modal-backdrop'); 1211 | 1212 | if (overlay === null) { 1213 | newOverlay.setAttribute('class', ("modal-backdrop" + (ops.animation ? ' fade' : ''))); 1214 | overlay = newOverlay; 1215 | document.body.appendChild(overlay); 1216 | } 1217 | return overlay; 1218 | } 1219 | function removeOverlay() { 1220 | overlay = queryElement('.modal-backdrop'); 1221 | if (overlay && !document.getElementsByClassName('modal show')[0]) { 1222 | document.body.removeChild(overlay); overlay = null; 1223 | } 1224 | if (overlay === null) { 1225 | document.body.classList.remove('modal-open'); 1226 | resetScrollbar(); 1227 | } 1228 | } 1229 | function toggleEvents(add) { 1230 | var action = add ? 'addEventListener' : 'removeEventListener'; 1231 | window[action]('resize', self.update, passiveHandler); 1232 | modal[action]('click', dismissHandler, false); 1233 | document[action]('keydown', keyHandler, false); 1234 | } 1235 | // triggers 1236 | function beforeShow() { 1237 | modal.style.display = 'block'; 1238 | 1239 | setScrollbar(); 1240 | if (!document.getElementsByClassName('modal show')[0]) { document.body.classList.add('modal-open'); } 1241 | 1242 | modal.classList.add('show'); 1243 | modal.setAttribute('aria-hidden', false); 1244 | 1245 | if (modal.classList.contains('fade')) { emulateTransitionEnd(modal, triggerShow); } 1246 | else { triggerShow(); } 1247 | } 1248 | function triggerShow() { 1249 | setFocus(modal); 1250 | modal.isAnimating = false; 1251 | 1252 | toggleEvents(1); 1253 | 1254 | shownCustomEvent = bootstrapCustomEvent('shown', 'modal', { relatedTarget: relatedTarget }); 1255 | dispatchCustomEvent.call(modal, shownCustomEvent); 1256 | } 1257 | function triggerHide(force) { 1258 | modal.style.display = ''; 1259 | if (element) { setFocus(element); } 1260 | 1261 | overlay = queryElement('.modal-backdrop'); 1262 | 1263 | // force can also be the transitionEvent object, we wanna make sure it's not 1264 | if (force !== 1 && overlay && overlay.classList.contains('show') && !document.getElementsByClassName('modal show')[0]) { 1265 | overlay.classList.remove('show'); 1266 | emulateTransitionEnd(overlay, removeOverlay); 1267 | } else { 1268 | removeOverlay(); 1269 | } 1270 | 1271 | toggleEvents(); 1272 | 1273 | modal.isAnimating = false; 1274 | 1275 | hiddenCustomEvent = bootstrapCustomEvent('hidden', 'modal'); 1276 | dispatchCustomEvent.call(modal, hiddenCustomEvent); 1277 | } 1278 | // handlers 1279 | function clickHandler(e) { 1280 | if (modal.isAnimating) { return; } 1281 | var clickTarget = e.target; 1282 | var modalID = "#" + (modal.getAttribute('id')); 1283 | var targetAttrValue = clickTarget.getAttribute('data-target') || clickTarget.getAttribute('href'); 1284 | var elemAttrValue = element.getAttribute('data-target') || element.getAttribute('href'); 1285 | 1286 | if (!modal.classList.contains('show') 1287 | && ((clickTarget === element && targetAttrValue === modalID) 1288 | || (element.contains(clickTarget) && elemAttrValue === modalID))) { 1289 | modal.modalTrigger = element; 1290 | relatedTarget = element; 1291 | self.show(); 1292 | e.preventDefault(); 1293 | } 1294 | } 1295 | function keyHandler(ref) { 1296 | var which = ref.which; 1297 | 1298 | if (!modal.isAnimating && ops.keyboard && which === 27 && modal.classList.contains('show')) { 1299 | self.hide(); 1300 | } 1301 | } 1302 | function dismissHandler(e) { 1303 | if (modal.isAnimating) { return; } 1304 | var clickTarget = e.target; 1305 | var hasData = clickTarget.getAttribute('data-dismiss') === 'modal'; 1306 | var parentWithData = clickTarget.closest('[data-dismiss="modal"]'); 1307 | 1308 | if (modal.classList.contains('show') && (parentWithData || hasData 1309 | || (clickTarget === modal && ops.backdrop !== 'static'))) { 1310 | self.hide(); relatedTarget = null; 1311 | e.preventDefault(); 1312 | } 1313 | } 1314 | 1315 | // public methods 1316 | self.toggle = function () { 1317 | if (modal.classList.contains('show')) { self.hide(); } else { self.show(); } 1318 | }; 1319 | self.show = function () { 1320 | if (modal.classList.contains('show') && !!modal.isAnimating) { return; } 1321 | 1322 | showCustomEvent = bootstrapCustomEvent('show', 'modal', { relatedTarget: relatedTarget }); 1323 | dispatchCustomEvent.call(modal, showCustomEvent); 1324 | 1325 | if (showCustomEvent.defaultPrevented) { return; } 1326 | 1327 | modal.isAnimating = true; 1328 | 1329 | // we elegantly hide any opened modal 1330 | var currentOpen = document.getElementsByClassName('modal show')[0]; 1331 | if (currentOpen && currentOpen !== modal) { 1332 | if (currentOpen.modalTrigger) { currentOpen.modalTrigger.Modal.hide(); } 1333 | if (currentOpen.Modal) { currentOpen.Modal.hide(); } 1334 | } 1335 | 1336 | if (ops.backdrop) { overlay = createOverlay(); } 1337 | 1338 | if (overlay && !currentOpen && !overlay.classList.contains('show')) { 1339 | reflow(overlay); 1340 | overlayDelay = getElementTransitionDuration(overlay); 1341 | overlay.classList.add('show'); 1342 | } 1343 | 1344 | if (!currentOpen) { setTimeout(beforeShow, overlay && overlayDelay ? overlayDelay : 0); } 1345 | else { beforeShow(); } 1346 | }; 1347 | self.hide = function (force) { 1348 | if (!modal.classList.contains('show')) { return; } 1349 | 1350 | hideCustomEvent = bootstrapCustomEvent('hide', 'modal'); 1351 | dispatchCustomEvent.call(modal, hideCustomEvent); 1352 | if (hideCustomEvent.defaultPrevented) { return; } 1353 | 1354 | modal.isAnimating = true; 1355 | 1356 | modal.classList.remove('show'); 1357 | modal.setAttribute('aria-hidden', true); 1358 | 1359 | if (modal.classList.contains('fade') && force !== 1) { emulateTransitionEnd(modal, triggerHide); } 1360 | else { triggerHide(); } 1361 | }; 1362 | self.setContent = function (content) { 1363 | queryElement('.modal-content', modal).innerHTML = content; 1364 | }; 1365 | self.update = function () { 1366 | if (modal.classList.contains('show')) { 1367 | setScrollbar(); 1368 | } 1369 | }; 1370 | self.dispose = function () { 1371 | self.hide(1); 1372 | if (element) { element.removeEventListener('click', clickHandler, false); delete element.Modal; } else { delete modal.Modal; } 1373 | }; 1374 | 1375 | // init 1376 | 1377 | // the modal (both JavaScript / DATA API init) / triggering button element (DATA API) 1378 | element = queryElement(elem); 1379 | 1380 | // determine modal, triggering element 1381 | var checkModal = queryElement(element.getAttribute('data-target') || element.getAttribute('href')); 1382 | modal = element.classList.contains('modal') ? element : checkModal; 1383 | 1384 | // set fixed items 1385 | fixedItems = Array.from(document.getElementsByClassName('fixed-top')) 1386 | .concat(Array.from(document.getElementsByClassName('fixed-bottom'))); 1387 | 1388 | if (element.classList.contains('modal')) { element = null; } // modal is now independent of it's triggering element 1389 | 1390 | // reset on re-init 1391 | if (element && element.Modal) { element.Modal.dispose(); } 1392 | if (modal && modal.Modal) { modal.Modal.dispose(); } 1393 | 1394 | // set options 1395 | ops.keyboard = !(options.keyboard === false || modal.getAttribute('data-keyboard') === 'false'); 1396 | ops.backdrop = options.backdrop === 'static' || modal.getAttribute('data-backdrop') === 'static' ? 'static' : true; 1397 | ops.backdrop = options.backdrop === false || modal.getAttribute('data-backdrop') === 'false' ? false : ops.backdrop; 1398 | ops.animation = !!modal.classList.contains('fade'); 1399 | ops.content = options.content; // JavaScript only 1400 | 1401 | // set an initial state of the modal 1402 | modal.isAnimating = false; 1403 | 1404 | // prevent adding event handlers over and over 1405 | // modal is independent of a triggering element 1406 | if (element && !element.Modal) { 1407 | element.addEventListener('click', clickHandler, false); 1408 | } 1409 | 1410 | if (ops.content) { 1411 | self.setContent(ops.content.trim()); 1412 | } 1413 | 1414 | // set associations 1415 | if (element) { 1416 | modal.modalTrigger = element; 1417 | element.Modal = self; 1418 | } else { 1419 | modal.Modal = self; 1420 | } 1421 | } 1422 | 1423 | /** 1424 | * A global namespace for mouse click events. 1425 | * @type {{down: string, up: string}} 1426 | */ 1427 | var mouseClickEvents = { down: 'mousedown', up: 'mouseup' }; 1428 | 1429 | /** 1430 | * Returns the `Window` / `HTML` scroll position. 1431 | * Popover, Tooltip & ScrollSpy need it. 1432 | * 1433 | * @returns {{x: number, y: number}} the scroll `{x,y}` values 1434 | */ 1435 | function getScroll() { 1436 | return { 1437 | y: window.pageYOffset || document.documentElement.scrollTop, 1438 | x: window.pageXOffset || document.documentElement.scrollLeft, 1439 | }; 1440 | } 1441 | 1442 | // both popovers and tooltips (target,tooltip,placement,elementToAppendTo) 1443 | function styleTip(link, element, originalPosition, parent) { 1444 | var tipPositions = /\b(top|bottom|left|right)+/; 1445 | var elementDimensions = { w: element.offsetWidth, h: element.offsetHeight }; 1446 | var windowWidth = (document.documentElement.clientWidth || document.body.clientWidth); 1447 | var windowHeight = (document.documentElement.clientHeight || document.body.clientHeight); 1448 | var rect = link.getBoundingClientRect(); 1449 | var scroll = parent === document.body 1450 | ? getScroll() 1451 | : { x: parent.offsetLeft + parent.scrollLeft, y: parent.offsetTop + parent.scrollTop }; 1452 | var linkDimensions = { w: rect.right - rect.left, h: rect.bottom - rect.top }; 1453 | var isPopover = element.classList.contains('popover'); 1454 | var arrow = element.getElementsByClassName('arrow')[0]; 1455 | var halfTopExceed = rect.top + linkDimensions.h / 2 - elementDimensions.h / 2 < 0; 1456 | var halfLeftExceed = rect.left + linkDimensions.w / 2 - elementDimensions.w / 2 < 0; 1457 | var halfRightExceed = rect.left + elementDimensions.w / 2 1458 | + linkDimensions.w / 2 >= windowWidth; 1459 | var halfBottomExceed = rect.top + elementDimensions.h / 2 1460 | + linkDimensions.h / 2 >= windowHeight; 1461 | var topExceed = rect.top - elementDimensions.h < 0; 1462 | var leftExceed = rect.left - elementDimensions.w < 0; 1463 | var bottomExceed = rect.top + elementDimensions.h + linkDimensions.h >= windowHeight; 1464 | var rightExceed = rect.left + elementDimensions.w + linkDimensions.w >= windowWidth; 1465 | var position = originalPosition; 1466 | 1467 | // recompute position 1468 | // first, when both left and right limits are exceeded, we fall back to top|bottom 1469 | position = (position === 'left' || position === 'right') && leftExceed && rightExceed ? 'top' : position; 1470 | position = position === 'top' && topExceed ? 'bottom' : position; 1471 | position = position === 'bottom' && bottomExceed ? 'top' : position; 1472 | position = position === 'left' && leftExceed ? 'right' : position; 1473 | position = position === 'right' && rightExceed ? 'left' : position; 1474 | 1475 | var topPosition; 1476 | var leftPosition; 1477 | var arrowTop; 1478 | var arrowLeft; 1479 | 1480 | // update tooltip/popover class 1481 | if (element.className.indexOf(position) === -1) { 1482 | element.className = element.className.replace(tipPositions, position); 1483 | } 1484 | 1485 | // we check the computed width & height and update here 1486 | var arrowWidth = arrow.offsetWidth; 1487 | var arrowHeight = arrow.offsetHeight; 1488 | 1489 | // apply styling to tooltip or popover 1490 | // secondary|side positions 1491 | if (position === 'left' || position === 'right') { 1492 | if (position === 'left') { // LEFT 1493 | leftPosition = rect.left + scroll.x - elementDimensions.w - (isPopover ? arrowWidth : 0); 1494 | } else { // RIGHT 1495 | leftPosition = rect.left + scroll.x + linkDimensions.w; 1496 | } 1497 | 1498 | // adjust top and arrow 1499 | if (halfTopExceed) { 1500 | topPosition = rect.top + scroll.y; 1501 | arrowTop = linkDimensions.h / 2 - arrowWidth; 1502 | } else if (halfBottomExceed) { 1503 | topPosition = rect.top + scroll.y - elementDimensions.h + linkDimensions.h; 1504 | arrowTop = elementDimensions.h - linkDimensions.h / 2 - arrowWidth; 1505 | } else { 1506 | topPosition = rect.top + scroll.y - elementDimensions.h / 2 + linkDimensions.h / 2; 1507 | arrowTop = elementDimensions.h / 2 - (isPopover ? arrowHeight * 0.9 : arrowHeight / 2); 1508 | } 1509 | // primary|vertical positions 1510 | } else if (position === 'top' || position === 'bottom') { 1511 | if (position === 'top') { // TOP 1512 | topPosition = rect.top + scroll.y - elementDimensions.h - (isPopover ? arrowHeight : 0); 1513 | } else { // BOTTOM 1514 | topPosition = rect.top + scroll.y + linkDimensions.h; 1515 | } 1516 | // adjust left | right and also the arrow 1517 | if (halfLeftExceed) { 1518 | leftPosition = 0; 1519 | arrowLeft = rect.left + linkDimensions.w / 2 - arrowWidth; 1520 | } else if (halfRightExceed) { 1521 | leftPosition = windowWidth - elementDimensions.w * 1.01; 1522 | arrowLeft = elementDimensions.w - (windowWidth - rect.left) 1523 | + linkDimensions.w / 2 - arrowWidth / 2; 1524 | } else { 1525 | leftPosition = rect.left + scroll.x - elementDimensions.w / 2 + linkDimensions.w / 2; 1526 | arrowLeft = elementDimensions.w / 2 - (isPopover ? arrowWidth : arrowWidth / 2); 1527 | } 1528 | } 1529 | 1530 | // apply style to tooltip/popover and its arrow 1531 | element.style.top = topPosition + "px"; 1532 | element.style.left = leftPosition + "px"; 1533 | 1534 | if (arrowTop) { arrow.style.top = arrowTop + "px"; } 1535 | if (arrowLeft) { arrow.style.left = arrowLeft + "px"; } 1536 | } 1537 | 1538 | /* Native JavaScript for Bootstrap 4 | Popover 1539 | ---------------------------------------------- */ 1540 | 1541 | // POPOVER DEFINITION 1542 | // ================== 1543 | 1544 | function Popover(elem, opsInput) { 1545 | var element; 1546 | // set instance options 1547 | var options = opsInput || {}; 1548 | 1549 | // bind 1550 | var self = this; 1551 | 1552 | // popover and timer 1553 | var popover = null; 1554 | var timer = 0; 1555 | var isIphone = /(iPhone|iPod|iPad)/.test(navigator.userAgent); 1556 | // title and content 1557 | var titleString; 1558 | var contentString; 1559 | var placementClass; 1560 | 1561 | // options 1562 | var ops = {}; 1563 | 1564 | // close btn for dissmissible popover 1565 | var closeBtn; 1566 | 1567 | // custom events 1568 | var showCustomEvent; 1569 | var shownCustomEvent; 1570 | var hideCustomEvent; 1571 | var hiddenCustomEvent; 1572 | 1573 | // handlers 1574 | function dismissibleHandler(e) { 1575 | if (popover !== null && e.target === queryElement('.close', popover)) { 1576 | self.hide(); 1577 | } 1578 | } 1579 | // private methods 1580 | function getAttr(att) { 1581 | return options[att] || element.dataset[att] || null; 1582 | } 1583 | function getTitle() { 1584 | return getAttr('title'); 1585 | } 1586 | function getContent() { 1587 | return getAttr('content'); 1588 | } 1589 | function removePopover() { 1590 | ops.container.removeChild(popover); 1591 | timer = null; popover = null; 1592 | } 1593 | 1594 | function createPopover() { 1595 | titleString = getTitle(); 1596 | contentString = getContent(); 1597 | // fixing https://github.com/thednp/bootstrap.native/issues/233 1598 | contentString = contentString ? contentString.trim() : null; 1599 | 1600 | popover = document.createElement('div'); 1601 | 1602 | // popover arrow 1603 | var popoverArrow = document.createElement('div'); 1604 | popoverArrow.classList.add('arrow'); 1605 | popover.appendChild(popoverArrow); 1606 | 1607 | // create the popover from data attributes 1608 | if (contentString !== null && ops.template === null) { 1609 | popover.setAttribute('role', 'tooltip'); 1610 | 1611 | if (titleString !== null) { 1612 | var popoverTitle = document.createElement('h3'); 1613 | popoverTitle.classList.add('popover-header'); 1614 | popoverTitle.innerHTML = ops.dismissible ? titleString + closeBtn : titleString; 1615 | popover.appendChild(popoverTitle); 1616 | } 1617 | 1618 | // set popover content 1619 | var popoverBodyMarkup = document.createElement('div'); 1620 | popoverBodyMarkup.classList.add('popover-body'); 1621 | popoverBodyMarkup.innerHTML = ops.dismissible && titleString === null 1622 | ? contentString + closeBtn 1623 | : contentString; 1624 | popover.appendChild(popoverBodyMarkup); 1625 | } else { // or create the popover from template 1626 | var popoverTemplate = document.createElement('div'); 1627 | popoverTemplate.innerHTML = ops.template.trim(); 1628 | popover.className = popoverTemplate.firstChild.className; 1629 | popover.innerHTML = popoverTemplate.firstChild.innerHTML; 1630 | 1631 | var popoverHeader = queryElement('.popover-header', popover); 1632 | var popoverBody = queryElement('.popover-body', popover); 1633 | 1634 | // fill the template with content from data attributes 1635 | if (titleString && popoverHeader) { popoverHeader.innerHTML = titleString.trim(); } 1636 | if (contentString && popoverBody) { popoverBody.innerHTML = contentString.trim(); } 1637 | } 1638 | 1639 | // append to the container 1640 | ops.container.appendChild(popover); 1641 | popover.style.display = 'block'; 1642 | if (!popover.classList.contains('popover')) { popover.classList.add('popover'); } 1643 | if (!popover.classList.contains(ops.animation)) { popover.classList.add(ops.animation); } 1644 | if (!popover.classList.contains(placementClass)) { popover.classList.add(placementClass); } 1645 | } 1646 | function showPopover() { 1647 | if (!popover.classList.contains('show')) { popover.classList.add('show'); } 1648 | } 1649 | function updatePopover() { 1650 | styleTip(element, popover, ops.placement, ops.container); 1651 | } 1652 | function forceFocus() { 1653 | if (popover === null) { element.focus(); } 1654 | } 1655 | function toggleEvents(add) { 1656 | var action = add ? 'addEventListener' : 'removeEventListener'; 1657 | if (ops.trigger === 'hover') { 1658 | element[action](mouseClickEvents.down, self.show); 1659 | element[action](mouseHoverEvents[0], self.show); 1660 | // mouseHover = ('onmouseleave' in document) 1661 | // ? [ 'mouseenter', 'mouseleave'] 1662 | // : [ 'mouseover', 'mouseout' ] 1663 | if (!ops.dismissible) { element[action](mouseHoverEvents[1], self.hide); } 1664 | } else if (ops.trigger === 'click') { 1665 | element[action](ops.trigger, self.toggle); 1666 | } else if (ops.trigger === 'focus') { 1667 | if (isIphone) { element[action]('click', forceFocus, false); } 1668 | element[action](ops.trigger, self.toggle); 1669 | } 1670 | } 1671 | function touchHandler(e) { 1672 | if ((popover && popover.contains(e.target)) 1673 | || e.target === element || element.contains(e.target)) ; else { 1674 | self.hide(); 1675 | } 1676 | } 1677 | // event toggle 1678 | function dismissHandlerToggle(add) { 1679 | var action = add ? 'addEventListener' : 'removeEventListener'; 1680 | if (ops.dismissible) { 1681 | document[action]('click', dismissibleHandler, false); 1682 | } else { 1683 | if (ops.trigger === 'focus') { element[action]('blur', self.hide); } 1684 | if (ops.trigger === 'hover') { document[action]('touchstart', touchHandler, passiveHandler); } 1685 | } 1686 | window[action]('resize', self.hide, passiveHandler); 1687 | } 1688 | // triggers 1689 | function showTrigger() { 1690 | dismissHandlerToggle(1); 1691 | dispatchCustomEvent.call(element, shownCustomEvent); 1692 | } 1693 | function hideTrigger() { 1694 | dismissHandlerToggle(); 1695 | removePopover(); 1696 | dispatchCustomEvent.call(element, hiddenCustomEvent); 1697 | } 1698 | 1699 | // public methods / handlers 1700 | self.toggle = function () { 1701 | if (popover === null) { self.show(); } 1702 | else { self.hide(); } 1703 | }; 1704 | self.show = function () { 1705 | clearTimeout(timer); 1706 | timer = setTimeout(function () { 1707 | if (popover === null) { 1708 | dispatchCustomEvent.call(element, showCustomEvent); 1709 | if (showCustomEvent.defaultPrevented) { return; } 1710 | 1711 | createPopover(); 1712 | updatePopover(); 1713 | showPopover(); 1714 | if (ops.animation) { emulateTransitionEnd(popover, showTrigger); } 1715 | else { showTrigger(); } 1716 | } 1717 | }, 20); 1718 | }; 1719 | self.hide = function () { 1720 | clearTimeout(timer); 1721 | timer = setTimeout(function () { 1722 | if (popover && popover !== null && popover.classList.contains('show')) { 1723 | dispatchCustomEvent.call(element, hideCustomEvent); 1724 | if (hideCustomEvent.defaultPrevented) { return; } 1725 | popover.classList.remove('show'); 1726 | if (ops.animation) { emulateTransitionEnd(popover, hideTrigger); } 1727 | else { hideTrigger(); } 1728 | } 1729 | }, ops.delay); 1730 | }; 1731 | self.dispose = function () { 1732 | self.hide(); 1733 | toggleEvents(); 1734 | delete element.Popover; 1735 | }; 1736 | 1737 | // INIT 1738 | // initialization element 1739 | element = queryElement(elem); 1740 | 1741 | // reset on re-init 1742 | if (element.Popover) { element.Popover.dispose(); } 1743 | 1744 | // DATA API 1745 | var triggerData = element.getAttribute('data-trigger'); // click / hover / focus 1746 | var animationData = element.getAttribute('data-animation'); // true / false 1747 | 1748 | var placementData = element.getAttribute('data-placement'); 1749 | var dismissibleData = element.getAttribute('data-dismissible'); 1750 | var delayData = element.getAttribute('data-delay'); 1751 | var containerData = element.getAttribute('data-container'); 1752 | 1753 | // close btn for dissmissible popover 1754 | closeBtn = ''; 1755 | 1756 | // custom events 1757 | showCustomEvent = bootstrapCustomEvent('show', 'popover'); 1758 | shownCustomEvent = bootstrapCustomEvent('shown', 'popover'); 1759 | hideCustomEvent = bootstrapCustomEvent('hide', 'popover'); 1760 | hiddenCustomEvent = bootstrapCustomEvent('hidden', 'popover'); 1761 | 1762 | // check container 1763 | var containerElement = queryElement(options.container); 1764 | var containerDataElement = queryElement(containerData); 1765 | 1766 | // maybe the element is inside a modal 1767 | var modal = element.closest('.modal'); 1768 | 1769 | // maybe the element is inside a fixed navbar 1770 | var navbarFixedTop = element.closest('.fixed-top'); 1771 | var navbarFixedBottom = element.closest('.fixed-bottom'); 1772 | 1773 | // set instance options 1774 | ops.template = options.template ? options.template : null; // JavaScript only 1775 | ops.trigger = options.trigger ? options.trigger : triggerData || 'hover'; 1776 | ops.animation = options.animation && options.animation !== 'fade' ? options.animation : animationData || 'fade'; 1777 | ops.placement = options.placement ? options.placement : placementData || 'top'; 1778 | ops.delay = parseInt((options.delay || delayData), 10) || 200; 1779 | ops.dismissible = !!(options.dismissible || dismissibleData === 'true'); 1780 | ops.container = containerElement 1781 | || (containerDataElement 1782 | || (navbarFixedTop || (navbarFixedBottom || (modal || document.body)))); 1783 | 1784 | placementClass = "bs-popover-" + (ops.placement); 1785 | 1786 | // invalidate 1787 | titleString = getTitle(); 1788 | contentString = getContent(); 1789 | 1790 | if (!contentString && !ops.template) { return; } 1791 | 1792 | // init 1793 | if (!element.Popover) { // prevent adding event handlers twice 1794 | toggleEvents(1); 1795 | } 1796 | 1797 | // associate target to init object 1798 | element.Popover = self; 1799 | } 1800 | 1801 | /* Native JavaScript for Bootstrap 5 | ScrollSpy 1802 | ------------------------------------------------ */ 1803 | 1804 | // SCROLLSPY DEFINITION 1805 | // ==================== 1806 | 1807 | function ScrollSpy(elem, opsInput) { 1808 | var element; 1809 | 1810 | // set options 1811 | var options = opsInput || {}; 1812 | 1813 | // bind 1814 | var self = this; 1815 | 1816 | // GC internals 1817 | var vars; 1818 | var links; 1819 | 1820 | // targets 1821 | var spyTarget; 1822 | // determine which is the real scrollTarget 1823 | var scrollTarget; 1824 | // options 1825 | var ops = {}; 1826 | 1827 | // private methods 1828 | // populate items and targets 1829 | function updateTargets() { 1830 | links = spyTarget.getElementsByTagName('A'); 1831 | 1832 | vars.scrollTop = vars.isWindow ? getScroll().y : element.scrollTop; 1833 | 1834 | // only update vars once or with each mutation 1835 | if (vars.length !== links.length || getScrollHeight() !== vars.scrollHeight) { 1836 | var href; 1837 | var targetItem; 1838 | var rect; 1839 | 1840 | // reset arrays & update 1841 | vars.items = []; 1842 | vars.offsets = []; 1843 | vars.scrollHeight = getScrollHeight(); 1844 | vars.maxScroll = vars.scrollHeight - getOffsetHeight(); 1845 | 1846 | Array.from(links).forEach(function (link) { 1847 | href = link.getAttribute('href'); 1848 | targetItem = href && href.charAt(0) === '#' && href.slice(-1) !== '#' && queryElement(href); 1849 | 1850 | if (targetItem) { 1851 | vars.items.push(link); 1852 | rect = targetItem.getBoundingClientRect(); 1853 | vars.offsets.push((vars.isWindow 1854 | ? rect.top + vars.scrollTop 1855 | : targetItem.offsetTop) - ops.offset); 1856 | } 1857 | }); 1858 | vars.length = vars.items.length; 1859 | } 1860 | } 1861 | // item update 1862 | function toggleEvents(add) { 1863 | var action = add ? 'addEventListener' : 'removeEventListener'; 1864 | scrollTarget[action]('scroll', self.refresh, passiveHandler); 1865 | window[action]('resize', self.refresh, passiveHandler); 1866 | } 1867 | function getScrollHeight() { 1868 | return scrollTarget.scrollHeight || Math.max( 1869 | document.body.scrollHeight, 1870 | document.documentElement.scrollHeight 1871 | ); 1872 | } 1873 | function getOffsetHeight() { 1874 | return !vars.isWindow ? element.getBoundingClientRect().height : window.innerHeight; 1875 | } 1876 | function clear() { 1877 | Array.from(links).map(function (item) { return item.classList.contains('active') && item.classList.remove('active'); }); 1878 | } 1879 | function activate(input) { 1880 | var item = input; 1881 | var itemClassList; 1882 | clear(); 1883 | vars.activeItem = item; 1884 | item.classList.add('active'); 1885 | 1886 | // activate all parents 1887 | var parents = []; 1888 | while (item.parentNode !== document.body) { 1889 | item = item.parentNode; 1890 | itemClassList = item.classList; 1891 | 1892 | if (itemClassList.contains('dropdown-menu') || itemClassList.contains('nav')) { parents.push(item); } 1893 | } 1894 | 1895 | parents.forEach(function (menuItem) { 1896 | var parentLink = menuItem.previousElementSibling; 1897 | 1898 | if (parentLink && !parentLink.classList.contains('active')) { 1899 | parentLink.classList.add('active'); 1900 | } 1901 | }); 1902 | 1903 | dispatchCustomEvent.call(element, bootstrapCustomEvent('activate', 'scrollspy', { relatedTarget: vars.activeItem })); 1904 | } 1905 | 1906 | // public method 1907 | self.refresh = function () { 1908 | updateTargets(); 1909 | 1910 | if (vars.scrollTop >= vars.maxScroll) { 1911 | var newActiveItem = vars.items[vars.length - 1]; 1912 | 1913 | if (vars.activeItem !== newActiveItem) { 1914 | activate(newActiveItem); 1915 | } 1916 | 1917 | return; 1918 | } 1919 | 1920 | if (vars.activeItem && vars.scrollTop < vars.offsets[0] && vars.offsets[0] > 0) { 1921 | vars.activeItem = null; 1922 | clear(); 1923 | return; 1924 | } 1925 | 1926 | var i = vars.length; 1927 | while (i > -1) { 1928 | if (vars.activeItem !== vars.items[i] && vars.scrollTop >= vars.offsets[i] 1929 | && (typeof vars.offsets[i + 1] === 'undefined' || vars.scrollTop < vars.offsets[i + 1])) { 1930 | activate(vars.items[i]); 1931 | } 1932 | i -= 1; 1933 | } 1934 | }; 1935 | self.dispose = function () { 1936 | toggleEvents(); 1937 | delete element.ScrollSpy; 1938 | }; 1939 | 1940 | // init 1941 | // initialization element, the element we spy on 1942 | element = queryElement(elem); 1943 | 1944 | // reset on re-init 1945 | if (element.ScrollSpy) { element.ScrollSpy.dispose(); } 1946 | 1947 | // event targets, constants 1948 | // DATA API 1949 | var targetData = element.getAttribute('data-target'); 1950 | var offsetData = element.getAttribute('data-offset'); 1951 | 1952 | // targets 1953 | spyTarget = queryElement(options.target || targetData); 1954 | 1955 | // determine which is the real scrollTarget 1956 | scrollTarget = element.clientHeight < element.scrollHeight ? element : window; 1957 | 1958 | if (!spyTarget) { return; } 1959 | 1960 | // set instance option 1961 | ops.offset = +(options.offset || offsetData) || 10; 1962 | 1963 | // set instance priority variables 1964 | vars = {}; 1965 | vars.length = 0; 1966 | vars.items = []; 1967 | vars.offsets = []; 1968 | vars.isWindow = scrollTarget === window; 1969 | vars.activeItem = null; 1970 | vars.scrollHeight = 0; 1971 | vars.maxScroll = 0; 1972 | 1973 | // prevent adding event handlers twice 1974 | if (!element.ScrollSpy) { toggleEvents(1); } 1975 | 1976 | self.refresh(); 1977 | 1978 | // associate target with init object 1979 | element.ScrollSpy = self; 1980 | } 1981 | 1982 | /* Native JavaScript for Bootstrap 4 | Tab 1983 | ------------------------------------------ */ 1984 | 1985 | // TAB DEFINITION 1986 | // ============== 1987 | 1988 | function Tab(elem, opsInput) { 1989 | var element; 1990 | // set options 1991 | var options = opsInput || {}; 1992 | 1993 | // bind 1994 | var self = this; 1995 | 1996 | // event targets 1997 | var tabs; 1998 | var dropdown; 1999 | 2000 | // custom events 2001 | var showCustomEvent; 2002 | var shownCustomEvent; 2003 | var hideCustomEvent; 2004 | var hiddenCustomEvent; 2005 | 2006 | // more GC material 2007 | var next; 2008 | var tabsContentContainer = false; 2009 | var activeTab; 2010 | var activeContent; 2011 | var nextContent; 2012 | var containerHeight; 2013 | var equalContents; 2014 | var nextHeight; 2015 | 2016 | // triggers 2017 | function triggerEnd() { 2018 | tabsContentContainer.style.height = ''; 2019 | tabsContentContainer.classList.remove('collapsing'); 2020 | tabs.isAnimating = false; 2021 | } 2022 | function triggerShow() { 2023 | if (tabsContentContainer) { // height animation 2024 | if (equalContents) { 2025 | triggerEnd(); 2026 | } else { 2027 | setTimeout(function () { // enables height animation 2028 | tabsContentContainer.style.height = nextHeight + "px"; // height animation 2029 | reflow(tabsContentContainer); 2030 | emulateTransitionEnd(tabsContentContainer, triggerEnd); 2031 | }, 50); 2032 | } 2033 | } else { 2034 | tabs.isAnimating = false; 2035 | } 2036 | shownCustomEvent = bootstrapCustomEvent('shown', 'tab', { relatedTarget: activeTab }); 2037 | dispatchCustomEvent.call(next, shownCustomEvent); 2038 | } 2039 | function triggerHide() { 2040 | if (tabsContentContainer) { 2041 | activeContent.style.float = 'left'; 2042 | nextContent.style.float = 'left'; 2043 | containerHeight = activeContent.scrollHeight; 2044 | } 2045 | 2046 | showCustomEvent = bootstrapCustomEvent('show', 'tab', { relatedTarget: activeTab }); 2047 | hiddenCustomEvent = bootstrapCustomEvent('hidden', 'tab', { relatedTarget: next }); 2048 | 2049 | dispatchCustomEvent.call(next, showCustomEvent); 2050 | if (showCustomEvent.defaultPrevented) { return; } 2051 | 2052 | nextContent.classList.add('active'); 2053 | 2054 | activeContent.classList.remove('active'); 2055 | 2056 | if (tabsContentContainer) { 2057 | nextHeight = nextContent.scrollHeight; 2058 | equalContents = nextHeight === containerHeight; 2059 | tabsContentContainer.classList.add('collapsing'); 2060 | tabsContentContainer.style.height = containerHeight + "px"; // height animation 2061 | reflow(tabsContentContainer); 2062 | activeContent.style.float = ''; 2063 | nextContent.style.float = ''; 2064 | } 2065 | 2066 | if (nextContent.classList.contains('fade')) { 2067 | setTimeout(function () { 2068 | nextContent.classList.add('show'); 2069 | emulateTransitionEnd(nextContent, triggerShow); 2070 | }, 20); 2071 | } else { triggerShow(); } 2072 | 2073 | dispatchCustomEvent.call(activeTab, hiddenCustomEvent); 2074 | } 2075 | // private methods 2076 | function getActiveTab() { 2077 | var assign; 2078 | 2079 | var activeTabs = tabs.getElementsByClassName('active'); 2080 | 2081 | if (activeTabs.length === 1 && !activeTabs[0].parentNode.classList.contains('dropdown')) { 2082 | (assign = activeTabs, activeTab = assign[0]); 2083 | } else if (activeTabs.length > 1) { 2084 | activeTab = activeTabs[activeTabs.length - 1]; 2085 | } 2086 | return activeTab; 2087 | } 2088 | function getActiveContent() { return queryElement(getActiveTab().getAttribute('href')); } 2089 | // handler 2090 | function clickHandler(e) { 2091 | e.preventDefault(); 2092 | next = e.currentTarget; 2093 | if (!tabs.isAnimating) { self.show(); } 2094 | } 2095 | 2096 | // public method 2097 | self.show = function () { // the tab we clicked is now the next tab 2098 | next = next || element; 2099 | 2100 | if (!next.classList.contains('active')) { 2101 | nextContent = queryElement(next.getAttribute('href')); // this is the actual object, the next tab content to activate 2102 | activeTab = getActiveTab(); 2103 | activeContent = getActiveContent(); 2104 | 2105 | hideCustomEvent = bootstrapCustomEvent('hide', 'tab', { relatedTarget: next }); 2106 | dispatchCustomEvent.call(activeTab, hideCustomEvent); 2107 | if (hideCustomEvent.defaultPrevented) { return; } 2108 | 2109 | tabs.isAnimating = true; 2110 | activeTab.classList.remove('active'); 2111 | activeTab.setAttribute('aria-selected', 'false'); 2112 | next.classList.add('active'); 2113 | next.setAttribute('aria-selected', 'true'); 2114 | 2115 | if (dropdown) { 2116 | if (!element.parentNode.classList.contains('dropdown-menu')) { 2117 | if (dropdown.classList.contains('active')) { dropdown.classList.remove('active'); } 2118 | } else if (!dropdown.classList.contains('active')) { dropdown.classList.add('active'); } 2119 | } 2120 | 2121 | if (activeContent.classList.contains('fade')) { 2122 | activeContent.classList.remove('show'); 2123 | emulateTransitionEnd(activeContent, triggerHide); 2124 | } else { triggerHide(); } 2125 | } 2126 | }; 2127 | self.dispose = function () { 2128 | element.removeEventListener('click', clickHandler, false); 2129 | delete element.Tab; 2130 | }; 2131 | 2132 | // INIT 2133 | // initialization element 2134 | element = queryElement(elem); 2135 | 2136 | // reset on re-init 2137 | if (element.Tab) { element.Tab.dispose(); } 2138 | 2139 | // DATA API 2140 | var heightData = element.getAttribute('data-height'); 2141 | // event targets 2142 | tabs = element.closest('.nav'); 2143 | dropdown = tabs && queryElement('.dropdown-toggle', tabs); 2144 | 2145 | // instance options 2146 | var animateHeight = !(!supportTransition || (options.height === false || heightData === 'false')); 2147 | 2148 | // set default animation state 2149 | tabs.isAnimating = false; 2150 | 2151 | // init 2152 | if (!element.Tab) { // prevent adding event handlers twice 2153 | element.addEventListener('click', clickHandler, false); 2154 | } 2155 | 2156 | if (animateHeight) { tabsContentContainer = getActiveContent().parentNode; } 2157 | 2158 | // associate target with init object 2159 | element.Tab = self; 2160 | } 2161 | 2162 | /* Native JavaScript for Bootstrap 4 | Toast 2163 | -------------------------------------------- */ 2164 | 2165 | // TOAST DEFINITION 2166 | // ================== 2167 | 2168 | function Toast(elem, opsInput) { 2169 | var element; 2170 | 2171 | // set options 2172 | var options = opsInput || {}; 2173 | 2174 | // bind 2175 | var self = this; 2176 | 2177 | // toast, timer 2178 | var toast; 2179 | var timer = 0; 2180 | 2181 | // custom events 2182 | var showCustomEvent; 2183 | var hideCustomEvent; 2184 | var shownCustomEvent; 2185 | var hiddenCustomEvent; 2186 | var ops = {}; 2187 | 2188 | // private methods 2189 | function showComplete() { 2190 | toast.classList.remove('showing'); 2191 | toast.classList.add('show'); 2192 | dispatchCustomEvent.call(toast, shownCustomEvent); 2193 | if (ops.autohide) { self.hide(); } 2194 | } 2195 | function hideComplete() { 2196 | toast.classList.add('hide'); 2197 | dispatchCustomEvent.call(toast, hiddenCustomEvent); 2198 | } 2199 | function close() { 2200 | toast.classList.remove('show'); 2201 | if (ops.animation) { emulateTransitionEnd(toast, hideComplete); } 2202 | else { hideComplete(); } 2203 | } 2204 | function disposeComplete() { 2205 | clearTimeout(timer); 2206 | element.removeEventListener('click', self.hide, false); 2207 | 2208 | delete element.Toast; 2209 | } 2210 | 2211 | // public methods 2212 | self.show = function () { 2213 | if (toast && !toast.classList.contains('show')) { 2214 | dispatchCustomEvent.call(toast, showCustomEvent); 2215 | if (showCustomEvent.defaultPrevented) { return; } 2216 | if (ops.animation) { toast.classList.add('fade'); } 2217 | toast.classList.remove('hide'); 2218 | reflow(toast); // force reflow 2219 | toast.classList.add('showing'); 2220 | 2221 | if (ops.animation) { emulateTransitionEnd(toast, showComplete); } 2222 | else { showComplete(); } 2223 | } 2224 | }; 2225 | self.hide = function (noTimer) { 2226 | if (toast && toast.classList.contains('show')) { 2227 | dispatchCustomEvent.call(toast, hideCustomEvent); 2228 | if (hideCustomEvent.defaultPrevented) { return; } 2229 | 2230 | if (noTimer) { close(); } 2231 | else { timer = setTimeout(close, ops.delay); } 2232 | } 2233 | }; 2234 | self.dispose = function () { 2235 | if (ops.animation) { emulateTransitionEnd(toast, disposeComplete); } 2236 | else { disposeComplete(); } 2237 | }; 2238 | 2239 | // init 2240 | 2241 | // initialization element 2242 | element = queryElement(elem); 2243 | 2244 | // reset on re-init 2245 | if (element.Toast) { element.Toast.dispose(); } 2246 | 2247 | // toast, timer 2248 | toast = element.closest('.toast'); 2249 | 2250 | // DATA API 2251 | var animationData = element.getAttribute('data-animation'); 2252 | var autohideData = element.getAttribute('data-autohide'); 2253 | var delayData = element.getAttribute('data-delay'); 2254 | 2255 | // custom events 2256 | showCustomEvent = bootstrapCustomEvent('show', 'toast'); 2257 | hideCustomEvent = bootstrapCustomEvent('hide', 'toast'); 2258 | shownCustomEvent = bootstrapCustomEvent('shown', 'toast'); 2259 | hiddenCustomEvent = bootstrapCustomEvent('hidden', 'toast'); 2260 | 2261 | // set instance options 2262 | ops.animation = options.animation === false || animationData === 'false' ? 0 : 1; // true by default 2263 | ops.autohide = options.autohide === false || autohideData === 'false' ? 0 : 1; // true by default 2264 | ops.delay = parseInt((options.delay || delayData), 10) || 500; // 500ms default 2265 | 2266 | if (!element.Toast) { // prevent adding event handlers twice 2267 | element.addEventListener('click', self.hide, false); 2268 | } 2269 | 2270 | // associate targets to init object 2271 | element.Toast = self; 2272 | } 2273 | 2274 | /* Native JavaScript for Bootstrap 4 | Tooltip 2275 | ---------------------------------------------- */ 2276 | 2277 | // TOOLTIP DEFINITION 2278 | // ================== 2279 | 2280 | function Tooltip(elem, opsInput) { 2281 | var element; 2282 | // set options 2283 | var options = opsInput || {}; 2284 | 2285 | // bind 2286 | var self = this; 2287 | 2288 | // tooltip, timer, and title 2289 | var tooltip = null; 2290 | var timer = 0; 2291 | var titleString; 2292 | var placementClass; 2293 | 2294 | // custom events 2295 | var showCustomEvent; 2296 | var shownCustomEvent; 2297 | var hideCustomEvent; 2298 | var hiddenCustomEvent; 2299 | 2300 | var ops = {}; 2301 | 2302 | // private methods 2303 | function getTitle() { 2304 | return element.getAttribute('title') 2305 | || element.getAttribute('data-title') 2306 | || element.getAttribute('data-original-title'); 2307 | } 2308 | function removeToolTip() { 2309 | ops.container.removeChild(tooltip); 2310 | tooltip = null; timer = null; 2311 | } 2312 | function createToolTip() { 2313 | titleString = getTitle(); // read the title again 2314 | if (titleString) { // invalidate, maybe markup changed 2315 | // create tooltip 2316 | tooltip = document.createElement('div'); 2317 | 2318 | // set markup 2319 | if (ops.template) { 2320 | var tooltipMarkup = document.createElement('div'); 2321 | tooltipMarkup.innerHTML = ops.template.trim(); 2322 | 2323 | tooltip.className = tooltipMarkup.firstChild.className; 2324 | tooltip.innerHTML = tooltipMarkup.firstChild.innerHTML; 2325 | 2326 | queryElement('.tooltip-inner', tooltip).innerHTML = titleString.trim(); 2327 | } else { 2328 | // tooltip arrow 2329 | var tooltipArrow = document.createElement('div'); 2330 | tooltipArrow.classList.add('arrow'); 2331 | tooltip.appendChild(tooltipArrow); 2332 | // tooltip inner 2333 | var tooltipInner = document.createElement('div'); 2334 | tooltipInner.classList.add('tooltip-inner'); 2335 | tooltip.appendChild(tooltipInner); 2336 | tooltipInner.innerHTML = titleString; 2337 | } 2338 | // reset position 2339 | tooltip.style.left = '0'; 2340 | tooltip.style.top = '0'; 2341 | // set class and role attribute 2342 | tooltip.setAttribute('role', 'tooltip'); 2343 | if (!tooltip.classList.contains('tooltip')) { tooltip.classList.add('tooltip'); } 2344 | if (!tooltip.classList.contains(ops.animation)) { tooltip.classList.add(ops.animation); } 2345 | if (!tooltip.classList.contains(placementClass)) { tooltip.classList.add(placementClass); } 2346 | // append to container 2347 | ops.container.appendChild(tooltip); 2348 | } 2349 | } 2350 | function updateTooltip() { 2351 | styleTip(element, tooltip, ops.placement, ops.container); 2352 | } 2353 | function showTooltip() { 2354 | if (!tooltip.classList.contains('show')) { tooltip.classList.add('show'); } 2355 | } 2356 | function touchHandler(e) { 2357 | if ((tooltip && tooltip.contains(e.target)) 2358 | || e.target === element || element.contains(e.target)) ; else { 2359 | self.hide(); 2360 | } 2361 | } 2362 | // triggers 2363 | function toggleAction(add) { 2364 | var action = add ? 'addEventListener' : 'removeEventListener'; 2365 | document[action]('touchstart', touchHandler, passiveHandler); 2366 | window[action]('resize', self.hide, passiveHandler); 2367 | } 2368 | function showAction() { 2369 | toggleAction(1); 2370 | dispatchCustomEvent.call(element, shownCustomEvent); 2371 | } 2372 | function hideAction() { 2373 | toggleAction(); 2374 | removeToolTip(); 2375 | dispatchCustomEvent.call(element, hiddenCustomEvent); 2376 | } 2377 | function toggleEvents(add) { 2378 | var action = add ? 'addEventListener' : 'removeEventListener'; 2379 | element[action](mouseClickEvents.down, self.show, false); 2380 | element[action](mouseHoverEvents[0], self.show, false); 2381 | element[action](mouseHoverEvents[1], self.hide, false); 2382 | } 2383 | 2384 | // public methods 2385 | self.show = function () { 2386 | clearTimeout(timer); 2387 | timer = setTimeout(function () { 2388 | if (tooltip === null) { 2389 | dispatchCustomEvent.call(element, showCustomEvent); 2390 | if (showCustomEvent.defaultPrevented) { return; } 2391 | // if(createToolTip() == false) return; 2392 | if (createToolTip() !== false) { 2393 | updateTooltip(); 2394 | showTooltip(); 2395 | if (ops.animation) { emulateTransitionEnd(tooltip, showAction); } 2396 | else { showAction(); } 2397 | } 2398 | } 2399 | }, 20); 2400 | }; 2401 | self.hide = function () { 2402 | clearTimeout(timer); 2403 | timer = setTimeout(function () { 2404 | if (tooltip && tooltip.classList.contains('show')) { 2405 | dispatchCustomEvent.call(element, hideCustomEvent); 2406 | if (hideCustomEvent.defaultPrevented) { return; } 2407 | tooltip.classList.remove('show'); 2408 | if (ops.animation) { emulateTransitionEnd(tooltip, hideAction); } 2409 | else { hideAction(); } 2410 | } 2411 | }, ops.delay); 2412 | }; 2413 | self.toggle = function () { 2414 | if (!tooltip) { self.show(); } 2415 | else { self.hide(); } 2416 | }; 2417 | self.dispose = function () { 2418 | toggleEvents(); 2419 | self.hide(); 2420 | element.setAttribute('title', element.getAttribute('data-original-title')); 2421 | element.removeAttribute('data-original-title'); 2422 | delete element.Tooltip; 2423 | }; 2424 | 2425 | // init 2426 | // initialization element 2427 | element = queryElement(elem); 2428 | 2429 | // reset on re-init 2430 | if (element.Tooltip) { element.Tooltip.dispose(); } 2431 | 2432 | // DATA API 2433 | var animationData = element.getAttribute('data-animation'); 2434 | var placementData = element.getAttribute('data-placement'); 2435 | var delayData = element.getAttribute('data-delay'); 2436 | var containerData = element.getAttribute('data-container'); 2437 | 2438 | // check container 2439 | var containerElement = queryElement(options.container); 2440 | var containerDataElement = queryElement(containerData); 2441 | 2442 | // maybe the element is inside a modal 2443 | var modal = element.closest('.modal'); 2444 | 2445 | // custom events 2446 | showCustomEvent = bootstrapCustomEvent('show', 'tooltip'); 2447 | shownCustomEvent = bootstrapCustomEvent('shown', 'tooltip'); 2448 | hideCustomEvent = bootstrapCustomEvent('hide', 'tooltip'); 2449 | hiddenCustomEvent = bootstrapCustomEvent('hidden', 'tooltip'); 2450 | 2451 | // maybe the element is inside a fixed navbar 2452 | var navbarFixedTop = element.closest('.fixed-top'); 2453 | var navbarFixedBottom = element.closest('.fixed-bottom'); 2454 | 2455 | // set instance options 2456 | ops.animation = options.animation && options.animation !== 'fade' ? options.animation : animationData || 'fade'; 2457 | ops.placement = options.placement ? options.placement : placementData || 'top'; 2458 | ops.template = options.template ? options.template : null; // JavaScript only 2459 | ops.delay = parseInt((options.delay || delayData), 10) || 200; 2460 | ops.container = containerElement 2461 | || (containerDataElement 2462 | || (navbarFixedTop || (navbarFixedBottom || (modal || document.body)))); 2463 | 2464 | // set placement class 2465 | placementClass = "bs-tooltip-" + (ops.placement); 2466 | 2467 | // set tooltip content 2468 | titleString = getTitle(); 2469 | 2470 | // invalidate 2471 | if (!titleString) { return; } 2472 | 2473 | // prevent adding event handlers twice 2474 | if (!element.Tooltip) { 2475 | element.setAttribute('data-original-title', titleString); 2476 | element.removeAttribute('title'); 2477 | toggleEvents(1); 2478 | } 2479 | 2480 | // associate target to init object 2481 | element.Tooltip = self; 2482 | } 2483 | 2484 | /** BSN v4 componentsInit */ 2485 | var componentsInit = {}; 2486 | 2487 | /* Native JavaScript for Bootstrap v4 | Initialize Data API 2488 | -------------------------------------------------------- */ 2489 | function initializeDataAPI(Constructor, collection) { 2490 | Array.from(collection).map(function (x) { return new Constructor(x); }); 2491 | } 2492 | function initCallback(context) { 2493 | var lookUp = context instanceof Element ? context : document; 2494 | Object.keys(componentsInit).forEach(function (component) { 2495 | initializeDataAPI(componentsInit[component][0], 2496 | lookUp.querySelectorAll(componentsInit[component][1])); 2497 | }); 2498 | } 2499 | 2500 | componentsInit.Alert = [Alert, '[data-dismiss="alert"]']; 2501 | componentsInit.Button = [Button, '[data-toggle="buttons"]']; 2502 | componentsInit.Carousel = [Carousel, '[data-ride="carousel"]']; 2503 | componentsInit.Collapse = [Collapse, '[data-toggle="collapse"]']; 2504 | componentsInit.Dropdown = [Dropdown, '[data-toggle="dropdown"]']; 2505 | componentsInit.Modal = [Modal, '[data-toggle="modal"]']; 2506 | componentsInit.Popover = [Popover, '[data-toggle="popover"],[data-tip="popover"]']; 2507 | componentsInit.ScrollSpy = [ScrollSpy, '[data-spy="scroll"]']; 2508 | componentsInit.Tab = [Tab, '[data-toggle="tab"]']; 2509 | componentsInit.Toast = [Toast, '[data-dismiss="toast"]']; 2510 | componentsInit.Tooltip = [Tooltip, '[data-toggle="tooltip"],[data-tip="tooltip"]']; 2511 | 2512 | // bulk initialize all components 2513 | if (document.body) { initCallback(); } 2514 | else { 2515 | document.addEventListener('DOMContentLoaded', function initWrapper() { 2516 | initCallback(); 2517 | document.removeEventListener('DOMContentLoaded', initWrapper, false); 2518 | }, false); 2519 | } 2520 | 2521 | /* Native JavaScript for Bootstrap v4 | Remove Data API 2522 | ---------------------------------------------------- */ 2523 | function removeElementDataAPI(ConstructorName, collection) { 2524 | Array.from(collection).map(function (x) { return x[ConstructorName].dispose(); }); 2525 | } 2526 | function removeDataAPI(context) { 2527 | var lookUp = context instanceof Element ? context : document; 2528 | Object.keys(componentsInit).forEach(function (component) { 2529 | removeElementDataAPI(component, lookUp.querySelectorAll(componentsInit[component][1])); 2530 | }); 2531 | } 2532 | 2533 | var version = "4.1.0"; 2534 | 2535 | var Version = version; 2536 | 2537 | var BSN = { 2538 | Alert: Alert, 2539 | Button: Button, 2540 | Carousel: Carousel, 2541 | Collapse: Collapse, 2542 | Dropdown: Dropdown, 2543 | Modal: Modal, 2544 | Popover: Popover, 2545 | ScrollSpy: ScrollSpy, 2546 | Tab: Tab, 2547 | Toast: Toast, 2548 | Tooltip: Tooltip, 2549 | 2550 | initCallback: initCallback, 2551 | removeDataAPI: removeDataAPI, 2552 | componentsInit: componentsInit, 2553 | Version: Version, 2554 | }; 2555 | 2556 | return BSN; 2557 | 2558 | })); 2559 | --------------------------------------------------------------------------------