good_job 1.2.3 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +127 -0
  3. data/README.md +324 -160
  4. data/engine/app/controllers/good_job/active_jobs_controller.rb +8 -0
  5. data/engine/app/controllers/good_job/base_controller.rb +5 -0
  6. data/engine/app/controllers/good_job/dashboards_controller.rb +50 -0
  7. data/engine/app/helpers/good_job/application_helper.rb +4 -0
  8. data/engine/app/views/assets/_style.css.erb +16 -0
  9. data/engine/app/views/good_job/active_jobs/show.html.erb +1 -0
  10. data/engine/app/views/good_job/dashboards/index.html.erb +19 -0
  11. data/engine/app/views/layouts/good_job/base.html.erb +61 -0
  12. data/engine/app/views/shared/_chart.erb +51 -0
  13. data/engine/app/views/shared/_jobs_table.erb +26 -0
  14. data/engine/app/views/vendor/bootstrap/_bootstrap-native.js.erb +1662 -0
  15. data/engine/app/views/vendor/bootstrap/_bootstrap.css.erb +10258 -0
  16. data/engine/app/views/vendor/chartist/_chartist.css.erb +613 -0
  17. data/engine/app/views/vendor/chartist/_chartist.js.erb +4516 -0
  18. data/engine/config/routes.rb +4 -0
  19. data/engine/lib/good_job/engine.rb +5 -0
  20. data/lib/active_job/queue_adapters/good_job_adapter.rb +3 -2
  21. data/lib/generators/good_job/install_generator.rb +8 -0
  22. data/lib/good_job.rb +59 -27
  23. data/lib/good_job/adapter.rb +41 -0
  24. data/lib/good_job/cli.rb +70 -14
  25. data/lib/good_job/configuration.rb +61 -2
  26. data/lib/good_job/job.rb +126 -36
  27. data/lib/good_job/lockable.rb +126 -13
  28. data/lib/good_job/log_subscriber.rb +80 -16
  29. data/lib/good_job/multi_scheduler.rb +6 -0
  30. data/lib/good_job/notifier.rb +57 -31
  31. data/lib/good_job/performer.rb +38 -0
  32. data/lib/good_job/poller.rb +94 -0
  33. data/lib/good_job/railtie.rb +1 -0
  34. data/lib/good_job/scheduler.rb +54 -81
  35. data/lib/good_job/version.rb +2 -1
  36. metadata +154 -11
  37. data/lib/good_job/pg_locks.rb +0 -21
