good_job 1.2.4 → 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -7
  3. data/README.md +13 -10
  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 +50 -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 +40 -24
  23. data/lib/good_job/adapter.rb +38 -0
  24. data/lib/good_job/cli.rb +30 -7
  25. data/lib/good_job/configuration.rb +44 -0
  26. data/lib/good_job/job.rb +116 -20
  27. data/lib/good_job/lockable.rb +119 -6
  28. data/lib/good_job/log_subscriber.rb +70 -4
  29. data/lib/good_job/multi_scheduler.rb +6 -0
  30. data/lib/good_job/notifier.rb +55 -29
  31. data/lib/good_job/performer.rb +38 -0
  32. data/lib/good_job/railtie.rb +1 -0
  33. data/lib/good_job/scheduler.rb +33 -20
  34. data/lib/good_job/version.rb +2 -1
  35. metadata +96 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f3fa6d323c18dfb49aaf3b87f0ca71492a935752e7ca140880b68daa72a60f9
4
- data.tar.gz: 999b400b7e1dd22206898fb1cdc35f6255088ce94c3562fa48fe97fc20a23672
3
+ metadata.gz: ca2673887424565881a47ad6eaf9300b6281e43c87066f01ceb899b76716d43e
4
+ data.tar.gz: 6a0f749171316300ebfc7257d1d62efc330092391f026d6f50e20ccda5fee208
5
5
  SHA512:
