├── 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 |
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 |
12 |
13 | ### ❷ Assign CSS Property — background-image: url(…)
14 |
15 |
16 |
17 | ### ❸ Canvas Element
18 |
19 |
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 |
33 |
34 |
35 |
36 | | Dimensions Width × Height |
37 | |
38 |
39 |
40 | | Available Dimensions Width × Height |
41 | |
42 |
43 |
44 | | Color Depth Bit Depth |
45 | |
46 |
47 |
48 | | Color Resolution |
49 | |
50 |
51 |
52 | | Device to CSS Pixel Ratio |
53 | |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | | Name |
67 | |
68 |
69 |
70 | | Size |
71 | |
72 |
73 |
74 | | Type |
75 | |
76 |
77 |
78 | | Dimensions Width × Height |
79 | |
80 |
81 |
82 | | Last Modified |
83 | |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
98 |
99 |
105 |
106 |
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 |
--------------------------------------------------------------------------------