good_job 1.2.4 → 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -7
- data/README.md +13 -10
- data/engine/app/controllers/good_job/active_jobs_controller.rb +8 -0
- data/engine/app/controllers/good_job/base_controller.rb +5 -0
- data/engine/app/controllers/good_job/dashboards_controller.rb +50 -0
- data/engine/app/helpers/good_job/application_helper.rb +4 -0
- data/engine/app/views/assets/_style.css.erb +16 -0
- data/engine/app/views/good_job/active_jobs/show.html.erb +1 -0
- data/engine/app/views/good_job/dashboards/index.html.erb +19 -0
- data/engine/app/views/layouts/good_job/base.html.erb +50 -0
- data/engine/app/views/shared/_chart.erb +51 -0
- data/engine/app/views/shared/_jobs_table.erb +26 -0
- data/engine/app/views/vendor/bootstrap/_bootstrap-native.js.erb +1662 -0
- data/engine/app/views/vendor/bootstrap/_bootstrap.css.erb +10258 -0
- data/engine/app/views/vendor/chartist/_chartist.css.erb +613 -0
- data/engine/app/views/vendor/chartist/_chartist.js.erb +4516 -0
- data/engine/config/routes.rb +4 -0
- data/engine/lib/good_job/engine.rb +5 -0
- data/lib/active_job/queue_adapters/good_job_adapter.rb +3 -2
- data/lib/generators/good_job/install_generator.rb +8 -0
- data/lib/good_job.rb +40 -24
- data/lib/good_job/adapter.rb +38 -0
- data/lib/good_job/cli.rb +30 -7
- data/lib/good_job/configuration.rb +44 -0
- data/lib/good_job/job.rb +116 -20
- data/lib/good_job/lockable.rb +119 -6
- data/lib/good_job/log_subscriber.rb +70 -4
- data/lib/good_job/multi_scheduler.rb +6 -0
- data/lib/good_job/notifier.rb +55 -29
- data/lib/good_job/performer.rb +38 -0
- data/lib/good_job/railtie.rb +1 -0
- data/lib/good_job/scheduler.rb +33 -20
- data/lib/good_job/version.rb +2 -1
- metadata +96 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ca2673887424565881a47ad6eaf9300b6281e43c87066f01ceb899b76716d43e
|
|
4
|
+
data.tar.gz: 6a0f749171316300ebfc7257d1d62efc330092391f026d6f50e20ccda5fee208
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4fcef37d707e0d25f44965949abfeba914a4ee9f743ed32498e28df95751f666bdfb942c12049b23d78ff2b52e14465ca1cda2007bfae3541f9f0c53e6f74964
|
|
7
|
+
data.tar.gz: db7d224567bde1427210638226d5005dd279a2c73c97ff9c19eb40edc523ae7863973cccf7d263a08dcf56ec66fd112a492afa269dad4b12cc70b79027baf296
|
data/CHANGELOG.md
CHANGED
|
@@ -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
|
|
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
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
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,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,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">»</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
|
+
})));
|