@@ -0,0 +1,8 @@
1
+ module GoodJob
2
+ class ActiveJobsController < GoodJob::BaseController
3
+ def show
4
+ @jobs = GoodJob::Job.where("serialized_params ->> 'job_id' = ?", params[:id])
5
+ .order('COALESCE(scheduled_at, created_at) DESC')
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module GoodJob
2
+ class BaseController < ActionController::Base # rubocop:disable Rails/ApplicationController
3
+ protect_from_forgery with: :exception
4
+ end
5
+ end
@@ -0,0 +1,50 @@
1
+ module GoodJob
2
+ class DashboardsController < GoodJob::BaseController
3
+ def index
4
+ @jobs = GoodJob::Job.display_all(after_scheduled_at: params[:after_scheduled_at], after_id: params[:after_id])
5
+ .limit(params.fetch(:limit, 10))
6
+
7
+ job_data = GoodJob::Job.connection.exec_query Arel.sql(<<~SQL.squish)
8
+ SELECT *
9
+ FROM generate_series(
10
+ date_trunc('hour', NOW() - '1 day'::interval),
11
+ date_trunc('hour', NOW()),
12
+ '1 hour'
13
+ ) timestamp
14
+ LEFT JOIN (
15
+ SELECT
16
+ date_trunc('hour', scheduled_at) AS scheduled_at,
17
+ queue_name,
18
+ count(*) AS count
19
+ FROM (
20
+ SELECT
21
+ COALESCE(scheduled_at, created_at)::timestamp AS scheduled_at,
22
+ queue_name
23
+ FROM good_jobs
24
+ ) sources
25
+ GROUP BY date_trunc('hour', scheduled_at), queue_name
26
+ ) sources ON sources.scheduled_at = timestamp
27
+ ORDER BY timestamp DESC
28
+ SQL
29
+
30
+ queue_names = job_data.map { |d| d['queue_name'] }.uniq
31
+ labels = []
32
+ queues_data = job_data.to_a.group_by { |d| d['timestamp'] }.each_with_object({}) do |(timestamp, values), hash|
33
+ labels << timestamp
34
+ queue_names.each do |queue_name|
35
+ (hash[queue_name] ||= []) << values.find { |d| d['queue_name'] == queue_name }&.[]('count')
36
+ end
37
+ end
38
+
39
+ @chart = {
40
+ labels: labels,
41
+ series: queues_data.map do |queue, data|
42
+ {
43
+ name: queue,
44
+ data: data,
45
+ }
46
+ end,
47
+ }
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,4 @@
1
+ module GoodJob
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,16 @@
1
+ .tooltip {
2
+ position: absolute;
3
+ z-index: 1;
4
+ padding: 5px;
5
+ background: rgba(0, 0, 0, 0.3);
6
+ opacity: 1;
7
+ border-radius: 3px;
8
+ text-align: center;
9
+ pointer-events: none;
10
+ color: white;
11
+ transition: opacity .1s ease-out;
12
+ }
13
+
14
+ .tooltip.tooltip-hidden {
15
+ opacity: 0;
16
+ }
@@ -0,0 +1 @@
1
+ <%= render 'shared/jobs_table', jobs: @jobs %>
@@ -0,0 +1,19 @@
1
+ <div class="card my-3 p-6">
2
+ <%= render 'shared/chart', chart_data: @chart %>
3
+ </div>
4
+
5
+ <% if @jobs.present? %>
6
+ <%= render 'shared/jobs_table', jobs: @jobs %>
7
+
8
+ <nav aria-label="Job pagination">
9
+ <ul class="pagination">
10
+ <li class="page-item">
11
+ <%= link_to({ after_scheduled_at: (@jobs.last.scheduled_at || @jobs.last.created_at), after_id: @jobs.last.id }, class: "page-link") do %>
12
+ Next jobs <span aria-hidden="true">&raquo;</span>
13
+ <% end %>
14
+ </li>
15
+ </ul>
16
+ </nav>
17
+ <% else %>
18
+ <em>No jobs present.</em>
19
+ <% end %>
@@ -0,0 +1,61 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Good job</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <style>
9
+ <%= render "vendor/bootstrap/bootstrap.css" %>
10
+ <%= render "vendor/chartist/chartist.css" %>
11
+ <%= render "assets/style.css" %>
12
+ </style>
13
+
14
+ <script>
15
+ <%= render "vendor/bootstrap/bootstrap-native.js" %>
16
+ <%= render "vendor/chartist/chartist.js" %>
17
+ </script>
18
+ </head>
19
+ <body>
20
+ <nav class="navbar navbar-expand-lg navbar-light bg-light">
21
+ <div class="container">
22
+ <%= link_to "GoodJob 👍", root_path, class: 'navbar-brand mb-0 h1' %>
23
+ <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
24
+ <span class="navbar-toggler-icon"></span>
25
+ </button>
26
+
27
+ <div class="collapse navbar-collapse" id="navbarSupportedContent">
28
+ <ul class="navbar-nav mr-auto">
29
+ <li class="nav-item">
30
+ <%= link_to root_path, class: ["nav-link", ("active" if current_page?(root_path))] do %>
31
+ All jobs <span class="badge badge-secondary">More views coming soon</span>
32
+ <% end %>
33
+ </li>
34
+
35
+ <!-- Coming Soon
36
+ <li class="nav-item">
37
+ <%= link_to "Upcoming Jobs", 'todo', class: ["nav-link", ("active" if current_page?('todo'))] %>
38
+ </li>
39
+ <li class="nav-item">
40
+ <%= link_to "Finished Jobs", 'todo', class: ["nav-link", ("active" if current_page?('todo'))] %>
41
+ </li>
42
+ <li class="nav-item">
43
+ <%= link_to "Errored Jobs", 'todo', class: ["nav-link", ("active" if current_page?('todo'))] %>
44
+ </li>
45
+ -->
46
+ </ul>
47
+ </div>
48
+ </div>
49
+ </nav>
50
+
51
+ <div class="container">
52
+ <div class="card border-warning text-dark my-3">
53
+ <div class="card-body">
54
+ <p class="card-text">🚧 GoodJob's dashboard is a work in progress. Please contribute ideas and code on <a href="https://github.com/bensheldon/good_job/issues" target="_blank" rel="nofollow noopener noreferrer">Github</a>.</p>
55
+ </div>
56
+ </div>
57
+
58
+ <%= yield %>
59
+ </div>
60
+ </body>
61
+ </html>
@@ -0,0 +1,51 @@
1
+ <div id="chart"></div>
2
+
3
+ <script>
4
+ new Chartist.Line('#chart', <%= raw chart_data.to_json %>, {
5
+ height: '300px',
6
+ fullWidth: true,
7
+ chartPadding: {
8
+ right: 40,
9
+ top: 20
10
+ },
11
+ axisX: {
12
+ labelInterpolationFnc: function(value, index) {
13
+ return index % 3 === 0 ? value : null;
14
+ }
15
+ },
16
+ axisY: {
17
+ low: 0,
18
+ onlyInteger: true
19
+ }
20
+ })
21
+
22
+ // https://www.smashingmagazine.com/2014/12/chartist-js-open-source-library-responsive-charts/
23
+ const chartEl = document.getElementById('chart');
24
+ const tooltipEl = document.createElement('div')
25
+
26
+ tooltipEl.classList.add('tooltip', 'tooltip-hidden');
27
+ chartEl.appendChild(tooltipEl);
28
+
29
+ document.body.addEventListener('mouseenter', function (event) {
30
+ if (!(event.target.matches && event.target.matches('.ct-point'))) return;
31
+
32
+ const seriesName = event.target.closest('.ct-series').getAttribute('ct:series-name');
33
+ const value = event.target.getAttribute('ct:value');
34
+
35
+ tooltipEl.innerText = seriesName + ': ' + value;
36
+ tooltipEl.classList.remove('tooltip-hidden');
37
+ }, true);
38
+
39
+ document.body.addEventListener('mouseleave', function (event) {
40
+ if (!(event.target.matches && event.target.matches('.ct-point'))) return;
41
+
42
+ tooltipEl.classList.add('tooltip-hidden');
43
+ }, true);
44
+
45
+ document.body.addEventListener('mousemove', function(event) {
46
+ if (!(event.target.matches && event.target.matches('.ct-point'))) return;
47
+
48
+ tooltipEl.style.left = (event.offsetX || event.originalEvent.layerX) + tooltipEl.offsetWidth + 10 + 'px';
49
+ tooltipEl.style.top = (event.offsetY || event.originalEvent.layerY) + tooltipEl.offsetHeight - 20 + 'px';
50
+ }, true);
51
+ </script>
@@ -0,0 +1,26 @@
1
+ <div class="table-responsive">
2
+ <table class="table table-bordered table-hover">
3
+ <thead>
4
+ <th>GoodJob ID</th>
5
+ <th>ActiveJob ID</th>
6
+ <th>Job Class</th>
7
+ <th>Queue</th>
8
+ <th>Scheduled At</th>
9
+ <th>Error</th>
10
+ <th>ActiveJob Params</th>
11
+ </thead>
12
+ <tbody>
13
+ <% jobs.each do |job| %>
14
+ <tr id="<%= dom_id(job) %>">
15
+ <td><%= link_to job.id, active_job_path(job.serialized_params['job_id'], anchor: dom_id(job)) %></td>
16
+ <td><%= link_to job.serialized_params['job_id'], active_job_path(job.serialized_params['job_id']) %></td>
17
+ <td><%= job.serialized_params['job_class'] %></td>
18
+ <td><%= job.queue_name %></td>
19
+ <td><%= job.scheduled_at || job.created_at %></td>
20
+ <td><%= job.error %></td>
21
+ <td><pre><%= JSON.pretty_generate(job.serialized_params) %></pre></td>
22
+ </tr>
23
+ <% end %>
24
+ </tbody>
25
+ </table>
26
+ </div>
@@ -0,0 +1,1662 @@
1
+ /*!
2
+ * Native JavaScript for Bootstrap v3.0.10 (https://thednp.github.io/bootstrap.native/)
3
+ * Copyright 2015-2020 © 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 = global || self, global.BSN = factory());
10
+ }(this, (function () { 'use strict';
11
+
12
+ var transitionEndEvent = 'webkitTransition' in document.head.style ? 'webkitTransitionEnd' : 'transitionend';
13
+
14
+ var supportTransition = 'webkitTransition' in document.head.style || 'transition' in document.head.style;
15
+
16
+ var transitionDuration = 'webkitTransition' in document.head.style ? 'webkitTransitionDuration' : 'transitionDuration';
17
+
18
+ function getElementTransitionDuration(element) {
19
+ var duration = supportTransition ? parseFloat(getComputedStyle(element)[transitionDuration]) : 0;
20
+ duration = typeof duration === 'number' && !isNaN(duration) ? duration * 1000 : 0;
21
+ return duration;
22
+ }
23
+
24
+ function emulateTransitionEnd(element,handler){
25
+ var called = 0, duration = getElementTransitionDuration(element);
26
+ duration ? element.addEventListener( transitionEndEvent, function transitionEndWrapper(e){
27
+ !called && handler(e), called = 1;
28
+ element.removeEventListener( transitionEndEvent, transitionEndWrapper);
29
+ })
30
+ : setTimeout(function() { !called && handler(), called = 1; }, 17);
31
+ }
32
+
33
+ function queryElement(selector, parent) {
34
+ var lookUp = parent && parent instanceof Element ? parent : document;
35
+ return selector instanceof Element ? selector : lookUp.querySelector(selector);
36
+ }
37
+
38
+ function bootstrapCustomEvent(eventName, componentName, related) {
39
+ var OriginalCustomEvent = new CustomEvent( eventName + '.bs.' + componentName, {cancelable: true});
40
+ OriginalCustomEvent.relatedTarget = related;
41
+ return OriginalCustomEvent;
42
+ }
43
+
44
+ function dispatchCustomEvent(customEvent){
45
+ this && this.dispatchEvent(customEvent);
46
+ }
47
+
48
+ function Alert(element) {
49
+ var self = this,
50
+ alert,
51
+ closeCustomEvent = bootstrapCustomEvent('close','alert'),
52
+ closedCustomEvent = bootstrapCustomEvent('closed','alert');
53
+ function triggerHandler() {
54
+ alert.classList.contains('fade') ? emulateTransitionEnd(alert,transitionEndHandler) : transitionEndHandler();
55
+ }
56
+ function toggleEvents(action){
57
+ action = action ? 'addEventListener' : 'removeEventListener';
58
+ element[action]('click',clickHandler,false);
59
+ }
60
+ function clickHandler(e) {
61
+ alert = e && e.target.closest(".alert");
62
+ element = queryElement('[data-dismiss="alert"]',alert);
63
+ element && alert && (element === e.target || element.contains(e.target)) && self.close();
64
+ }
65
+ function transitionEndHandler() {
66
+ toggleEvents();
67
+ alert.parentNode.removeChild(alert);
68
+ dispatchCustomEvent.call(alert,closedCustomEvent);
69
+ }
70
+ self.close = function () {
71
+ if ( alert && element && alert.classList.contains('show') ) {
72
+ dispatchCustomEvent.call(alert,closeCustomEvent);
73
+ if ( closeCustomEvent.defaultPrevented ) { return; }
74
+ self.dispose();
75
+ alert.classList.remove('show');
76
+ triggerHandler();
77
+ }
78
+ };
79
+ self.dispose = function () {
80
+ toggleEvents();
81
+ delete element.Alert;
82
+ };
83
+ element = queryElement(element);
84
+ alert = element.closest('.alert');
85
+ element.Alert && element.Alert.dispose();
86
+ if ( !element.Alert ) {
87
+ toggleEvents(1);
88
+ }
89
+ self.element = element;
90
+ element.Alert = self;
91
+ }
92
+
93
+ function Button(element) {
94
+ var self = this, labels,
95
+ changeCustomEvent = bootstrapCustomEvent('change', 'button');
96
+ function toggle(e) {
97
+ var input,
98
+ label = e.target.tagName === 'LABEL' ? e.target
99
+ : e.target.closest('LABEL') ? e.target.closest('LABEL') : null;
100
+ input = label && label.getElementsByTagName('INPUT')[0];
101
+ if ( !input ) { return; }
102
+ dispatchCustomEvent.call(input, changeCustomEvent);
103
+ dispatchCustomEvent.call(element, changeCustomEvent);
104
+ if ( input.type === 'checkbox' ) {
105
+ if ( changeCustomEvent.defaultPrevented ) { return; }
106
+ if ( !input.checked ) {
107
+ label.classList.add('active');
108
+ input.getAttribute('checked');
109
+ input.setAttribute('checked','checked');
110
+ input.checked = true;
111
+ } else {
112
+ label.classList.remove('active');
113
+ input.getAttribute('checked');
114
+ input.removeAttribute('checked');
115
+ input.checked = false;
116
+ }
117
+ if (!element.toggled) {
118
+ element.toggled = true;
119
+ }
120
+ }
121
+ if ( input.type === 'radio' && !element.toggled ) {
122
+ if ( changeCustomEvent.defaultPrevented ) { return; }
123
+ if ( !input.checked || (e.screenX === 0 && e.screenY == 0) ) {
124
+ label.classList.add('active');
125
+ label.classList.add('focus');
126
+ input.setAttribute('checked','checked');
127
+ input.checked = true;
128
+ element.toggled = true;
129
+ Array.from(labels).map(function (otherLabel){
130
+ var otherInput = otherLabel.getElementsByTagName('INPUT')[0];
131
+ if ( otherLabel !== label && otherLabel.classList.contains('active') ) {
132
+ dispatchCustomEvent.call(otherInput, changeCustomEvent);
133
+ otherLabel.classList.remove('active');
134
+ otherInput.removeAttribute('checked');
135
+ otherInput.checked = false;
136
+ }
137
+ });
138
+ }
139
+ }
140
+ setTimeout( function () { element.toggled = false; }, 50 );
141
+ }
142
+ function keyHandler(e) {
143
+ var key = e.which || e.keyCode;
144
+ key === 32 && e.target === document.activeElement && toggle(e);
145
+ }
146
+ function preventScroll(e) {
147
+ var key = e.which || e.keyCode;
148
+ key === 32 && e.preventDefault();
149
+ }
150
+ function focusToggle(e) {
151
+ if (e.target.tagName === 'INPUT' ) {
152
+ var action = e.type === 'focusin' ? 'add' : 'remove';
153
+ e.target.closest('.btn').classList[action]('focus');
154
+ }
155
+ }
156
+ function toggleEvents(action) {
157
+ action = action ? 'addEventListener' : 'removeEventListener';
158
+ element[action]('click',toggle,false );
159
+ element[action]('keyup',keyHandler,false), element[action]('keydown',preventScroll,false);
160
+ element[action]('focusin',focusToggle,false), element[action]('focusout',focusToggle,false);
161
+ }
162
+ self.dispose = function () {
163
+ toggleEvents();
164
+ delete element.Button;
165
+ };
166
+ element = queryElement(element);
167
+ element.Button && element.Button.dispose();
168
+ labels = element.getElementsByClassName('btn');
169
+ if (!labels.length) { return; }
170
+ if ( !element.Button ) {
171
+ toggleEvents(1);
172
+ }
173
+ element.toggled = false;
174
+ element.Button = self;
175
+ Array.from(labels).map(function (btn){
176
+ !btn.classList.contains('active')
177
+ && queryElement('input:checked',btn)
178
+ && btn.classList.add('active');
179
+ btn.classList.contains('active')
180
+ && !queryElement('input:checked',btn)
181
+ && btn.classList.remove('active');
182
+ });
183
+ }
184
+
185
+ var mouseHoverEvents = ('onmouseleave' in document) ? [ 'mouseenter', 'mouseleave'] : [ 'mouseover', 'mouseout' ];
186
+
187
+ var supportPassive = (function () {
188
+ var result = false;
189
+ try {
190
+ var opts = Object.defineProperty({}, 'passive', {
191
+ get: function() {
192
+ result = true;
193
+ }
194
+ });
195
+ document.addEventListener('DOMContentLoaded', function wrap(){
196
+ document.removeEventListener('DOMContentLoaded', wrap, opts);
197
+ }, opts);
198
+ } catch (e) {}
199
+ return result;
200
+ })();
201
+
202
+ var passiveHandler = supportPassive ? { passive: true } : false;
203
+
204
+ function isElementInScrollRange(element) {
205
+ var bcr = element.getBoundingClientRect(),
206
+ viewportHeight = window.innerHeight || document.documentElement.clientHeight;
207
+ return bcr.top <= viewportHeight && bcr.bottom >= 0;
208
+ }
209
+
210
+ function Carousel (element,options) {
211
+ options = options || {};
212
+ var self = this,
213
+ vars, ops,
214
+ slideCustomEvent, slidCustomEvent,
215
+ slides, leftArrow, rightArrow, indicator, indicators;
216
+ function pauseHandler() {
217
+ if ( ops.interval !==false && !element.classList.contains('paused') ) {
218
+ element.classList.add('paused');
219
+ !vars.isSliding && ( clearInterval(vars.timer), vars.timer = null );
220
+ }
221
+ }
222
+ function resumeHandler() {
223
+ if ( ops.interval !== false && element.classList.contains('paused') ) {
224
+ element.classList.remove('paused');
225
+ !vars.isSliding && ( clearInterval(vars.timer), vars.timer = null );
226
+ !vars.isSliding && self.cycle();
227
+ }
228
+ }
229
+ function indicatorHandler(e) {
230
+ e.preventDefault();
231
+ if (vars.isSliding) { return; }
232
+ var eventTarget = e.target;
233
+ if ( eventTarget && !eventTarget.classList.contains('active') && eventTarget.getAttribute('data-slide-to') ) {
234
+ vars.index = parseInt( eventTarget.getAttribute('data-slide-to'));
235
+ } else { return false; }
236
+ self.slideTo( vars.index );
237
+ }
238
+ function controlsHandler(e) {
239
+ e.preventDefault();
240
+ if (vars.isSliding) { return; }
241
+ var eventTarget = e.currentTarget || e.srcElement;
242
+ if ( eventTarget === rightArrow ) {
243
+ vars.index++;
244
+ } else if ( eventTarget === leftArrow ) {
245
+ vars.index--;
246
+ }
247
+ self.slideTo( vars.index );
248
+ }
249
+ function keyHandler(ref) {
250
+ var which = ref.which;
251
+ if (vars.isSliding) { return; }
252
+ switch (which) {
253
+ case 39:
254
+ vars.index++;
255
+ break;
256
+ case 37:
257
+ vars.index--;
258
+ break;
259
+ default: return;
260
+ }
261
+ self.slideTo( vars.index );
262
+ }
263
+ function toggleEvents(action) {
264
+ action = action ? 'addEventListener' : 'removeEventListener';
265
+ if ( ops.pause && ops.interval ) {
266
+ element[action]( mouseHoverEvents[0], pauseHandler, false );
267
+ element[action]( mouseHoverEvents[1], resumeHandler, false );
268
+ element[action]( 'touchstart', pauseHandler, passiveHandler );
269
+ element[action]( 'touchend', resumeHandler, passiveHandler );
270
+ }
271
+ ops.touch && slides.length > 1 && element[action]( 'touchstart', touchDownHandler, passiveHandler );
272
+ rightArrow && rightArrow[action]( 'click', controlsHandler,false );
273
+ leftArrow && leftArrow[action]( 'click', controlsHandler,false );
274
+ indicator && indicator[action]( 'click', indicatorHandler,false );
275
+ ops.keyboard && window[action]( 'keydown', keyHandler,false );
276
+ }
277
+ function toggleTouchEvents(action) {
278
+ action = action ? 'addEventListener' : 'removeEventListener';
279
+ element[action]( 'touchmove', touchMoveHandler, passiveHandler );
280
+ element[action]( 'touchend', touchEndHandler, passiveHandler );
281
+ }
282
+ function touchDownHandler(e) {
283
+ if ( vars.isTouch ) { return; }
284
+ vars.touchPosition.startX = e.changedTouches[0].pageX;
285
+ if ( element.contains(e.target) ) {
286
+ vars.isTouch = true;
287
+ toggleTouchEvents(1);
288
+ }
289
+ }
290
+ function touchMoveHandler(e) {
291
+ if ( !vars.isTouch ) { e.preventDefault(); return; }
292
+ vars.touchPosition.currentX = e.changedTouches[0].pageX;
293
+ if ( e.type === 'touchmove' && e.changedTouches.length > 1 ) {
294
+ e.preventDefault();
295
+ return false;
296
+ }
297
+ }
298
+ function touchEndHandler (e) {
299
+ if ( !vars.isTouch || vars.isSliding ) { return }
300
+ vars.touchPosition.endX = vars.touchPosition.currentX || e.changedTouches[0].pageX;
301
+ if ( vars.isTouch ) {
302
+ if ( (!element.contains(e.target) || !element.contains(e.relatedTarget) )
303
+ && Math.abs(vars.touchPosition.startX - vars.touchPosition.endX) < 75 ) {
304
+ return false;
305
+ } else {
306
+ if ( vars.touchPosition.currentX < vars.touchPosition.startX ) {
307
+ vars.index++;
308
+ } else if ( vars.touchPosition.currentX > vars.touchPosition.startX ) {
309
+ vars.index--;
310
+ }
311
+ vars.isTouch = false;
312
+ self.slideTo(vars.index);
313
+ }
314
+ toggleTouchEvents();
315
+ }
316
+ }
317
+ function setActivePage(pageIndex) {
318
+ Array.from(indicators).map(function (x){x.classList.remove('active');});
319
+ indicators[pageIndex] && indicators[pageIndex].classList.add('active');
320
+ }
321
+ function transitionEndHandler(e){
322
+ if (vars.touchPosition){
323
+ var next = vars.index,
324
+ timeout = e && e.target !== slides[next] ? e.elapsedTime*1000+100 : 20,
325
+ activeItem = self.getActiveIndex(),
326
+ orientation = vars.direction === 'left' ? 'next' : 'prev';
327
+ vars.isSliding && setTimeout(function () {
328
+ if (vars.touchPosition){
329
+ vars.isSliding = false;
330
+ slides[next].classList.add('active');
331
+ slides[activeItem].classList.remove('active');
332
+ slides[next].classList.remove(("carousel-item-" + orientation));
333
+ slides[next].classList.remove(("carousel-item-" + (vars.direction)));
334
+ slides[activeItem].classList.remove(("carousel-item-" + (vars.direction)));
335
+ dispatchCustomEvent.call(element, slidCustomEvent);
336
+ if ( !document.hidden && ops.interval && !element.classList.contains('paused') ) {
337
+ self.cycle();
338
+ }
339
+ }
340
+ }, timeout);
341
+ }
342
+ }
343
+ self.cycle = function () {
344
+ if (vars.timer) {
345
+ clearInterval(vars.timer);
346
+ vars.timer = null;
347
+ }
348
+ vars.timer = setInterval(function () {
349
+ var idx = vars.index || self.getActiveIndex();
350
+ isElementInScrollRange(element) && (idx++, self.slideTo( idx ) );
351
+ }, ops.interval);
352
+ };
353
+ self.slideTo = function (next) {
354
+ if (vars.isSliding) { return; }
355
+ var activeItem = self.getActiveIndex(), orientation;
356
+ if ( activeItem === next ) {
357
+ return;
358
+ } else if ( (activeItem < next ) || (activeItem === 0 && next === slides.length -1 ) ) {
359
+ vars.direction = 'left';
360
+ } else if ( (activeItem > next) || (activeItem === slides.length - 1 && next === 0 ) ) {
361
+ vars.direction = 'right';
362
+ }
363
+ if ( next < 0 ) { next = slides.length - 1; }
364
+ else if ( next >= slides.length ){ next = 0; }
365
+ orientation = vars.direction === 'left' ? 'next' : 'prev';
366
+ slideCustomEvent = bootstrapCustomEvent('slide', 'carousel', slides[next]);
367
+ slidCustomEvent = bootstrapCustomEvent('slid', 'carousel', slides[next]);
368
+ dispatchCustomEvent.call(element, slideCustomEvent);
369
+ if (slideCustomEvent.defaultPrevented) { return; }
370
+ vars.index = next;
371
+ vars.isSliding = true;
372
+ clearInterval(vars.timer);
373
+ vars.timer = null;
374
+ setActivePage( next );
375
+ if ( getElementTransitionDuration(slides[next]) && element.classList.contains('slide') ) {
376
+ slides[next].classList.add(("carousel-item-" + orientation));
377
+ slides[next].offsetWidth;
378
+ slides[next].classList.add(("carousel-item-" + (vars.direction)));
379
+ slides[activeItem].classList.add(("carousel-item-" + (vars.direction)));
380
+ emulateTransitionEnd(slides[next], transitionEndHandler);
381
+ } else {
382
+ slides[next].classList.add('active');
383
+ slides[next].offsetWidth;
384
+ slides[activeItem].classList.remove('active');
385
+ setTimeout(function () {
386
+ vars.isSliding = false;
387
+ if ( ops.interval && element && !element.classList.contains('paused') ) {
388
+ self.cycle();
389
+ }
390
+ dispatchCustomEvent.call(element, slidCustomEvent);
391
+ }, 100 );
392
+ }
393
+ };
394
+ self.getActiveIndex = function () { return Array.from(slides).indexOf(element.getElementsByClassName('carousel-item active')[0]) || 0; };
395
+ self.dispose = function () {
396
+ var itemClasses = ['left','right','prev','next'];
397
+ Array.from(slides).map(function (slide,idx) {
398
+ slide.classList.contains('active') && setActivePage( idx );
399
+ itemClasses.map(function (cls) { return slide.classList.remove(("carousel-item-" + cls)); });
400
+ });
401
+ clearInterval(vars.timer);
402
+ toggleEvents();
403
+ vars = {};
404
+ ops = {};
405
+ delete element.Carousel;
406
+ };
407
+ element = queryElement( element );
408
+ element.Carousel && element.Carousel.dispose();
409
+ slides = element.getElementsByClassName('carousel-item');
410
+ leftArrow = element.getElementsByClassName('carousel-control-prev')[0];
411
+ rightArrow = element.getElementsByClassName('carousel-control-next')[0];
412
+ indicator = element.getElementsByClassName('carousel-indicators')[0];
413
+ indicators = indicator && indicator.getElementsByTagName( "LI" ) || [];
414
+ if (slides.length < 2) { return }
415
+ var
416
+ intervalAttribute = element.getAttribute('data-interval'),
417
+ intervalData = intervalAttribute === 'false' ? 0 : parseInt(intervalAttribute),
418
+ touchData = element.getAttribute('data-touch') === 'false' ? 0 : 1,
419
+ pauseData = element.getAttribute('data-pause') === 'hover' || false,
420
+ keyboardData = element.getAttribute('data-keyboard') === 'true' || false,
421
+ intervalOption = options.interval,
422
+ touchOption = options.touch;
423
+ ops = {};
424
+ ops.keyboard = options.keyboard === true || keyboardData;
425
+ ops.pause = (options.pause === 'hover' || pauseData) ? 'hover' : false;
426
+ ops.touch = touchOption || touchData;
427
+ ops.interval = typeof intervalOption === 'number' ? intervalOption
428
+ : intervalOption === false || intervalData === 0 || intervalData === false ? 0
429
+ : isNaN(intervalData) ? 5000
430
+ : intervalData;
431
+ if (self.getActiveIndex()<0) {
432
+ slides.length && slides[0].classList.add('active');
433
+ indicators.length && setActivePage(0);
434
+ }
435
+ vars = {};
436
+ vars.direction = 'left';
437
+ vars.index = 0;
438
+ vars.timer = null;
439
+ vars.isSliding = false;
440
+ vars.isTouch = false;
441
+ vars.touchPosition = {
442
+ startX : 0,
443
+ currentX : 0,
444
+ endX : 0
445
+ };
446
+ toggleEvents(1);
447
+ if ( ops.interval ){ self.cycle(); }
448
+ element.Carousel = self;
449
+ }
450
+
451
+ function Collapse(element,options) {
452
+ options = options || {};
453
+ var self = this;
454
+ var accordion = null,
455
+ collapse = null,
456
+ activeCollapse,
457
+ activeElement,
458
+ showCustomEvent,
459
+ shownCustomEvent,
460
+ hideCustomEvent,
461
+ hiddenCustomEvent;
462
+ function openAction(collapseElement, toggle) {
463
+ dispatchCustomEvent.call(collapseElement, showCustomEvent);
464
+ if ( showCustomEvent.defaultPrevented ) { return; }
465
+ collapseElement.isAnimating = true;
466
+ collapseElement.classList.add('collapsing');
467
+ collapseElement.classList.remove('collapse');
468
+ collapseElement.style.height = (collapseElement.scrollHeight) + "px";
469
+ emulateTransitionEnd(collapseElement, function () {
470
+ collapseElement.isAnimating = false;
471
+ collapseElement.setAttribute('aria-expanded','true');
472
+ toggle.setAttribute('aria-expanded','true');
473
+ collapseElement.classList.remove('collapsing');
474
+ collapseElement.classList.add('collapse');
475
+ collapseElement.classList.add('show');
476
+ collapseElement.style.height = '';
477
+ dispatchCustomEvent.call(collapseElement, shownCustomEvent);
478
+ });
479
+ }
480
+ function closeAction(collapseElement, toggle) {
481
+ dispatchCustomEvent.call(collapseElement, hideCustomEvent);
482
+ if ( hideCustomEvent.defaultPrevented ) { return; }
483
+ collapseElement.isAnimating = true;
484
+ collapseElement.style.height = (collapseElement.scrollHeight) + "px";
485
+ collapseElement.classList.remove('collapse');
486
+ collapseElement.classList.remove('show');
487
+ collapseElement.classList.add('collapsing');
488
+ collapseElement.offsetWidth;
489
+ collapseElement.style.height = '0px';
490
+ emulateTransitionEnd(collapseElement, function () {
491
+ collapseElement.isAnimating = false;
492
+ collapseElement.setAttribute('aria-expanded','false');
493
+ toggle.setAttribute('aria-expanded','false');
494
+ collapseElement.classList.remove('collapsing');
495
+ collapseElement.classList.add('collapse');
496
+ collapseElement.style.height = '';
497
+ dispatchCustomEvent.call(collapseElement, hiddenCustomEvent);
498
+ });
499
+ }
500
+ self.toggle = function (e) {
501
+ if (e && e.target.tagName === 'A' || element.tagName === 'A') {e.preventDefault();}
502
+ if (element.contains(e.target) || e.target === element) {
503
+ if (!collapse.classList.contains('show')) { self.show(); }
504
+ else { self.hide(); }
505
+ }
506
+ };
507
+ self.hide = function () {
508
+ if ( collapse.isAnimating ) { return; }
509
+ closeAction(collapse,element);
510
+ element.classList.add('collapsed');
511
+ };
512
+ self.show = function () {
513
+ if ( accordion ) {
514
+ activeCollapse = accordion.getElementsByClassName("collapse show")[0];
515
+ activeElement = activeCollapse && (queryElement(("[data-target=\"#" + (activeCollapse.id) + "\"]"),accordion)
516
+ || queryElement(("[href=\"#" + (activeCollapse.id) + "\"]"),accordion) );
517
+ }
518
+ if ( !collapse.isAnimating ) {
519
+ if ( activeElement && activeCollapse !== collapse ) {
520
+ closeAction(activeCollapse,activeElement);
521
+ activeElement.classList.add('collapsed');
522
+ }
523
+ openAction(collapse,element);
524
+ element.classList.remove('collapsed');
525
+ }
526
+ };
527
+ self.dispose = function () {
528
+ element.removeEventListener('click',self.toggle,false);
529
+ delete element.Collapse;
530
+ };
531
+ element = queryElement(element);
532
+ element.Collapse && element.Collapse.dispose();
533
+ var accordionData = element.getAttribute('data-parent');
534
+ showCustomEvent = bootstrapCustomEvent('show', 'collapse');
535
+ shownCustomEvent = bootstrapCustomEvent('shown', 'collapse');
536
+ hideCustomEvent = bootstrapCustomEvent('hide', 'collapse');
537
+ hiddenCustomEvent = bootstrapCustomEvent('hidden', 'collapse');
538
+ collapse = queryElement(options.target || element.getAttribute('data-target') || element.getAttribute('href'));
539
+ collapse.isAnimating = false;
540
+ accordion = element.closest(options.parent || accordionData);
541
+ if ( !element.Collapse ) {
542
+ element.addEventListener('click',self.toggle,false);
543
+ }
544
+ element.Collapse = self;
545
+ }
546
+
547
+ function setFocus (element){
548
+ element.focus ? element.focus() : element.setActive();
549
+ }
550
+
551
+ function Dropdown(element,option) {
552
+ var self = this,
553
+ showCustomEvent,
554
+ shownCustomEvent,
555
+ hideCustomEvent,
556
+ hiddenCustomEvent,
557
+ relatedTarget = null,
558
+ parent, menu, menuItems = [],
559
+ persist;
560
+ function preventEmptyAnchor(anchor) {
561
+ (anchor.href && anchor.href.slice(-1) === '#' || anchor.parentNode && anchor.parentNode.href
562
+ && anchor.parentNode.href.slice(-1) === '#') && this.preventDefault();
563
+ }
564
+ function toggleDismiss() {
565
+ var action = element.open ? 'addEventListener' : 'removeEventListener';
566
+ document[action]('click',dismissHandler,false);
567
+ document[action]('keydown',preventScroll,false);
568
+ document[action]('keyup',keyHandler,false);
569
+ document[action]('focus',dismissHandler,false);
570
+ }
571
+ function dismissHandler(e) {
572
+ var eventTarget = e.target,
573
+ hasData = eventTarget && (eventTarget.getAttribute('data-toggle')
574
+ || eventTarget.parentNode && eventTarget.parentNode.getAttribute
575
+ && eventTarget.parentNode.getAttribute('data-toggle'));
576
+ if ( e.type === 'focus' && (eventTarget === element || eventTarget === menu || menu.contains(eventTarget) ) ) {
577
+ return;
578
+ }
579
+ if ( (eventTarget === menu || menu.contains(eventTarget)) && (persist || hasData) ) { return; }
580
+ else {
581
+ relatedTarget = eventTarget === element || element.contains(eventTarget) ? element : null;
582
+ self.hide();
583
+ }
584
+ preventEmptyAnchor.call(e,eventTarget);
585
+ }
586
+ function clickHandler(e) {
587
+ relatedTarget = element;
588
+ self.show();
589
+ preventEmptyAnchor.call(e,e.target);
590
+ }
591
+ function preventScroll(e) {
592
+ var key = e.which || e.keyCode;
593
+ if( key === 38 || key === 40 ) { e.preventDefault(); }
594
+ }
595
+ function keyHandler(e) {
596
+ var key = e.which || e.keyCode,
597
+ activeItem = document.activeElement,
598
+ isSameElement = activeItem === element,
599
+ isInsideMenu = menu.contains(activeItem),
600
+ isMenuItem = activeItem.parentNode === menu || activeItem.parentNode.parentNode === menu,
601
+ idx = menuItems.indexOf(activeItem);
602
+ if ( isMenuItem ) {
603
+ idx = isSameElement ? 0
604
+ : key === 38 ? (idx>1?idx-1:0)
605
+ : key === 40 ? (idx<menuItems.length-1?idx+1:idx) : idx;
606
+ menuItems[idx] && setFocus(menuItems[idx]);
607
+ }
608
+ if ( (menuItems.length && isMenuItem
609
+ || !menuItems.length && (isInsideMenu || isSameElement)
610
+ || !isInsideMenu )
611
+ && element.open && key === 27
612
+ ) {
613
+ self.toggle();
614
+ relatedTarget = null;
615
+ }
616
+ }
617
+ self.show = function () {
618
+ showCustomEvent = bootstrapCustomEvent('show', 'dropdown', relatedTarget);
619
+ dispatchCustomEvent.call(parent, showCustomEvent);
620
+ if ( showCustomEvent.defaultPrevented ) { return; }
621
+ menu.classList.add('show');
622
+ parent.classList.add('show');
623
+ element.setAttribute('aria-expanded',true);
624
+ element.open = true;
625
+ element.removeEventListener('click',clickHandler,false);
626
+ setTimeout(function () {
627
+ setFocus( menu.getElementsByTagName('INPUT')[0] || element );
628
+ toggleDismiss();
629
+ shownCustomEvent = bootstrapCustomEvent( 'shown', 'dropdown', relatedTarget);
630
+ dispatchCustomEvent.call(parent, shownCustomEvent);
631
+ },1);
632
+ };
633
+ self.hide = function () {
634
+ hideCustomEvent = bootstrapCustomEvent('hide', 'dropdown', relatedTarget);
635
+ dispatchCustomEvent.call(parent, hideCustomEvent);
636
+ if ( hideCustomEvent.defaultPrevented ) { return; }
637
+ menu.classList.remove('show');
638
+ parent.classList.remove('show');
639
+ element.setAttribute('aria-expanded',false);
640
+ element.open = false;
641
+ toggleDismiss();
642
+ setFocus(element);
643
+ setTimeout(function () {
644
+ element.Dropdown && element.addEventListener('click',clickHandler,false);
645
+ },1);
646
+ hiddenCustomEvent = bootstrapCustomEvent('hidden', 'dropdown', relatedTarget);
647
+ dispatchCustomEvent.call(parent, hiddenCustomEvent);
648
+ };
649
+ self.toggle = function () {
650
+ if (parent.classList.contains('show') && element.open) { self.hide(); }
651
+ else { self.show(); }
652
+ };
653
+ self.dispose = function () {
654
+ if (parent.classList.contains('show') && element.open) { self.hide(); }
655
+ element.removeEventListener('click',clickHandler,false);
656
+ delete element.Dropdown;
657
+ };
658
+ element = queryElement(element);
659
+ element.Dropdown && element.Dropdown.dispose();
660
+ parent = element.parentNode;
661
+ menu = queryElement('.dropdown-menu', parent);
662
+ Array.from(menu.children).map(function (child){
663
+ child.children.length && (child.children[0].tagName === 'A' && menuItems.push(child.children[0]));
664
+ child.tagName === 'A' && menuItems.push(child);
665
+ });
666
+ if ( !element.Dropdown ) {
667
+ !('tabindex' in menu) && menu.setAttribute('tabindex', '0');
668
+ element.addEventListener('click',clickHandler,false);
669
+ }
670
+ persist = option === true || element.getAttribute('data-persist') === 'true' || false;
671
+ element.open = false;
672
+ element.Dropdown = self;
673
+ }
674
+
675
+ function Modal(element,options) {
676
+ options = options || {};
677
+ var self = this, modal,
678
+ showCustomEvent,
679
+ shownCustomEvent,
680
+ hideCustomEvent,
681
+ hiddenCustomEvent,
682
+ relatedTarget = null,
683
+ scrollBarWidth,
684
+ overlay,
685
+ overlayDelay,
686
+ fixedItems,
687
+ ops = {};
688
+ function setScrollbar() {
689
+ var openModal = document.body.classList.contains('modal-open'),
690
+ bodyPad = parseInt(getComputedStyle(document.body).paddingRight),
691
+ bodyOverflow = document.documentElement.clientHeight !== document.documentElement.scrollHeight
692
+ || document.body.clientHeight !== document.body.scrollHeight,
693
+ modalOverflow = modal.clientHeight !== modal.scrollHeight;
694
+ scrollBarWidth = measureScrollbar();
695
+ modal.style.paddingRight = !modalOverflow && scrollBarWidth ? (scrollBarWidth + "px") : '';
696
+ document.body.style.paddingRight = modalOverflow || bodyOverflow ? ((bodyPad + (openModal ? 0:scrollBarWidth)) + "px") : '';
697
+ fixedItems.length && fixedItems.map(function (fixed){
698
+ var itemPad = getComputedStyle(fixed).paddingRight;
699
+ fixed.style.paddingRight = modalOverflow || bodyOverflow ? ((parseInt(itemPad) + (openModal?0:scrollBarWidth)) + "px") : ((parseInt(itemPad)) + "px");
700
+ });
701
+ }
702
+ function resetScrollbar() {
703
+ document.body.style.paddingRight = '';
704
+ modal.style.paddingRight = '';
705
+ fixedItems.length && fixedItems.map(function (fixed){
706
+ fixed.style.paddingRight = '';
707
+ });
708
+ }
709
+ function measureScrollbar() {
710
+ var scrollDiv = document.createElement('div'), widthValue;
711
+ scrollDiv.className = 'modal-scrollbar-measure';
712
+ document.body.appendChild(scrollDiv);
713
+ widthValue = scrollDiv.offsetWidth - scrollDiv.clientWidth;
714
+ document.body.removeChild(scrollDiv);
715
+ return widthValue;
716
+ }
717
+ function createOverlay() {
718
+ var newOverlay = document.createElement('div');
719
+ overlay = queryElement('.modal-backdrop');
720
+ if ( overlay === null ) {
721
+ newOverlay.setAttribute('class', 'modal-backdrop' + (ops.animation ? ' fade' : ''));
722
+ overlay = newOverlay;
723
+ document.body.appendChild(overlay);
724
+ }
725
+ return overlay;
726
+ }
727
+ function removeOverlay () {
728
+ overlay = queryElement('.modal-backdrop');
729
+ if ( overlay && !document.getElementsByClassName('modal show')[0] ) {
730
+ document.body.removeChild(overlay); overlay = null;
731
+ }
732
+ overlay === null && (document.body.classList.remove('modal-open'), resetScrollbar());
733
+ }
734
+ function toggleEvents(action) {
735
+ action = action ? 'addEventListener' : 'removeEventListener';
736
+ window[action]( 'resize', self.update, passiveHandler);
737
+ modal[action]( 'click',dismissHandler,false);
738
+ document[action]( 'keydown',keyHandler,false);
739
+ }
740
+ function beforeShow() {
741
+ modal.style.display = 'block';
742
+ setScrollbar();
743
+ !document.getElementsByClassName('modal show')[0] && document.body.classList.add('modal-open');
744
+ modal.classList.add('show');
745
+ modal.setAttribute('aria-hidden', false);
746
+ modal.classList.contains('fade') ? emulateTransitionEnd(modal, triggerShow) : triggerShow();
747
+ }
748
+ function triggerShow() {
749
+ setFocus(modal);
750
+ modal.isAnimating = false;
751
+ toggleEvents(1);
752
+ shownCustomEvent = bootstrapCustomEvent('shown', 'modal', relatedTarget);
753
+ dispatchCustomEvent.call(modal, shownCustomEvent);
754
+ }
755
+ function triggerHide(force) {
756
+ modal.style.display = '';
757
+ element && (setFocus(element));
758
+ overlay = queryElement('.modal-backdrop');
759
+ if (force !== 1 && overlay && overlay.classList.contains('show') && !document.getElementsByClassName('modal show')[0]) {
760
+ overlay.classList.remove('show');
761
+ emulateTransitionEnd(overlay,removeOverlay);
762
+ } else {
763
+ removeOverlay();
764
+ }
765
+ toggleEvents();
766
+ modal.isAnimating = false;
767
+ hiddenCustomEvent = bootstrapCustomEvent('hidden', 'modal');
768
+ dispatchCustomEvent.call(modal, hiddenCustomEvent);
769
+ }
770
+ function clickHandler(e) {
771
+ if ( modal.isAnimating ) { return; }
772
+ var clickTarget = e.target,
773
+ modalID = "#" + (modal.getAttribute('id')),
774
+ targetAttrValue = clickTarget.getAttribute('data-target') || clickTarget.getAttribute('href'),
775
+ elemAttrValue = element.getAttribute('data-target') || element.getAttribute('href');
776
+ if ( !modal.classList.contains('show')
777
+ && (clickTarget === element && targetAttrValue === modalID
778
+ || element.contains(clickTarget) && elemAttrValue === modalID) ) {
779
+ modal.modalTrigger = element;
780
+ relatedTarget = element;
781
+ self.show();
782
+ e.preventDefault();
783
+ }
784
+ }
785
+ function keyHandler(ref) {
786
+ var which = ref.which;
787
+ if (!modal.isAnimating && ops.keyboard && which == 27 && modal.classList.contains('show') ) {
788
+ self.hide();
789
+ }
790
+ }
791
+ function dismissHandler(e) {
792
+ if ( modal.isAnimating ) { return; }
793
+ var clickTarget = e.target,
794
+ hasData = clickTarget.getAttribute('data-dismiss') === 'modal',
795
+ parentWithData = clickTarget.closest('[data-dismiss="modal"]');
796
+ if ( modal.classList.contains('show') && ( parentWithData || hasData
797
+ || clickTarget === modal && ops.backdrop !== 'static' ) ) {
798
+ self.hide(); relatedTarget = null;
799
+ e.preventDefault();
800
+ }
801
+ }
802
+ self.toggle = function () {
803
+ if ( modal.classList.contains('show') ) {self.hide();} else {self.show();}
804
+ };
805
+ self.show = function () {
806
+ if (modal.classList.contains('show') && !!modal.isAnimating ) {return}
807
+ showCustomEvent = bootstrapCustomEvent('show', 'modal', relatedTarget);
808
+ dispatchCustomEvent.call(modal, showCustomEvent);
809
+ if ( showCustomEvent.defaultPrevented ) { return; }
810
+ modal.isAnimating = true;
811
+ var currentOpen = document.getElementsByClassName('modal show')[0];
812
+ if (currentOpen && currentOpen !== modal) {
813
+ currentOpen.modalTrigger && currentOpen.modalTrigger.Modal.hide();
814
+ currentOpen.Modal && currentOpen.Modal.hide();
815
+ }
816
+ if ( ops.backdrop ) {
817
+ overlay = createOverlay();
818
+ }
819
+ if ( overlay && !currentOpen && !overlay.classList.contains('show') ) {
820
+ overlay.offsetWidth;
821
+ overlayDelay = getElementTransitionDuration(overlay);
822
+ overlay.classList.add('show');
823
+ }
824
+ !currentOpen ? setTimeout( beforeShow, overlay && overlayDelay ? overlayDelay:0 ) : beforeShow();
825
+ };
826
+ self.hide = function (force) {
827
+ if ( !modal.classList.contains('show') ) {return}
828
+ hideCustomEvent = bootstrapCustomEvent( 'hide', 'modal');
829
+ dispatchCustomEvent.call(modal, hideCustomEvent);
830
+ if ( hideCustomEvent.defaultPrevented ) { return; }
831
+ modal.isAnimating = true;
832
+ modal.classList.remove('show');
833
+ modal.setAttribute('aria-hidden', true);
834
+ modal.classList.contains('fade') && force !== 1 ? emulateTransitionEnd(modal, triggerHide) : triggerHide();
835
+ };
836
+ self.setContent = function (content) {
837
+ queryElement('.modal-content',modal).innerHTML = content;
838
+ };
839
+ self.update = function () {
840
+ if (modal.classList.contains('show')) {
841
+ setScrollbar();
842
+ }
843
+ };
844
+ self.dispose = function () {
845
+ self.hide(1);
846
+ if (element) {element.removeEventListener('click',clickHandler,false); delete element.Modal; }
847
+ else {delete modal.Modal;}
848
+ };
849
+ element = queryElement(element);
850
+ var checkModal = queryElement( element.getAttribute('data-target') || element.getAttribute('href') );
851
+ modal = element.classList.contains('modal') ? element : checkModal;
852
+ fixedItems = Array.from(document.getElementsByClassName('fixed-top'))
853
+ .concat(Array.from(document.getElementsByClassName('fixed-bottom')));
854
+ if ( element.classList.contains('modal') ) { element = null; }
855
+ element && element.Modal && element.Modal.dispose();
856
+ modal && modal.Modal && modal.Modal.dispose();
857
+ ops.keyboard = options.keyboard === false || modal.getAttribute('data-keyboard') === 'false' ? false : true;
858
+ ops.backdrop = options.backdrop === 'static' || modal.getAttribute('data-backdrop') === 'static' ? 'static' : true;
859
+ ops.backdrop = options.backdrop === false || modal.getAttribute('data-backdrop') === 'false' ? false : ops.backdrop;
860
+ ops.animation = modal.classList.contains('fade') ? true : false;
861
+ ops.content = options.content;
862
+ modal.isAnimating = false;
863
+ if ( element && !element.Modal ) {
864
+ element.addEventListener('click',clickHandler,false);
865
+ }
866
+ if ( ops.content ) {
867
+ self.setContent( ops.content.trim() );
868
+ }
869
+ if (element) {
870
+ modal.modalTrigger = element;
871
+ element.Modal = self;
872
+ } else {
873
+ modal.Modal = self;
874
+ }
875
+ }
876
+
877
+ var mouseClickEvents = { down: 'mousedown', up: 'mouseup' };
878
+
879
+ function getScroll() {
880
+ return {
881
+ y : window.pageYOffset || document.documentElement.scrollTop,
882
+ x : window.pageXOffset || document.documentElement.scrollLeft
883
+ }
884
+ }
885
+
886
+ function styleTip(link,element,position,parent) {
887
+ var tipPositions = /\b(top|bottom|left|right)+/,
888
+ elementDimensions = { w : element.offsetWidth, h: element.offsetHeight },
889
+ windowWidth = (document.documentElement.clientWidth || document.body.clientWidth),
890
+ windowHeight = (document.documentElement.clientHeight || document.body.clientHeight),
891
+ rect = link.getBoundingClientRect(),
892
+ scroll = parent === document.body ? getScroll() : { x: parent.offsetLeft + parent.scrollLeft, y: parent.offsetTop + parent.scrollTop },
893
+ linkDimensions = { w: rect.right - rect.left, h: rect.bottom - rect.top },
894
+ isPopover = element.classList.contains('popover'),
895
+ arrow = element.getElementsByClassName('arrow')[0],
896
+ halfTopExceed = rect.top + linkDimensions.h/2 - elementDimensions.h/2 < 0,
897
+ halfLeftExceed = rect.left + linkDimensions.w/2 - elementDimensions.w/2 < 0,
898
+ halfRightExceed = rect.left + elementDimensions.w/2 + linkDimensions.w/2 >= windowWidth,
899
+ halfBottomExceed = rect.top + elementDimensions.h/2 + linkDimensions.h/2 >= windowHeight,
900
+ topExceed = rect.top - elementDimensions.h < 0,
901
+ leftExceed = rect.left - elementDimensions.w < 0,
902
+ bottomExceed = rect.top + elementDimensions.h + linkDimensions.h >= windowHeight,
903
+ rightExceed = rect.left + elementDimensions.w + linkDimensions.w >= windowWidth;
904
+ position = (position === 'left' || position === 'right') && leftExceed && rightExceed ? 'top' : position;
905
+ position = position === 'top' && topExceed ? 'bottom' : position;
906
+ position = position === 'bottom' && bottomExceed ? 'top' : position;
907
+ position = position === 'left' && leftExceed ? 'right' : position;
908
+ position = position === 'right' && rightExceed ? 'left' : position;
909
+ var topPosition,
910
+ leftPosition,
911
+ arrowTop,
912
+ arrowLeft,
913
+ arrowWidth,
914
+ arrowHeight;
915
+ element.className.indexOf(position) === -1 && (element.className = element.className.replace(tipPositions,position));
916
+ arrowWidth = arrow.offsetWidth; arrowHeight = arrow.offsetHeight;
917
+ if ( position === 'left' || position === 'right' ) {
918
+ if ( position === 'left' ) {
919
+ leftPosition = rect.left + scroll.x - elementDimensions.w - ( isPopover ? arrowWidth : 0 );
920
+ } else {
921
+ leftPosition = rect.left + scroll.x + linkDimensions.w;
922
+ }
923
+ if (halfTopExceed) {
924
+ topPosition = rect.top + scroll.y;
925
+ arrowTop = linkDimensions.h/2 - arrowWidth;
926
+ } else if (halfBottomExceed) {
927
+ topPosition = rect.top + scroll.y - elementDimensions.h + linkDimensions.h;
928
+ arrowTop = elementDimensions.h - linkDimensions.h/2 - arrowWidth;
929
+ } else {
930
+ topPosition = rect.top + scroll.y - elementDimensions.h/2 + linkDimensions.h/2;
931
+ arrowTop = elementDimensions.h/2 - (isPopover ? arrowHeight*0.9 : arrowHeight/2);
932
+ }
933
+ } else if ( position === 'top' || position === 'bottom' ) {
934
+ if ( position === 'top') {
935
+ topPosition = rect.top + scroll.y - elementDimensions.h - ( isPopover ? arrowHeight : 0 );
936
+ } else {
937
+ topPosition = rect.top + scroll.y + linkDimensions.h;
938
+ }
939
+ if (halfLeftExceed) {
940
+ leftPosition = 0;
941
+ arrowLeft = rect.left + linkDimensions.w/2 - arrowWidth;
942
+ } else if (halfRightExceed) {
943
+ leftPosition = windowWidth - elementDimensions.w*1.01;
944
+ arrowLeft = elementDimensions.w - ( windowWidth - rect.left ) + linkDimensions.w/2 - arrowWidth/2;
945
+ } else {
946
+ leftPosition = rect.left + scroll.x - elementDimensions.w/2 + linkDimensions.w/2;
947
+ arrowLeft = elementDimensions.w/2 - ( isPopover ? arrowWidth : arrowWidth/2 );
948
+ }
949
+ }
950
+ element.style.top = topPosition + 'px';
951
+ element.style.left = leftPosition + 'px';
952
+ arrowTop && (arrow.style.top = arrowTop + 'px');
953
+ arrowLeft && (arrow.style.left = arrowLeft + 'px');
954
+ }
955
+
956
+ function Popover(element,options) {
957
+ options = options || {};
958
+ var self = this;
959
+ var popover = null,
960
+ timer = 0,
961
+ isIphone = /(iPhone|iPod|iPad)/.test(navigator.userAgent),
962
+ titleString,
963
+ contentString,
964
+ ops = {};
965
+ var triggerData,
966
+ animationData,
967
+ placementData,
968
+ dismissibleData,
969
+ delayData,
970
+ containerData,
971
+ closeBtn,
972
+ showCustomEvent,
973
+ shownCustomEvent,
974
+ hideCustomEvent,
975
+ hiddenCustomEvent,
976
+ containerElement,
977
+ containerDataElement,
978
+ modal,
979
+ navbarFixedTop,
980
+ navbarFixedBottom,
981
+ placementClass;
982
+ function dismissibleHandler(e) {
983
+ if (popover !== null && e.target === queryElement('.close',popover)) {
984
+ self.hide();
985
+ }
986
+ }
987
+ function getContents() {
988
+ return {
989
+ 0 : options.title || element.getAttribute('data-title') || null,
990
+ 1 : options.content || element.getAttribute('data-content') || null
991
+ }
992
+ }
993
+ function removePopover() {
994
+ ops.container.removeChild(popover);
995
+ timer = null; popover = null;
996
+ }
997
+ function createPopover() {
998
+ titleString = getContents()[0] || null;
999
+ contentString = getContents()[1];
1000
+ contentString = !!contentString ? contentString.trim() : null;
1001
+ popover = document.createElement('div');
1002
+ var popoverArrow = document.createElement('div');
1003
+ popoverArrow.classList.add('arrow');
1004
+ popover.appendChild(popoverArrow);
1005
+ if ( contentString !== null && ops.template === null ) {
1006
+ popover.setAttribute('role','tooltip');
1007
+ if (titleString !== null) {
1008
+ var popoverTitle = document.createElement('h3');
1009
+ popoverTitle.classList.add('popover-header');
1010
+ popoverTitle.innerHTML = ops.dismissible ? titleString + closeBtn : titleString;
1011
+ popover.appendChild(popoverTitle);
1012
+ }
1013
+ var popoverBodyMarkup = document.createElement('div');
1014
+ popoverBodyMarkup.classList.add('popover-body');
1015
+ popoverBodyMarkup.innerHTML = ops.dismissible && titleString === null ? contentString + closeBtn : contentString;
1016
+ popover.appendChild(popoverBodyMarkup);
1017
+ } else {
1018
+ var popoverTemplate = document.createElement('div');
1019
+ popoverTemplate.innerHTML = ops.template.trim();
1020
+ popover.className = popoverTemplate.firstChild.className;
1021
+ popover.innerHTML = popoverTemplate.firstChild.innerHTML;
1022
+ var popoverHeader = queryElement('.popover-header',popover),
1023
+ popoverBody = queryElement('.popover-body',popover);
1024
+ titleString && popoverHeader && (popoverHeader.innerHTML = titleString.trim());
1025
+ contentString && popoverBody && (popoverBody.innerHTML = contentString.trim());
1026
+ }
1027
+ ops.container.appendChild(popover);
1028
+ popover.style.display = 'block';
1029
+ !popover.classList.contains( 'popover') && popover.classList.add('popover');
1030
+ !popover.classList.contains( ops.animation) && popover.classList.add(ops.animation);
1031
+ !popover.classList.contains( placementClass) && popover.classList.add(placementClass);
1032
+ }
1033
+ function showPopover() {
1034
+ !popover.classList.contains('show') && ( popover.classList.add('show') );
1035
+ }
1036
+ function updatePopover() {
1037
+ styleTip(element, popover, ops.placement, ops.container);
1038
+ }
1039
+ function forceFocus () {
1040
+ if (popover === null) { element.focus(); }
1041
+ }
1042
+ function toggleEvents(action) {
1043
+ action = action ? 'addEventListener' : 'removeEventListener';
1044
+ if (ops.trigger === 'hover') {
1045
+ element[action]( mouseClickEvents.down, self.show );
1046
+ element[action]( mouseHoverEvents[0], self.show );
1047
+ if (!ops.dismissible) { element[action]( mouseHoverEvents[1], self.hide ); }
1048
+ } else if ('click' == ops.trigger) {
1049
+ element[action]( ops.trigger, self.toggle );
1050
+ } else if ('focus' == ops.trigger) {
1051
+ isIphone && element[action]( 'click', forceFocus, false );
1052
+ element[action]( ops.trigger, self.toggle );
1053
+ }
1054
+ }
1055
+ function touchHandler(e){
1056
+ if ( popover && popover.contains(e.target) || e.target === element || element.contains(e.target)) ; else {
1057
+ self.hide();
1058
+ }
1059
+ }
1060
+ function dismissHandlerToggle(action) {
1061
+ action = action ? 'addEventListener' : 'removeEventListener';
1062
+ if (ops.dismissible) {
1063
+ document[action]('click', dismissibleHandler, false );
1064
+ } else {
1065
+ 'focus' == ops.trigger && element[action]( 'blur', self.hide );
1066
+ 'hover' == ops.trigger && document[action]( 'touchstart', touchHandler, passiveHandler );
1067
+ }
1068
+ window[action]('resize', self.hide, passiveHandler );
1069
+ }
1070
+ function showTrigger() {
1071
+ dismissHandlerToggle(1);
1072
+ dispatchCustomEvent.call(element, shownCustomEvent);
1073
+ }
1074
+ function hideTrigger() {
1075
+ dismissHandlerToggle();
1076
+ removePopover();
1077
+ dispatchCustomEvent.call(element, hiddenCustomEvent);
1078
+ }
1079
+ self.toggle = function () {
1080
+ if (popover === null) { self.show(); }
1081
+ else { self.hide(); }
1082
+ };
1083
+ self.show = function () {
1084
+ clearTimeout(timer);
1085
+ timer = setTimeout( function () {
1086
+ if (popover === null) {
1087
+ dispatchCustomEvent.call(element, showCustomEvent);
1088
+ if ( showCustomEvent.defaultPrevented ) { return; }
1089
+ createPopover();
1090
+ updatePopover();
1091
+ showPopover();
1092
+ !!ops.animation ? emulateTransitionEnd(popover, showTrigger) : showTrigger();
1093
+ }
1094
+ }, 20 );
1095
+ };
1096
+ self.hide = function () {
1097
+ clearTimeout(timer);
1098
+ timer = setTimeout( function () {
1099
+ if (popover && popover !== null && popover.classList.contains('show')) {
1100
+ dispatchCustomEvent.call(element, hideCustomEvent);
1101
+ if ( hideCustomEvent.defaultPrevented ) { return; }
1102
+ popover.classList.remove('show');
1103
+ !!ops.animation ? emulateTransitionEnd(popover, hideTrigger) : hideTrigger();
1104
+ }
1105
+ }, ops.delay );
1106
+ };
1107
+ self.dispose = function () {
1108
+ self.hide();
1109
+ toggleEvents();
1110
+ delete element.Popover;
1111
+ };
1112
+ element = queryElement(element);
1113
+ element.Popover && element.Popover.dispose();
1114
+ triggerData = element.getAttribute('data-trigger');
1115
+ animationData = element.getAttribute('data-animation');
1116
+ placementData = element.getAttribute('data-placement');
1117
+ dismissibleData = element.getAttribute('data-dismissible');
1118
+ delayData = element.getAttribute('data-delay');
1119
+ containerData = element.getAttribute('data-container');
1120
+ closeBtn = '<button type="button" class="close">×</button>';
1121
+ showCustomEvent = bootstrapCustomEvent('show', 'popover');
1122
+ shownCustomEvent = bootstrapCustomEvent('shown', 'popover');
1123
+ hideCustomEvent = bootstrapCustomEvent('hide', 'popover');
1124
+ hiddenCustomEvent = bootstrapCustomEvent('hidden', 'popover');
1125
+ containerElement = queryElement(options.container);
1126
+ containerDataElement = queryElement(containerData);
1127
+ modal = element.closest('.modal');
1128
+ navbarFixedTop = element.closest('.fixed-top');
1129
+ navbarFixedBottom = element.closest('.fixed-bottom');
1130
+ ops.template = options.template ? options.template : null;
1131
+ ops.trigger = options.trigger ? options.trigger : triggerData || 'hover';
1132
+ ops.animation = options.animation && options.animation !== 'fade' ? options.animation : animationData || 'fade';
1133
+ ops.placement = options.placement ? options.placement : placementData || 'top';
1134
+ ops.delay = parseInt(options.delay || delayData) || 200;
1135
+ ops.dismissible = options.dismissible || dismissibleData === 'true' ? true : false;
1136
+ ops.container = containerElement ? containerElement
1137
+ : containerDataElement ? containerDataElement
1138
+ : navbarFixedTop ? navbarFixedTop
1139
+ : navbarFixedBottom ? navbarFixedBottom
1140
+ : modal ? modal : document.body;
1141
+ placementClass = "bs-popover-" + (ops.placement);
1142
+ var popoverContents = getContents();
1143
+ titleString = popoverContents[0];
1144
+ contentString = popoverContents[1];
1145
+ if ( !contentString && !ops.template ) { return; }
1146
+ if ( !element.Popover ) {
1147
+ toggleEvents(1);
1148
+ }
1149
+ element.Popover = self;
1150
+ }
1151
+
1152
+ function ScrollSpy(element,options) {
1153
+ options = options || {};
1154
+ var self = this,
1155
+ vars,
1156
+ targetData,
1157
+ offsetData,
1158
+ spyTarget,
1159
+ scrollTarget,
1160
+ ops = {};
1161
+ function updateTargets(){
1162
+ var links = spyTarget.getElementsByTagName('A');
1163
+ if (vars.length !== links.length) {
1164
+ vars.items = [];
1165
+ vars.targets = [];
1166
+ Array.from(links).map(function (link){
1167
+ var href = link.getAttribute('href'),
1168
+ targetItem = href && href.charAt(0) === '#' && href.slice(-1) !== '#' && queryElement(href);
1169
+ if ( targetItem ) {
1170
+ vars.items.push(link);
1171
+ vars.targets.push(targetItem);
1172
+ }
1173
+ });
1174
+ vars.length = links.length;
1175
+ }
1176
+ }
1177
+ function updateItem(index) {
1178
+ var item = vars.items[index],
1179
+ targetItem = vars.targets[index],
1180
+ dropmenu = item.classList.contains('dropdown-item') && item.closest('.dropdown-menu'),
1181
+ dropLink = dropmenu && dropmenu.previousElementSibling,
1182
+ nextSibling = item.nextElementSibling,
1183
+ activeSibling = nextSibling && nextSibling.getElementsByClassName('active').length,
1184
+ targetRect = vars.isWindow && targetItem.getBoundingClientRect(),
1185
+ isActive = item.classList.contains('active') || false,
1186
+ topEdge = (vars.isWindow ? targetRect.top + vars.scrollOffset : targetItem.offsetTop) - ops.offset,
1187
+ bottomEdge = vars.isWindow ? targetRect.bottom + vars.scrollOffset - ops.offset
1188
+ : vars.targets[index+1] ? vars.targets[index+1].offsetTop - ops.offset
1189
+ : element.scrollHeight,
1190
+ inside = activeSibling || vars.scrollOffset >= topEdge && bottomEdge > vars.scrollOffset;
1191
+ if ( !isActive && inside ) {
1192
+ item.classList.add('active');
1193
+ if (dropLink && !dropLink.classList.contains('active') ) {
1194
+ dropLink.classList.add('active');
1195
+ }
1196
+ dispatchCustomEvent.call(element, bootstrapCustomEvent( 'activate', 'scrollspy', vars.items[index]));
1197
+ } else if ( isActive && !inside ) {
1198
+ item.classList.remove('active');
1199
+ if (dropLink && dropLink.classList.contains('active') && !item.parentNode.getElementsByClassName('active').length ) {
1200
+ dropLink.classList.remove('active');
1201
+ }
1202
+ } else if ( isActive && inside || !inside && !isActive ) {
1203
+ return;
1204
+ }
1205
+ }
1206
+ function updateItems() {
1207
+ updateTargets();
1208
+ vars.scrollOffset = vars.isWindow ? getScroll().y : element.scrollTop;
1209
+ vars.items.map(function (l,idx){ return updateItem(idx); });
1210
+ }
1211
+ function toggleEvents(action) {
1212
+ action = action ? 'addEventListener' : 'removeEventListener';
1213
+ scrollTarget[action]('scroll', self.refresh, passiveHandler );
1214
+ window[action]( 'resize', self.refresh, passiveHandler );
1215
+ }
1216
+ self.refresh = function () {
1217
+ updateItems();
1218
+ };
1219
+ self.dispose = function () {
1220
+ toggleEvents();
1221
+ delete element.ScrollSpy;
1222
+ };
1223
+ element = queryElement(element);
1224
+ element.ScrollSpy && element.ScrollSpy.dispose();
1225
+ targetData = element.getAttribute('data-target');
1226
+ offsetData = element.getAttribute('data-offset');
1227
+ spyTarget = queryElement(options.target || targetData);
1228
+ scrollTarget = element.offsetHeight < element.scrollHeight ? element : window;
1229
+ if (!spyTarget) { return }
1230
+ ops.target = spyTarget;
1231
+ ops.offset = parseInt(options.offset || offsetData) || 10;
1232
+ vars = {};
1233
+ vars.length = 0;
1234
+ vars.items = [];
1235
+ vars.targets = [];
1236
+ vars.isWindow = scrollTarget === window;
1237
+ if ( !element.ScrollSpy ) {
1238
+ toggleEvents(1);
1239
+ }
1240
+ self.refresh();
1241
+ element.ScrollSpy = self;
1242
+ }
1243
+
1244
+ function Tab(element,options) {
1245
+ options = options || {};
1246
+ var self = this,
1247
+ heightData,
1248
+ tabs, dropdown,
1249
+ showCustomEvent,
1250
+ shownCustomEvent,
1251
+ hideCustomEvent,
1252
+ hiddenCustomEvent,
1253
+ next,
1254
+ tabsContentContainer = false,
1255
+ activeTab,
1256
+ activeContent,
1257
+ nextContent,
1258
+ containerHeight,
1259
+ equalContents,
1260
+ nextHeight,
1261
+ animateHeight;
1262
+ function triggerEnd() {
1263
+ tabsContentContainer.style.height = '';
1264
+ tabsContentContainer.classList.remove('collapsing');
1265
+ tabs.isAnimating = false;
1266
+ }
1267
+ function triggerShow() {
1268
+ if (tabsContentContainer) {
1269
+ if ( equalContents ) {
1270
+ triggerEnd();
1271
+ } else {
1272
+ setTimeout(function () {
1273
+ tabsContentContainer.style.height = nextHeight + "px";
1274
+ tabsContentContainer.offsetWidth;
1275
+ emulateTransitionEnd(tabsContentContainer, triggerEnd);
1276
+ },50);
1277
+ }
1278
+ } else {
1279
+ tabs.isAnimating = false;
1280
+ }
1281
+ shownCustomEvent = bootstrapCustomEvent('shown', 'tab', activeTab);
1282
+ dispatchCustomEvent.call(next, shownCustomEvent);
1283
+ }
1284
+ function triggerHide() {
1285
+ if (tabsContentContainer) {
1286
+ activeContent.style.float = 'left';
1287
+ nextContent.style.float = 'left';
1288
+ containerHeight = activeContent.scrollHeight;
1289
+ }
1290
+ showCustomEvent = bootstrapCustomEvent('show', 'tab', activeTab);
1291
+ hiddenCustomEvent = bootstrapCustomEvent('hidden', 'tab', next);
1292
+ dispatchCustomEvent.call(next, showCustomEvent);
1293
+ if ( showCustomEvent.defaultPrevented ) { return; }
1294
+ nextContent.classList.add('active');
1295
+ activeContent.classList.remove('active');
1296
+ if (tabsContentContainer) {
1297
+ nextHeight = nextContent.scrollHeight;
1298
+ equalContents = nextHeight === containerHeight;
1299
+ tabsContentContainer.classList.add('collapsing');
1300
+ tabsContentContainer.style.height = containerHeight + "px";
1301
+ tabsContentContainer.offsetHeight;
1302
+ activeContent.style.float = '';
1303
+ nextContent.style.float = '';
1304
+ }
1305
+ if ( nextContent.classList.contains('fade') ) {
1306
+ setTimeout(function () {
1307
+ nextContent.classList.add('show');
1308
+ emulateTransitionEnd(nextContent,triggerShow);
1309
+ },20);
1310
+ } else { triggerShow(); }
1311
+ dispatchCustomEvent.call(activeTab, hiddenCustomEvent);
1312
+ }
1313
+ function getActiveTab() {
1314
+ var activeTabs = tabs.getElementsByClassName('active'), activeTab;
1315
+ if ( activeTabs.length === 1 && !activeTabs[0].parentNode.classList.contains('dropdown') ) {
1316
+ activeTab = activeTabs[0];
1317
+ } else if ( activeTabs.length > 1 ) {
1318
+ activeTab = activeTabs[activeTabs.length-1];
1319
+ }
1320
+ return activeTab;
1321
+ }
1322
+ function getActiveContent() { return queryElement(getActiveTab().getAttribute('href')) }
1323
+ function clickHandler(e) {
1324
+ e.preventDefault();
1325
+ next = e.currentTarget;
1326
+ !tabs.isAnimating && self.show();
1327
+ }
1328
+ self.show = function () {
1329
+ next = next || element;
1330
+ if (!next.classList.contains('active')) {
1331
+ nextContent = queryElement(next.getAttribute('href'));
1332
+ activeTab = getActiveTab();
1333
+ activeContent = getActiveContent();
1334
+ hideCustomEvent = bootstrapCustomEvent( 'hide', 'tab', next);
1335
+ dispatchCustomEvent.call(activeTab, hideCustomEvent);
1336
+ if (hideCustomEvent.defaultPrevented) { return; }
1337
+ tabs.isAnimating = true;
1338
+ activeTab.classList.remove('active');
1339
+ activeTab.setAttribute('aria-selected','false');
1340
+ next.classList.add('active');
1341
+ next.setAttribute('aria-selected','true');
1342
+ if ( dropdown ) {
1343
+ if ( !element.parentNode.classList.contains('dropdown-menu') ) {
1344
+ if (dropdown.classList.contains('active')) { dropdown.classList.remove('active'); }
1345
+ } else {
1346
+ if (!dropdown.classList.contains('active')) { dropdown.classList.add('active'); }
1347
+ }
1348
+ }
1349
+ if (activeContent.classList.contains('fade')) {
1350
+ activeContent.classList.remove('show');
1351
+ emulateTransitionEnd(activeContent, triggerHide);
1352
+ } else { triggerHide(); }
1353
+ }
1354
+ };
1355
+ self.dispose = function () {
1356
+ element.removeEventListener('click',clickHandler,false);
1357
+ delete element.Tab;
1358
+ };
1359
+ element = queryElement(element);
1360
+ element.Tab && element.Tab.dispose();
1361
+ heightData = element.getAttribute('data-height');
1362
+ tabs = element.closest('.nav');
1363
+ dropdown = tabs && queryElement('.dropdown-toggle',tabs);
1364
+ animateHeight = !supportTransition || (options.height === false || heightData === 'false') ? false : true;
1365
+ tabs.isAnimating = false;
1366
+ if ( !element.Tab ) {
1367
+ element.addEventListener('click',clickHandler,false);
1368
+ }
1369
+ if (animateHeight) { tabsContentContainer = getActiveContent().parentNode; }
1370
+ element.Tab = self;
1371
+ }
1372
+
1373
+ function Toast(element,options) {
1374
+ options = options || {};
1375
+ var self = this,
1376
+ toast, timer = 0,
1377
+ animationData,
1378
+ autohideData,
1379
+ delayData,
1380
+ showCustomEvent,
1381
+ hideCustomEvent,
1382
+ shownCustomEvent,
1383
+ hiddenCustomEvent,
1384
+ ops = {};
1385
+ function showComplete() {
1386
+ toast.classList.remove( 'showing' );
1387
+ toast.classList.add( 'show' );
1388
+ dispatchCustomEvent.call(toast,shownCustomEvent);
1389
+ if (ops.autohide) { self.hide(); }
1390
+ }
1391
+ function hideComplete() {
1392
+ toast.classList.add( 'hide' );
1393
+ dispatchCustomEvent.call(toast,hiddenCustomEvent);
1394
+ }
1395
+ function close () {
1396
+ toast.classList.remove('show' );
1397
+ ops.animation ? emulateTransitionEnd(toast, hideComplete) : hideComplete();
1398
+ }
1399
+ function disposeComplete() {
1400
+ clearTimeout(timer);
1401
+ element.removeEventListener('click',self.hide,false);
1402
+ delete element.Toast;
1403
+ }
1404
+ self.show = function () {
1405
+ if (toast && !toast.classList.contains('show')) {
1406
+ dispatchCustomEvent.call(toast,showCustomEvent);
1407
+ if (showCustomEvent.defaultPrevented) { return; }
1408
+ ops.animation && toast.classList.add( 'fade' );
1409
+ toast.classList.remove('hide' );
1410
+ toast.offsetWidth;
1411
+ toast.classList.add('showing' );
1412
+ ops.animation ? emulateTransitionEnd(toast, showComplete) : showComplete();
1413
+ }
1414
+ };
1415
+ self.hide = function (noTimer) {
1416
+ if (toast && toast.classList.contains('show')) {
1417
+ dispatchCustomEvent.call(toast,hideCustomEvent);
1418
+ if(hideCustomEvent.defaultPrevented) { return; }
1419
+ noTimer ? close() : (timer = setTimeout( close, ops.delay));
1420
+ }
1421
+ };
1422
+ self.dispose = function () {
1423
+ ops.animation ? emulateTransitionEnd(toast, disposeComplete) : disposeComplete();
1424
+ };
1425
+ element = queryElement(element);
1426
+ element.Toast && element.Toast.dispose();
1427
+ toast = element.closest('.toast');
1428
+ animationData = element.getAttribute('data-animation');
1429
+ autohideData = element.getAttribute('data-autohide');
1430
+ delayData = element.getAttribute('data-delay');
1431
+ showCustomEvent = bootstrapCustomEvent('show', 'toast');
1432
+ hideCustomEvent = bootstrapCustomEvent('hide', 'toast');
1433
+ shownCustomEvent = bootstrapCustomEvent('shown', 'toast');
1434
+ hiddenCustomEvent = bootstrapCustomEvent('hidden', 'toast');
1435
+ ops.animation = options.animation === false || animationData === 'false' ? 0 : 1;
1436
+ ops.autohide = options.autohide === false || autohideData === 'false' ? 0 : 1;
1437
+ ops.delay = parseInt(options.delay || delayData) || 500;
1438
+ if ( !element.Toast ) {
1439
+ element.addEventListener('click',self.hide,false);
1440
+ }
1441
+ element.Toast = self;
1442
+ }
1443
+
1444
+ function Tooltip(element,options) {
1445
+ options = options || {};
1446
+ var self = this,
1447
+ tooltip = null, timer = 0, titleString,
1448
+ animationData,
1449
+ placementData,
1450
+ delayData,
1451
+ containerData,
1452
+ showCustomEvent,
1453
+ shownCustomEvent,
1454
+ hideCustomEvent,
1455
+ hiddenCustomEvent,
1456
+ containerElement,
1457
+ containerDataElement,
1458
+ modal,
1459
+ navbarFixedTop,
1460
+ navbarFixedBottom,
1461
+ placementClass,
1462
+ ops = {};
1463
+ function getTitle() {
1464
+ return element.getAttribute('title')
1465
+ || element.getAttribute('data-title')
1466
+ || element.getAttribute('data-original-title')
1467
+ }
1468
+ function removeToolTip() {
1469
+ ops.container.removeChild(tooltip);
1470
+ tooltip = null; timer = null;
1471
+ }
1472
+ function createToolTip() {
1473
+ titleString = getTitle();
1474
+ if ( titleString ) {
1475
+ tooltip = document.createElement('div');
1476
+ if (ops.template) {
1477
+ var tooltipMarkup = document.createElement('div');
1478
+ tooltipMarkup.innerHTML = ops.template.trim();
1479
+ tooltip.className = tooltipMarkup.firstChild.className;
1480
+ tooltip.innerHTML = tooltipMarkup.firstChild.innerHTML;
1481
+ queryElement('.tooltip-inner',tooltip).innerHTML = titleString.trim();
1482
+ } else {
1483
+ var tooltipArrow = document.createElement('div');
1484
+ tooltipArrow.classList.add('arrow');
1485
+ tooltip.appendChild(tooltipArrow);
1486
+ var tooltipInner = document.createElement('div');
1487
+ tooltipInner.classList.add('tooltip-inner');
1488
+ tooltip.appendChild(tooltipInner);
1489
+ tooltipInner.innerHTML = titleString;
1490
+ }
1491
+ tooltip.style.left = '0';
1492
+ tooltip.style.top = '0';
1493
+ tooltip.setAttribute('role','tooltip');
1494
+ !tooltip.classList.contains('tooltip') && tooltip.classList.add('tooltip');
1495
+ !tooltip.classList.contains(ops.animation) && tooltip.classList.add(ops.animation);
1496
+ !tooltip.classList.contains(placementClass) && tooltip.classList.add(placementClass);
1497
+ ops.container.appendChild(tooltip);
1498
+ }
1499
+ }
1500
+ function updateTooltip() {
1501
+ styleTip(element, tooltip, ops.placement, ops.container);
1502
+ }
1503
+ function showTooltip() {
1504
+ !tooltip.classList.contains('show') && ( tooltip.classList.add('show') );
1505
+ }
1506
+ function touchHandler(e){
1507
+ if ( tooltip && tooltip.contains(e.target) || e.target === element || element.contains(e.target)) ; else {
1508
+ self.hide();
1509
+ }
1510
+ }
1511
+ function toggleAction(action){
1512
+ action = action ? 'addEventListener' : 'removeEventListener';
1513
+ document[action]( 'touchstart', touchHandler, passiveHandler );
1514
+ window[action]( 'resize', self.hide, passiveHandler );
1515
+ }
1516
+ function showAction() {
1517
+ toggleAction(1);
1518
+ dispatchCustomEvent.call(element, shownCustomEvent);
1519
+ }
1520
+ function hideAction() {
1521
+ toggleAction();
1522
+ removeToolTip();
1523
+ dispatchCustomEvent.call(element, hiddenCustomEvent);
1524
+ }
1525
+ function toggleEvents(action) {
1526
+ action = action ? 'addEventListener' : 'removeEventListener';
1527
+ element[action](mouseClickEvents.down, self.show,false);
1528
+ element[action](mouseHoverEvents[0], self.show,false);
1529
+ element[action](mouseHoverEvents[1], self.hide,false);
1530
+ }
1531
+ self.show = function () {
1532
+ clearTimeout(timer);
1533
+ timer = setTimeout( function () {
1534
+ if (tooltip === null) {
1535
+ dispatchCustomEvent.call(element, showCustomEvent);
1536
+ if (showCustomEvent.defaultPrevented) { return; }
1537
+ if(createToolTip() !== false) {
1538
+ updateTooltip();
1539
+ showTooltip();
1540
+ !!ops.animation ? emulateTransitionEnd(tooltip, showAction) : showAction();
1541
+ }
1542
+ }
1543
+ }, 20 );
1544
+ };
1545
+ self.hide = function () {
1546
+ clearTimeout(timer);
1547
+ timer = setTimeout( function () {
1548
+ if (tooltip && tooltip.classList.contains('show')) {
1549
+ dispatchCustomEvent.call(element, hideCustomEvent);
1550
+ if (hideCustomEvent.defaultPrevented) { return; }
1551
+ tooltip.classList.remove('show');
1552
+ !!ops.animation ? emulateTransitionEnd(tooltip, hideAction) : hideAction();
1553
+ }
1554
+ }, ops.delay);
1555
+ };
1556
+ self.toggle = function () {
1557
+ if (!tooltip) { self.show(); }
1558
+ else { self.hide(); }
1559
+ };
1560
+ self.dispose = function () {
1561
+ toggleEvents();
1562
+ self.hide();
1563
+ element.setAttribute('title', element.getAttribute('data-original-title'));
1564
+ element.removeAttribute('data-original-title');
1565
+ delete element.Tooltip;
1566
+ };
1567
+ element = queryElement(element);
1568
+ element.Tooltip && element.Tooltip.dispose();
1569
+ animationData = element.getAttribute('data-animation');
1570
+ placementData = element.getAttribute('data-placement');
1571
+ delayData = element.getAttribute('data-delay');
1572
+ containerData = element.getAttribute('data-container');
1573
+ showCustomEvent = bootstrapCustomEvent('show', 'tooltip');
1574
+ shownCustomEvent = bootstrapCustomEvent('shown', 'tooltip');
1575
+ hideCustomEvent = bootstrapCustomEvent('hide', 'tooltip');
1576
+ hiddenCustomEvent = bootstrapCustomEvent('hidden', 'tooltip');
1577
+ containerElement = queryElement(options.container);
1578
+ containerDataElement = queryElement(containerData);
1579
+ modal = element.closest('.modal');
1580
+ navbarFixedTop = element.closest('.fixed-top');
1581
+ navbarFixedBottom = element.closest('.fixed-bottom');
1582
+ ops.animation = options.animation && options.animation !== 'fade' ? options.animation : animationData || 'fade';
1583
+ ops.placement = options.placement ? options.placement : placementData || 'top';
1584
+ ops.template = options.template ? options.template : null;
1585
+ ops.delay = parseInt(options.delay || delayData) || 200;
1586
+ ops.container = containerElement ? containerElement
1587
+ : containerDataElement ? containerDataElement
1588
+ : navbarFixedTop ? navbarFixedTop
1589
+ : navbarFixedBottom ? navbarFixedBottom
1590
+ : modal ? modal : document.body;
1591
+ placementClass = "bs-tooltip-" + (ops.placement);
1592
+ titleString = getTitle();
1593
+ if ( !titleString ) { return; }
1594
+ if (!element.Tooltip) {
1595
+ element.setAttribute('data-original-title',titleString);
1596
+ element.removeAttribute('title');
1597
+ toggleEvents(1);
1598
+ }
1599
+ element.Tooltip = self;
1600
+ }
1601
+
1602
+ var componentsInit = {};
1603
+
1604
+ function initializeDataAPI( Constructor, collection ){
1605
+ Array.from(collection).map(function (x){ return new Constructor(x); });
1606
+ }
1607
+ function initCallback(lookUp){
1608
+ lookUp = lookUp || document;
1609
+ for (var component in componentsInit) {
1610
+ initializeDataAPI( componentsInit[component][0], lookUp.querySelectorAll (componentsInit[component][1]) );
1611
+ }
1612
+ }
1613
+
1614
+ componentsInit.Alert = [ Alert, '[data-dismiss="alert"]'];
1615
+ componentsInit.Button = [ Button, '[data-toggle="buttons"]' ];
1616
+ componentsInit.Carousel = [ Carousel, '[data-ride="carousel"]' ];
1617
+ componentsInit.Collapse = [ Collapse, '[data-toggle="collapse"]' ];
1618
+ componentsInit.Dropdown = [ Dropdown, '[data-toggle="dropdown"]'];
1619
+ componentsInit.Modal = [ Modal, '[data-toggle="modal"]' ];
1620
+ componentsInit.Popover = [ Popover, '[data-toggle="popover"],[data-tip="popover"]' ];
1621
+ componentsInit.ScrollSpy = [ ScrollSpy, '[data-spy="scroll"]' ];
1622
+ componentsInit.Tab = [ Tab, '[data-toggle="tab"]' ];
1623
+ componentsInit.Toast = [ Toast, '[data-dismiss="toast"]' ];
1624
+ componentsInit.Tooltip = [ Tooltip, '[data-toggle="tooltip"],[data-tip="tooltip"]' ];
1625
+ document.body ? initCallback() : document.addEventListener( 'DOMContentLoaded', function initWrapper(){
1626
+ initCallback();
1627
+ document.removeEventListener('DOMContentLoaded',initWrapper,false);
1628
+ }, false );
1629
+
1630
+ function removeElementDataAPI( ConstructorName, collection ){
1631
+ Array.from(collection).map(function (x){ return x[ConstructorName].dispose(); });
1632
+ }
1633
+ function removeDataAPI(lookUp) {
1634
+ lookUp = lookUp || document;
1635
+ for (var component in componentsInit) {
1636
+ removeElementDataAPI( component, lookUp.querySelectorAll (componentsInit[component][1]) );
1637
+ }
1638
+ }
1639
+
1640
+ var version = "3.0.10";
1641
+
1642
+ var index = {
1643
+ Alert: Alert,
1644
+ Button: Button,
1645
+ Carousel: Carousel,
1646
+ Collapse: Collapse,
1647
+ Dropdown: Dropdown,
1648
+ Modal: Modal,
1649
+ Popover: Popover,
1650
+ ScrollSpy: ScrollSpy,
1651
+ Tab: Tab,
1652
+ Toast: Toast,
1653
+ Tooltip: Tooltip,
1654
+ initCallback: initCallback,
1655
+ removeDataAPI: removeDataAPI,
1656
+ componentsInit: componentsInit,
1657
+ Version: version
1658
+ };
1659
+
1660
+ return index;
1661
+
1662
+ })));