6
- metadata.gz: 1b9236562e20d4da1e5738d9b143fabe9377d854b392b6b0eb2d77c0009d9f8a0c82ef94b414446a7b5db0b2a43e477ad4b8a6c7bff737b69c9d8a744ee29151
7
- data.tar.gz: cfcc994ea2f6e9a26c3b1115ccb2df6383e2897c3db891415745b86769dbdc411a62b7d60c89aa7ddb6880009c6124c795cef33efff55daa860ed7fd90e1b81d
6
+ metadata.gz: 4fcef37d707e0d25f44965949abfeba914a4ee9f743ed32498e28df95751f666bdfb942c12049b23d78ff2b52e14465ca1cda2007bfae3541f9f0c53e6f74964
7
+ data.tar.gz: db7d224567bde1427210638226d5005dd279a2c73c97ff9c19eb40edc523ae7863973cccf7d263a08dcf56ec66fd112a492afa269dad4b12cc70b79027baf296
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## [v1.2.5](https://github.com/bensheldon/good_job/tree/v1.2.5) (2020-09-17)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.2.4...v1.2.5)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Use Zeitwerk for auto-loading [\#87](https://github.com/bensheldon/good_job/issues/87)
10
+
11
+ **Fixed bugs:**
12
+
13
+ - `poll-interval=-1` does not disable polling as intended [\#133](https://github.com/bensheldon/good_job/issues/133)
14
+
15
+ **Closed issues:**
16
+
17
+ - Lint - Introduce line character limits [\#122](https://github.com/bensheldon/good_job/issues/122)
18
+ - Jobs are not processed in multi schema setup. Apartment + GoodJob \( post 1.1.2 \) [\#117](https://github.com/bensheldon/good_job/issues/117)
19
+ - Host a documentation sprint [\#48](https://github.com/bensheldon/good_job/issues/48)
20
+
21
+ **Merged pull requests:**
22
+
23
+ - Test GoodJob against Rails HEAD [\#144](https://github.com/bensheldon/good_job/pull/144) ([bensheldon](https://github.com/bensheldon))
24
+ - Update Gemspec to reflect that GoodJob is not compatible with Rails 5.1 [\#143](https://github.com/bensheldon/good_job/pull/143) ([bensheldon](https://github.com/bensheldon))
25
+ - Drop Ruby 2.4 support [\#142](https://github.com/bensheldon/good_job/pull/142) ([morgoth](https://github.com/morgoth))
26
+ - Prevent jobs hanging [\#141](https://github.com/bensheldon/good_job/pull/141) ([morgoth](https://github.com/morgoth))
27
+ - Remove arguments from perform method [\#140](https://github.com/bensheldon/good_job/pull/140) ([morgoth](https://github.com/morgoth))
28
+ - Extract "execute" method to reduce "perform" method complexity [\#138](https://github.com/bensheldon/good_job/pull/138) ([morgoth](https://github.com/morgoth))
29
+ - Correct example on how to configure multiple queues by command line. [\#135](https://github.com/bensheldon/good_job/pull/135) ([morgoth](https://github.com/morgoth))
30
+ - Add explicit require\_paths to gemspec for engine [\#134](https://github.com/bensheldon/good_job/pull/134) ([bensheldon](https://github.com/bensheldon))
31
+ - Spike on data dashboard; pull in full Bootstrap CSS and JS [\#131](https://github.com/bensheldon/good_job/pull/131) ([bensheldon](https://github.com/bensheldon))
32
+ - Update ActionMailer Job class, to match the default [\#130](https://github.com/bensheldon/good_job/pull/130) ([morgoth](https://github.com/morgoth))
33
+ - Add initial Engine scaffold [\#125](https://github.com/bensheldon/good_job/pull/125) ([bensheldon](https://github.com/bensheldon))
34
+ - Use `connection.quote\_table\_name` and add spacing for SQL concatenation [\#124](https://github.com/bensheldon/good_job/pull/124) ([bensheldon](https://github.com/bensheldon))
35
+ - Zeitwerk Loader Implementation [\#123](https://github.com/bensheldon/good_job/pull/123) ([gadimbaylisahil](https://github.com/gadimbaylisahil))
36
+ - Update code-level documentation [\#111](https://github.com/bensheldon/good_job/pull/111) ([bensheldon](https://github.com/bensheldon))
37
+
3
38
  ## [v1.2.4](https://github.com/bensheldon/good_job/tree/v1.2.4) (2020-09-01)
4
39
 
5
40
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.2.3...v1.2.4)
@@ -88,7 +123,6 @@
88
123
  **Merged pull requests:**
89
124
 
90
125
  - Document GoodJob module [\#83](https://github.com/bensheldon/good_job/pull/83) ([bensheldon](https://github.com/bensheldon))
91
- - Add Postgres LISTEN/NOTIFY support [\#82](https://github.com/bensheldon/good_job/pull/82) ([bensheldon](https://github.com/bensheldon))
92
126
 
93
127
  ## [v1.1.4](https://github.com/bensheldon/good_job/tree/v1.1.4) (2020-08-19)
94
128
 
@@ -101,6 +135,7 @@
101
135
 
102
136
  **Merged pull requests:**
103
137
 
138
+ - Add Postgres LISTEN/NOTIFY support [\#82](https://github.com/bensheldon/good_job/pull/82) ([bensheldon](https://github.com/bensheldon))
104
139
  - Allow Schedulers to filter \#create\_thread to avoid flood of queries when running async with multiple schedulers [\#81](https://github.com/bensheldon/good_job/pull/81) ([bensheldon](https://github.com/bensheldon))
105
140
  - Fully name scheduler threadpools and thread names; refactor CLI STDOUT [\#80](https://github.com/bensheldon/good_job/pull/80) ([bensheldon](https://github.com/bensheldon))
106
141
 
@@ -182,6 +217,10 @@
182
217
 
183
218
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.0.1...v1.0.2)
184
219
 
220
+ **Fixed bugs:**
221
+
222
+ - Fix counting of available execution threads [\#58](https://github.com/bensheldon/good_job/pull/58) ([bensheldon](https://github.com/bensheldon))
223
+
185
224
  **Merged pull requests:**
186
225
 
187
226
  - Add migration generator [\#56](https://github.com/bensheldon/good_job/pull/56) ([thedanbob](https://github.com/thedanbob))
@@ -203,10 +242,6 @@
203
242
 
204
243
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v0.8.2...v0.9.0)
205
244
 
206
- **Fixed bugs:**
207
-
208
- - Fix counting of available execution threads [\#58](https://github.com/bensheldon/good_job/pull/58) ([bensheldon](https://github.com/bensheldon))
209
-
210
245
  **Merged pull requests:**
211
246
 
212
247
  - Allow preservation of finished job records [\#46](https://github.com/bensheldon/good_job/pull/46) ([bensheldon](https://github.com/bensheldon))
@@ -287,7 +322,6 @@
287
322
 
288
323
  - Improve ActiveRecord usage for advisory locking [\#24](https://github.com/bensheldon/good_job/pull/24) ([bensheldon](https://github.com/bensheldon))
289
324
  - Remove support for Rails 5.1 [\#23](https://github.com/bensheldon/good_job/pull/23) ([bensheldon](https://github.com/bensheldon))
290
- - Clean up Gemspec [\#15](https://github.com/bensheldon/good_job/pull/15) ([bensheldon](https://github.com/bensheldon))
291
325
 
292
326
  ## [v0.3.0](https://github.com/bensheldon/good_job/tree/v0.3.0) (2020-03-22)
293
327
 
@@ -315,10 +349,12 @@
315
349
 
316
350
  **Merged pull requests:**
317
351
 
352
+ - Clean up Gemspec [\#15](https://github.com/bensheldon/good_job/pull/15) ([bensheldon](https://github.com/bensheldon))
318
353
  - Set up Rubocop [\#14](https://github.com/bensheldon/good_job/pull/14) ([bensheldon](https://github.com/bensheldon))
319
354
  - Add pg gem as explicit dependency [\#13](https://github.com/bensheldon/good_job/pull/13) ([bensheldon](https://github.com/bensheldon))
320
355
  - Bump nokogiri from 1.10.7 to 1.10.9 [\#12](https://github.com/bensheldon/good_job/pull/12) ([dependabot[bot]](https://github.com/apps/dependabot))
321
356
  - Add Appraisal with tests for Rails 5.1, 5.2, 6.0 [\#11](https://github.com/bensheldon/good_job/pull/11) ([bensheldon](https://github.com/bensheldon))
357
+ - Use Rails.logger and ActiveSupport::Notifications for logging instead of puts [\#10](https://github.com/bensheldon/good_job/pull/10) ([bensheldon](https://github.com/bensheldon))
322
358
 
323
359
  ## [v0.2.0](https://github.com/bensheldon/good_job/tree/v0.2.0) (2020-03-06)
324
360
 
@@ -326,7 +362,6 @@
326
362
 
327
363
  **Merged pull requests:**
328
364
 
329
- - Use Rails.logger and ActiveSupport::Notifications for logging instead of puts [\#10](https://github.com/bensheldon/good_job/pull/10) ([bensheldon](https://github.com/bensheldon))
330
365
  - Remove minitest files [\#9](https://github.com/bensheldon/good_job/pull/9) ([bensheldon](https://github.com/bensheldon))
331
366
  - Use scheduled\_at and priority for scheduling [\#8](https://github.com/bensheldon/good_job/pull/8) ([bensheldon](https://github.com/bensheldon))
332
367
  - Create Github Action workflow for PRs and Issues [\#7](https://github.com/bensheldon/good_job/pull/7) ([bensheldon](https://github.com/bensheldon))
data/README.md CHANGED
@@ -184,7 +184,7 @@ To use GoodJob, you can set `config.active_job.queue_adapter` to a `:good_job` o
184
184
 
185
185
  - `execution_mode` (symbol) specifies how and where jobs should be executed. You can also set this with the environment variable `GOOD_JOB_EXECUTION_MODE`. It can be any one of:
186
186
  - `:inline` executes jobs immediately in whatever process queued them (usually the web server process). This should only be used in test and development environments.
187
- - `:external` causes the adapter to equeue jobs, but not execute them. When using this option (the default for production environments), you’ll need to use the command-line tool to actually execute your jobs.
187
+ - `:external` causes the adapter to enqueue jobs, but not execute them. When using this option (the default for production environments), you’ll need to use the command-line tool to actually execute your jobs.
188
188
  - `:async` causes the adapter to execute you jobs in separate threads in whatever process queued them (usually the web process). This is akin to running the command-line tool’s code inside your web server. It can be more economical for small workloads (you don’t need a separate machine or environment for running your jobs), but if your web server is under heavy load or your jobs require a lot of resources, you should choose `:external` instead.
189
189
  - `max_threads` (integer) sets the maximum number of threads to use when `execution_mode` is set to `:async`. You can also set this with the environment variable `GOOD_JOB_MAX_THREADS`.
190
190
  - `queues` (string) determines which queues to execute jobs from when `execution_mode` is set to `:async`. See the description of `good_job start` for more details on the format of this string. You can also set this with the environment variable `GOOD_JOB_QUEUES`.
@@ -210,7 +210,7 @@ Good Job’s general behavior can also be configured via several attributes dire
210
210
  - **`GoodJob.logger`** ([Rails Logger](https://api.rubyonrails.org/classes/ActiveSupport/Logger.html)) lets you set a custom logger for GoodJob. It should be an instance of a Rails `Logger`.
211
211
  - **`GoodJob.preserve_job_records`** (boolean) keeps job records in your database even after jobs are completed. (Default: `false`)
212
212
  - **`GoodJob.reperform_jobs_on_standard_error`** (boolean) causes jobs to be re-queued and retried if they raise an instance of `StandardError`. Instances of `Exception`, like SIGINT, will *always* be retried, regardless of this attribute’s value. (Default: `true`)
213
- - **`GoodJob.on_thread_error`** (proc, lambda, or callable) will be called when a job raises an error. It can be useful for logging errors to bug tracking services, like Sentry or Airbrake.
213
+ - **`GoodJob.on_thread_error`** (proc, lambda, or callable) will be called when an Exception. It can be useful for logging errors to bug tracking services, like Sentry or Airbrake.
214
214
 
215
215
  You’ll generally want to configure these in `config/initializers/good_job.rb`, like so:
216
216
 
@@ -291,16 +291,16 @@ end
291
291
 
292
292
  #### ActionMailer retries
293
293
 
294
- Any configuration in `ApplicationJob` will have to be duplicated on `ActionMailer::DeliveryJob` because ActionMailer uses a custom class, `ActionMailer::DeliveryJob`, which inherits from `ActiveJob::Base`, rather than your applications `ApplicationJob`.
294
+ Any configuration in `ApplicationJob` will have to be duplicated on `ActionMailer::MailDeliveryJob` (`ActionMailer::DeliveryJob` in Rails 5.2 or earlier) because ActionMailer uses a custom class, `ActionMailer::MailDeliveryJob`, which inherits from `ActiveJob::Base`, rather than your applications `ApplicationJob`.
295
295
 
296
- You can use an initializer to configure `ActionMailer::DeliveryJob`, for example:
296
+ You can use an initializer to configure `ActionMailer::MailDeliveryJob`, for example:
297
297
 
298
298
  ```ruby
299
299
  # config/initializers/good_job.rb
300
- ActionMailer::DeliveryJob.retry_on StandardError, wait: :exponentially_longer, attempts: Float::INFINITY
300
+ ActionMailer::MailDeliveryJob.retry_on StandardError, wait: :exponentially_longer, attempts: Float::INFINITY
301
301
 
302
302
  # With Sentry (or Bugsnag, Airbrake, Honeybadger, etc.)
303
- ActionMailer::DeliveryJob.around_perform do |_job, block|
303
+ ActionMailer::MailDeliveryJob.around_perform do |_job, block|
304
304
  block.call
305
305
  rescue StandardError => e
306
306
  Raven.capture_exception(e)
@@ -308,6 +308,9 @@ rescue StandardError => e
308
308
  end
309
309
  ```
310
310
 
311
+ Note, that `ActionMailer::MailDeliveryJob` is a default since Rails 6.0. Be sure that your app is using that class, as it
312
+ might also be configured to use (deprecated now) `ActionMailer::DeliveryJob`.
313
+
311
314
  ### Timeouts
312
315
 
313
316
  Job timeouts can be configured with an `around_perform`:
@@ -332,7 +335,7 @@ By default, GoodJob creates a single thread execution pool that will execute job
332
335
  - Multiple execution pools within a single process:
333
336
 
334
337
  ```bash
335
- $ bundle exec good_job --queues=transactional_messages:2;batch_processing:1;-transactional_messages,batch_processing:2;* --max-threads=5
338
+ $ bundle exec good_job --queues="transactional_messages:2;batch_processing:1;-transactional_messages,batch_processing:2;*" --max-threads=5
336
339
  ```
337
340
 
338
341
  This configuration will result in a single process with 4 isolated thread execution pools. Isolated execution pools are separated with a semicolon (`;`) and queue names and thread counts with a colon (`:`)
@@ -357,11 +360,11 @@ By default, GoodJob creates a single thread execution pool that will execute job
357
360
 
358
361
  # Separate dyno types
359
362
  worker: bundle exec good_job --max-threads=5
360
- transactional_worker: bundle exec good_job --queues=transactional_messages --max-threads=2
361
- batch_worker: bundle exec good_job --queues=batch_processing --max-threads=1
363
+ transactional_worker: bundle exec good_job --queues="transactional_messages" --max-threads=2
364
+ batch_worker: bundle exec good_job --queues="batch_processing" --max-threads=1
362
365
 
363
366
  # Combined multi-process dyno
364
- combined_worker: bundle exec good_job --max-threads=5 & bundle exec good_job --queues=transactional_messages --max-threads=2 & bundle exec good_job --queues=batch_processing --max-threads=1 & wait -n
367
+ combined_worker: bundle exec good_job --max-threads=5 & bundle exec good_job --queues="transactional_messages" --max-threads=2 & bundle exec good_job --queues="batch_processing" --max-threads=1 & wait -n
365
368
  ```
366
369
 
367
370
  Running multiple processes can optimize for CPU performance at the expense of greater memory and system resource usage.
@@ -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)
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,50 @@
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 "All jobs", root_path, class: ["nav-link", ("active" if current_page?(root_path))] %>
31
+ </li>
32
+ <li class="nav-item">
33
+ <%= link_to "Upcoming Jobs", 'todo', class: ["nav-link", ("active" if current_page?('todo'))] %>
34
+ </li>
35
+ <li class="nav-item">
36
+ <%= link_to "Finished Jobs", 'todo', class: ["nav-link", ("active" if current_page?('todo'))] %>
37
+ </li>
38
+ <li class="nav-item">
39
+ <%= link_to "Errored Jobs", 'todo', class: ["nav-link", ("active" if current_page?('todo'))] %>
40
+ </li>
41
+ </ul>
42
+ </div>
43
+ </div>
44
+ </nav>
45
+
46
+ <div class="container">
47
+ <%= yield %>
48
+ </div>
49
+ </body>
50
+ </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>ActiveJob Params</th>
10
+ <th>Error</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.serialized_params %></td>
21
+ <td><%= job.error %></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
+ })));