archangel 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.jshintrc +2 -0
  3. data/.travis.yml +2 -0
  4. data/Gemfile +0 -1
  5. data/README.md +0 -1
  6. data/app/assets/javascripts/archangel/input/datetimepicker.js +87 -0
  7. data/app/assets/javascripts/archangel/object/translate/datetimepicker.js.erb +41 -0
  8. data/app/models/archangel/entry.rb +27 -0
  9. data/app/models/archangel/page.rb +17 -0
  10. data/app/policies/archangel/application_policy.rb +7 -7
  11. data/app/policies/archangel/asset_policy.rb +1 -1
  12. data/app/policies/archangel/entry_policy.rb +1 -1
  13. data/app/policies/archangel/site_policy.rb +2 -2
  14. data/app/themes/default/assets/javascripts/default/backend.js +4 -4
  15. data/app/themes/default/assets/stylesheets/default/backend.css +3 -0
  16. data/app/themes/default/assets/stylesheets/default/backend/core.scss +1 -1
  17. data/app/themes/default/assets/stylesheets/default/common/_selectize.scss +126 -77
  18. data/app/views/archangel/backend/entries/index.html.erb +1 -1
  19. data/app/views/archangel/backend/pages/index.html.erb +1 -1
  20. data/archangel.gemspec +0 -1
  21. data/config/initializers/simple_form_bootstrap.rb +1 -2
  22. data/config/locales/en.yml +31 -17
  23. data/docs/Extension/Ideas.md +2 -0
  24. data/lib/archangel.rb +0 -1
  25. data/lib/archangel/liquid/tags/application_tag.rb +58 -0
  26. data/lib/archangel/liquid/tags/asset_tag.rb +3 -37
  27. data/lib/archangel/liquid/tags/collection_tag.rb +1 -12
  28. data/lib/archangel/liquid/tags/gist_tag.rb +3 -37
  29. data/lib/archangel/liquid/tags/vimeo_tag.rb +3 -24
  30. data/lib/archangel/liquid/tags/widget_tag.rb +1 -6
  31. data/lib/archangel/liquid/tags/youtube_tag.rb +3 -24
  32. data/lib/archangel/testing_support/factories/archangel_entries.rb +5 -1
  33. data/lib/archangel/testing_support/factories/archangel_sites.rb +2 -0
  34. data/lib/archangel/version.rb +1 -1
  35. data/spec/controllers/archangel/frontend/pages_controller_spec.rb +1 -0
  36. data/spec/features/auth/log_in_spec.rb +56 -26
  37. data/spec/features/frontend/drop_variables_spec.rb +18 -2
  38. data/spec/features/frontend/homepage_redirect_spec.rb +2 -2
  39. data/spec/helpers/archangel/flash_helper_spec.rb +7 -3
  40. data/spec/models/archangel/asset_spec.rb +7 -2
  41. data/spec/models/archangel/collection_spec.rb +0 -3
  42. data/spec/models/archangel/entry_spec.rb +40 -0
  43. data/spec/models/archangel/page_spec.rb +20 -3
  44. data/spec/models/archangel/user_spec.rb +0 -3
  45. data/spec/models/archangel/widget_spec.rb +0 -3
  46. data/vendor/assets/javascripts/daterangepicker/daterangepicker.js +1529 -0
  47. data/vendor/assets/stylesheets/daterangepicker/_daterangepicker.scss +407 -0
  48. metadata +6 -18
  49. data/app/assets/javascripts/archangel/input/datetime_picker.js +0 -46
  50. data/app/assets/javascripts/archangel/object/translate/datetime_picker.js.erb +0 -26
@@ -11,7 +11,11 @@ FactoryBot.define do
11
11
  end
12
12
 
13
13
  trait :unavailable do
14
- deleted_at nil
14
+ available_at nil
15
+ end
16
+
17
+ trait :future do
18
+ available_at { 1.week.from_now }
15
19
  end
16
20
  end
17
21
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  FactoryBot.define do
4
4
  factory :site, class: "Archangel::Site" do
5
+ initialize_with { Archangel::Site.first_or_create }
6
+
5
7
  sequence(:name) { |n| "Site #{n} Name" }
6
8
  locale "en"
7
9
  meta_keywords "default,keywords,of,my,site"
@@ -4,7 +4,7 @@ module Archangel
4
4
  ##
5
5
  # Archangel gem version
6
6
  #
7
- VERSION = "0.0.6".freeze
7
+ VERSION = "0.0.7".freeze
8
8
 
9
9
  ##
10
10
  # Archangel version
@@ -40,6 +40,7 @@ module Archangel
40
40
  get :show, params: { path: page.path }
41
41
 
42
42
  expect(response).to redirect_to(root_path)
43
+ expect(response.status).to eq(301)
43
44
  end
44
45
 
45
46
  it "returns a 404 status code when page is not found" do
@@ -13,7 +13,7 @@ RSpec.feature "Auth log in", type: :feature do
13
13
  fill_in "Password", with: "password"
14
14
  click_button "Log in"
15
15
 
16
- expect(page.body).to have_content "Signed in successfully"
16
+ expect(page).to have_content I18n.t("devise.sessions.signed_in")
17
17
  end
18
18
 
19
19
  it "locks account after 4 failed attempts" do
@@ -22,33 +22,32 @@ RSpec.feature "Auth log in", type: :feature do
22
22
 
23
23
  visit archangel.new_user_session_path
24
24
 
25
- (1..2).each do |attempt_num|
25
+ attempts = 3
26
+ (1..attempts).each do |attempt|
26
27
  fill_in "Email", with: email
27
- fill_in "Password", with: "wrong-password-#{attempt_num}"
28
+ fill_in "Password", with: "wrong-password-#{attempt}"
28
29
  click_button "Log in"
29
30
 
30
- expect(page.body).to have_content "Invalid Email or password"
31
- end
31
+ message = I18n.t("devise.failure.not_found_in_database",
32
+ authentication_keys: "Email")
33
+ message = I18n.t("devise.failure.last_attempt") if attempt == attempts
32
34
 
33
- fill_in "Email", with: email
34
- fill_in "Password", with: "wrong-password-3"
35
- click_button "Log in"
36
-
37
- expect(page.body).to have_content(
38
- "You have one more attempt before your account is locked"
39
- )
35
+ expect(page).to have_content message
36
+ end
40
37
 
41
38
  fill_in "Email", with: email
42
- fill_in "Password", with: "wrong-password-4"
39
+ fill_in "Password", with: "wrong-password-#{attempts + 1}"
43
40
  click_button "Log in"
44
41
 
45
- expect(page.body).to have_content "Your account is locked"
42
+ expect(page).to have_content(I18n.t("devise.failure.locked"))
46
43
  end
47
44
  end
48
45
 
49
46
  describe "unconfirmed user" do
50
47
  it "cannot login" do
51
- create(:user, :unconfirmed, email: "me@example.com", password: "password")
48
+ create(:user, :unconfirmed, email: "me@example.com",
49
+ password: "password",
50
+ created_at: 1.week.ago)
52
51
 
53
52
  visit archangel.new_user_session_path
54
53
 
@@ -57,16 +56,13 @@ RSpec.feature "Auth log in", type: :feature do
57
56
  click_button "Log in"
58
57
 
59
58
  expect(current_path).to eq(archangel.new_user_session_path)
60
- expect(page).not_to have_content "Signed in successfully"
61
- expect(page).to have_content(
62
- "You have to confirm your email address before continuing"
63
- )
59
+ expect(page).to have_content I18n.t("devise.failure.unconfirmed")
64
60
  end
65
61
  end
66
62
 
67
63
  describe "locked user" do
68
64
  it "cannot login" do
69
- create(:user, :unconfirmed, email: "me@example.com", password: "password")
65
+ create(:user, :locked, email: "me@example.com", password: "password")
70
66
 
71
67
  visit archangel.new_user_session_path
72
68
 
@@ -75,14 +71,11 @@ RSpec.feature "Auth log in", type: :feature do
75
71
  click_button "Log in"
76
72
 
77
73
  expect(current_path).to eq(archangel.new_user_session_path)
78
- expect(page).not_to have_content "Signed in successfully"
79
- expect(page).to have_content(
80
- "You have to confirm your email address before continuing"
81
- )
74
+ expect(page).to have_content(I18n.t("devise.failure.locked"))
82
75
  end
83
76
  end
84
77
 
85
- describe "with invalid or unknown user credentials" do
78
+ describe "unknown user credentials" do
86
79
  it "is not successful" do
87
80
  visit archangel.new_user_session_path
88
81
 
@@ -90,7 +83,44 @@ RSpec.feature "Auth log in", type: :feature do
90
83
  fill_in "Password", with: "password"
91
84
  click_button "Log in"
92
85
 
93
- expect(page.body).to have_content "Invalid Email or password"
86
+ expect(page).to(
87
+ have_content(I18n.t("devise.failure.not_found_in_database",
88
+ authentication_keys: "Email"))
89
+ )
90
+ end
91
+ end
92
+
93
+ describe "with invalid email" do
94
+ it "is not successful" do
95
+ create(:user, email: "me@example.com", password: "password")
96
+
97
+ visit archangel.new_user_session_path
98
+
99
+ fill_in "Email", with: "not_me@example.com"
100
+ fill_in "Password", with: "password"
101
+ click_button "Log in"
102
+
103
+ expect(page).to(
104
+ have_content(I18n.t("devise.failure.not_found_in_database",
105
+ authentication_keys: "Email"))
106
+ )
107
+ end
108
+ end
109
+
110
+ describe "with invalid password" do
111
+ it "is not successful" do
112
+ create(:user, email: "me@example.com", password: "password")
113
+
114
+ visit archangel.new_user_session_path
115
+
116
+ fill_in "Email", with: "me@example.com"
117
+ fill_in "Password", with: "bad_password"
118
+ click_button "Log in"
119
+
120
+ expect(page).to(
121
+ have_content(I18n.t("devise.failure.invalid",
122
+ authentication_keys: "Email"))
123
+ )
94
124
  end
95
125
  end
96
126
  end
@@ -3,7 +3,7 @@
3
3
  require "rails_helper"
4
4
 
5
5
  RSpec.feature "Default variables", type: :feature do
6
- describe "for current_page" do
6
+ describe "for $current_page" do
7
7
  let(:site) { create(:site) }
8
8
 
9
9
  it "knows the current page at root level" do
@@ -69,7 +69,7 @@ RSpec.feature "Default variables", type: :feature do
69
69
  end
70
70
  end
71
71
 
72
- describe "for page" do
72
+ describe "for $page" do
73
73
  let(:site) { create(:site) }
74
74
 
75
75
  it "knows the page properties" do
@@ -122,4 +122,20 @@ RSpec.feature "Default variables", type: :feature do
122
122
  expect(page).to have_content("Site Logo: #{site.logo}")
123
123
  end
124
124
  end
125
+
126
+ describe "for unknown variable" do
127
+ let(:site) { create(:site) }
128
+
129
+ it "responds with blank value" do
130
+ content = <<-CONTENT
131
+ Unknown Variable: ~{{ unknown_variable }}~
132
+ CONTENT
133
+
134
+ post = create(:page, site: site, content: content)
135
+
136
+ visit archangel.frontend_page_path(post.path)
137
+
138
+ expect(page).to have_content("Unknown Variable: ~~")
139
+ end
140
+ end
125
141
  end
@@ -3,13 +3,13 @@
3
3
  require "rails_helper"
4
4
 
5
5
  RSpec.feature "Home page", type: :feature do
6
- describe "when on home page slug" do
6
+ describe "when on home page path" do
7
7
  it "redirects to /" do
8
8
  home = create(:page, :homepage)
9
9
 
10
10
  visit archangel.frontend_page_path(home.path)
11
11
 
12
- expect(page.current_path).to eq archangel.root_path
12
+ expect(page.current_path).to eq archangel.frontend_root_path
13
13
  end
14
14
  end
15
15
  end
@@ -9,20 +9,24 @@ module Archangel
9
9
  expect(helper.flash_class_for("success")).to eq("success")
10
10
  end
11
11
 
12
- it "returns error class" do
12
+ it "returns `error` class" do
13
13
  expect(helper.flash_class_for("error")).to eq("danger")
14
14
  end
15
15
 
16
- it "returns alert class" do
16
+ it "returns `alert` class" do
17
17
  expect(helper.flash_class_for("alert")).to eq("warning")
18
18
  end
19
19
 
20
- it "returns notice class" do
20
+ it "returns `notice` class" do
21
21
  expect(helper.flash_class_for("notice")).to eq("info")
22
22
  end
23
23
 
24
24
  it "returns unknown class" do
25
25
  expect(helper.flash_class_for("unknown")).to eq("unknown")
26
+ expect(helper.flash_class_for("foo bar")).to eq("foo-bar")
27
+ expect(helper.flash_class_for("foo-bar")).to eq("foo-bar")
28
+ expect(helper.flash_class_for("foo_bar")).to eq("foo_bar")
29
+ expect(helper.flash_class_for("foo bar")).to eq("foo-bar")
26
30
  end
27
31
  end
28
32
  end
@@ -15,11 +15,16 @@ module Archangel
15
15
  it { is_expected.to allow_value("success.js").for(:file_name) }
16
16
  it { is_expected.to allow_value("success.jpg").for(:file_name) }
17
17
  it { is_expected.to allow_value("filename.extension").for(:file_name) }
18
+ it { is_expected.to allow_value("foo-bar.jpg").for(:file_name) }
19
+ it { is_expected.to allow_value("foo_bar.jpg").for(:file_name) }
20
+ it { is_expected.to allow_value("18.jpg").for(:file_name) }
18
21
 
19
- it { is_expected.to_not allow_value("error.c").for(:file_name) }
20
- it { is_expected.to_not allow_value("without-extension").for(:file_name) }
22
+ it { is_expected.to_not allow_value("error").for(:file_name) }
23
+ it { is_expected.to_not allow_value("error.j").for(:file_name) }
21
24
  it { is_expected.to_not allow_value("with.dot.jpg").for(:file_name) }
22
25
  it { is_expected.to_not allow_value("with space.jpg").for(:file_name) }
26
+ it { is_expected.to_not allow_value("with/slash.jpg").for(:file_name) }
27
+ it { is_expected.to_not allow_value("with/numbers.18").for(:file_name) }
23
28
  end
24
29
 
25
30
  context "associations" do
@@ -38,9 +38,6 @@ module Archangel
38
38
  end
39
39
 
40
40
  context "#column_reset" do
41
- before { ::Timecop.freeze }
42
- after { ::Timecop.return }
43
-
44
41
  it "resets `slug` to `slug` + current time" do
45
42
  resource = create(:collection)
46
43
 
@@ -18,5 +18,45 @@ module Archangel
18
18
  context "associations" do
19
19
  it { is_expected.to belong_to(:collection) }
20
20
  end
21
+
22
+ context ".available?" do
23
+ it "is available" do
24
+ entry = build(:entry)
25
+
26
+ expect(entry.available?).to be_truthy
27
+ end
28
+
29
+ it "is available in the future" do
30
+ entry = build(:entry, available_at: 1.week.from_now)
31
+
32
+ expect(entry.available?).to be_truthy
33
+ end
34
+
35
+ it "is not available" do
36
+ entry = build(:entry, :unavailable)
37
+
38
+ expect(entry.available?).to be_falsey
39
+ end
40
+ end
41
+
42
+ context "#available_status" do
43
+ it "returns `unavailable` for Entries not available" do
44
+ entry = build(:entry, :unavailable)
45
+
46
+ expect(entry.available_status).to eq("unavailable")
47
+ end
48
+
49
+ it "returns `future-available` for Entries available in the future" do
50
+ entry = build(:entry, :future)
51
+
52
+ expect(entry.available_status).to eq("future-available")
53
+ end
54
+
55
+ it "returns `available` for Entries available in the past" do
56
+ entry = build(:entry)
57
+
58
+ expect(entry.available_status).to eq("available")
59
+ end
60
+ end
21
61
  end
22
62
  end
@@ -109,6 +109,26 @@ module Archangel
109
109
  end
110
110
  end
111
111
 
112
+ context "#published_status" do
113
+ it "returns `unpublished` for Pages not published" do
114
+ page = build(:page, :unpublished)
115
+
116
+ expect(page.published_status).to eq("unpublished")
117
+ end
118
+
119
+ it "returns `future-published` for Pages published in the future" do
120
+ page = build(:page, :future)
121
+
122
+ expect(page.published_status).to eq("future-published")
123
+ end
124
+
125
+ it "returns `published` for Pages published in the past" do
126
+ page = build(:page)
127
+
128
+ expect(page.published_status).to eq("published")
129
+ end
130
+ end
131
+
112
132
  context "#to_liquid" do
113
133
  it "returns a Liquid object" do
114
134
  resource = build(:page)
@@ -118,9 +138,6 @@ module Archangel
118
138
  end
119
139
 
120
140
  context "#column_reset" do
121
- before { ::Timecop.freeze }
122
- after { ::Timecop.return }
123
-
124
141
  it "resets `slug` to `slug` + current time" do
125
142
  resource = create(:page)
126
143
 
@@ -78,9 +78,6 @@ module Archangel
78
78
  end
79
79
 
80
80
  context "#column_reset" do
81
- before { ::Timecop.freeze }
82
- after { ::Timecop.return }
83
-
84
81
  it "resets `slug` to `slug` + current time" do
85
82
  resource = create(:user)
86
83
 
@@ -46,9 +46,6 @@ module Archangel
46
46
  end
47
47
 
48
48
  context "#column_reset" do
49
- before { ::Timecop.freeze }
50
- after { ::Timecop.return }
51
-
52
49
  it "resets `slug` to `slug` + current time" do
53
50
  resource = create(:widget)
54
51
 
@@ -0,0 +1,1529 @@
1
+ /**
2
+ * @version: 3.0.2
3
+ * @author: Dan Grossman http://www.dangrossman.info/
4
+ * @copyright: Copyright (c) 2012-2018 Dan Grossman. All rights reserved.
5
+ * @license: Licensed under the MIT license. See http://www.opensource.org/licenses/mit-license.php
6
+ * @website: http://www.daterangepicker.com/
7
+ */
8
+ // Following the UMD template https://github.com/umdjs/umd/blob/master/templates/returnExportsGlobal.js
9
+ (function (root, factory) {
10
+ if (typeof define === 'function' && define.amd) {
11
+ // AMD. Make globaly available as well
12
+ define(['moment', 'jquery'], function (moment, jquery) {
13
+ if (!jquery.fn) jquery.fn = {}; // webpack server rendering
14
+ return factory(moment, jquery);
15
+ });
16
+ } else if (typeof module === 'object' && module.exports) {
17
+ // Node / Browserify
18
+ //isomorphic issue
19
+ var jQuery = (typeof window != 'undefined') ? window.jQuery : undefined;
20
+ if (!jQuery) {
21
+ jQuery = require('jquery');
22
+ if (!jQuery.fn) jQuery.fn = {};
23
+ }
24
+ var moment = (typeof window != 'undefined' && typeof window.moment != 'undefined') ? window.moment : require('moment');
25
+ module.exports = factory(moment, jQuery);
26
+ } else {
27
+ // Browser globals
28
+ root.daterangepicker = factory(root.moment, root.jQuery);
29
+ }
30
+ }(this, function(moment, $) {
31
+ var DateRangePicker = function(element, options, cb) {
32
+
33
+ //default settings for options
34
+ this.parentEl = 'body';
35
+ this.element = $(element);
36
+ this.startDate = moment().startOf('day');
37
+ this.endDate = moment().endOf('day');
38
+ this.minDate = false;
39
+ this.maxDate = false;
40
+ this.maxSpan = false;
41
+ this.autoApply = false;
42
+ this.singleDatePicker = false;
43
+ this.showDropdowns = false;
44
+ this.minYear = moment().subtract(100, 'year').format('YYYY');
45
+ this.maxYear = moment().add(100, 'year').format('YYYY');
46
+ this.showWeekNumbers = false;
47
+ this.showISOWeekNumbers = false;
48
+ this.showCustomRangeLabel = true;
49
+ this.timePicker = false;
50
+ this.timePicker24Hour = false;
51
+ this.timePickerIncrement = 1;
52
+ this.timePickerSeconds = false;
53
+ this.linkedCalendars = true;
54
+ this.autoUpdateInput = true;
55
+ this.alwaysShowCalendars = false;
56
+ this.ranges = {};
57
+
58
+ this.opens = 'right';
59
+ if (this.element.hasClass('pull-right'))
60
+ this.opens = 'left';
61
+
62
+ this.drops = 'down';
63
+ if (this.element.hasClass('dropup'))
64
+ this.drops = 'up';
65
+
66
+ this.buttonClasses = 'btn btn-sm';
67
+ this.applyButtonClasses = 'btn-primary';
68
+ this.cancelButtonClasses = 'btn-default';
69
+
70
+ this.locale = {
71
+ direction: 'ltr',
72
+ format: moment.localeData().longDateFormat('L'),
73
+ separator: ' - ',
74
+ applyLabel: 'Apply',
75
+ cancelLabel: 'Cancel',
76
+ weekLabel: 'W',
77
+ customRangeLabel: 'Custom Range',
78
+ daysOfWeek: moment.weekdaysMin(),
79
+ monthNames: moment.monthsShort(),
80
+ firstDay: moment.localeData().firstDayOfWeek()
81
+ };
82
+
83
+ this.callback = function() { };
84
+
85
+ //some state information
86
+ this.isShowing = false;
87
+ this.leftCalendar = {};
88
+ this.rightCalendar = {};
89
+
90
+ //custom options from user
91
+ if (typeof options !== 'object' || options === null)
92
+ options = {};
93
+
94
+ //allow setting options with data attributes
95
+ //data-api options will be overwritten with custom javascript options
96
+ options = $.extend(this.element.data(), options);
97
+
98
+ //html template for the picker UI
99
+ if (typeof options.template !== 'string' && !(options.template instanceof $))
100
+ options.template =
101
+ '<div class="daterangepicker">' +
102
+ '<div class="ranges"></div>' +
103
+ '<div class="drp-calendar left">' +
104
+ '<div class="calendar-table"></div>' +
105
+ '<div class="calendar-time"></div>' +
106
+ '</div>' +
107
+ '<div class="drp-calendar right">' +
108
+ '<div class="calendar-table"></div>' +
109
+ '<div class="calendar-time"></div>' +
110
+ '</div>' +
111
+ '<div class="drp-buttons">' +
112
+ '<span class="drp-selected"></span>' +
113
+ '<button class="cancelBtn" type="button"></button>' +
114
+ '<button class="applyBtn" disabled="disabled" type="button"></button> ' +
115
+ '</div>' +
116
+ '</div>';
117
+
118
+ this.parentEl = (options.parentEl && $(options.parentEl).length) ? $(options.parentEl) : $(this.parentEl);
119
+ this.container = $(options.template).appendTo(this.parentEl);
120
+
121
+ //
122
+ // handle all the possible options overriding defaults
123
+ //
124
+
125
+ if (typeof options.locale === 'object') {
126
+
127
+ if (typeof options.locale.direction === 'string')
128
+ this.locale.direction = options.locale.direction;
129
+
130
+ if (typeof options.locale.format === 'string')
131
+ this.locale.format = options.locale.format;
132
+
133
+ if (typeof options.locale.separator === 'string')
134
+ this.locale.separator = options.locale.separator;
135
+
136
+ if (typeof options.locale.daysOfWeek === 'object')
137
+ this.locale.daysOfWeek = options.locale.daysOfWeek.slice();
138
+
139
+ if (typeof options.locale.monthNames === 'object')
140
+ this.locale.monthNames = options.locale.monthNames.slice();
141
+
142
+ if (typeof options.locale.firstDay === 'number')
143
+ this.locale.firstDay = options.locale.firstDay;
144
+
145
+ if (typeof options.locale.applyLabel === 'string')
146
+ this.locale.applyLabel = options.locale.applyLabel;
147
+
148
+ if (typeof options.locale.cancelLabel === 'string')
149
+ this.locale.cancelLabel = options.locale.cancelLabel;
150
+
151
+ if (typeof options.locale.weekLabel === 'string')
152
+ this.locale.weekLabel = options.locale.weekLabel;
153
+
154
+ if (typeof options.locale.customRangeLabel === 'string'){
155
+ //Support unicode chars in the custom range name.
156
+ var elem = document.createElement('textarea');
157
+ elem.innerHTML = options.locale.customRangeLabel;
158
+ var rangeHtml = elem.value;
159
+ this.locale.customRangeLabel = rangeHtml;
160
+ }
161
+ }
162
+ this.container.addClass(this.locale.direction);
163
+
164
+ if (typeof options.startDate === 'string')
165
+ this.startDate = moment(options.startDate, this.locale.format);
166
+
167
+ if (typeof options.endDate === 'string')
168
+ this.endDate = moment(options.endDate, this.locale.format);
169
+
170
+ if (typeof options.minDate === 'string')
171
+ this.minDate = moment(options.minDate, this.locale.format);
172
+
173
+ if (typeof options.maxDate === 'string')
174
+ this.maxDate = moment(options.maxDate, this.locale.format);
175
+
176
+ if (typeof options.startDate === 'object')
177
+ this.startDate = moment(options.startDate);
178
+
179
+ if (typeof options.endDate === 'object')
180
+ this.endDate = moment(options.endDate);
181
+
182
+ if (typeof options.minDate === 'object')
183
+ this.minDate = moment(options.minDate);
184
+
185
+ if (typeof options.maxDate === 'object')
186
+ this.maxDate = moment(options.maxDate);
187
+
188
+ // sanity check for bad options
189
+ if (this.minDate && this.startDate.isBefore(this.minDate))
190
+ this.startDate = this.minDate.clone();
191
+
192
+ // sanity check for bad options
193
+ if (this.maxDate && this.endDate.isAfter(this.maxDate))
194
+ this.endDate = this.maxDate.clone();
195
+
196
+ if (typeof options.applyButtonClasses === 'string')
197
+ this.applyButtonClasses = options.applyButtonClasses;
198
+
199
+ if (typeof options.applyClass === 'string') //backwards compat
200
+ this.applyButtonClasses = options.applyClass;
201
+
202
+ if (typeof options.cancelButtonClasses === 'string')
203
+ this.cancelButtonClasses = options.cancelButtonClasses;
204
+
205
+ if (typeof options.cancelClass === 'string') //backwards compat
206
+ this.cancelButtonClasses = options.cancelClass;
207
+
208
+ if (typeof options.maxSpan === 'object')
209
+ this.maxSpan = options.maxSpan;
210
+
211
+ if (typeof options.dateLimit === 'object') //backwards compat
212
+ this.maxSpan = options.dateLimit;
213
+
214
+ if (typeof options.opens === 'string')
215
+ this.opens = options.opens;
216
+
217
+ if (typeof options.drops === 'string')
218
+ this.drops = options.drops;
219
+
220
+ if (typeof options.showWeekNumbers === 'boolean')
221
+ this.showWeekNumbers = options.showWeekNumbers;
222
+
223
+ if (typeof options.showISOWeekNumbers === 'boolean')
224
+ this.showISOWeekNumbers = options.showISOWeekNumbers;
225
+
226
+ if (typeof options.buttonClasses === 'string')
227
+ this.buttonClasses = options.buttonClasses;
228
+
229
+ if (typeof options.buttonClasses === 'object')
230
+ this.buttonClasses = options.buttonClasses.join(' ');
231
+
232
+ if (typeof options.showDropdowns === 'boolean')
233
+ this.showDropdowns = options.showDropdowns;
234
+
235
+ if (typeof options.minYear === 'number')
236
+ this.minYear = options.minYear;
237
+
238
+ if (typeof options.maxYear === 'number')
239
+ this.maxYear = options.maxYear;
240
+
241
+ if (typeof options.showCustomRangeLabel === 'boolean')
242
+ this.showCustomRangeLabel = options.showCustomRangeLabel;
243
+
244
+ if (typeof options.singleDatePicker === 'boolean') {
245
+ this.singleDatePicker = options.singleDatePicker;
246
+ if (this.singleDatePicker)
247
+ this.endDate = this.startDate.clone();
248
+ }
249
+
250
+ if (typeof options.timePicker === 'boolean')
251
+ this.timePicker = options.timePicker;
252
+
253
+ if (typeof options.timePickerSeconds === 'boolean')
254
+ this.timePickerSeconds = options.timePickerSeconds;
255
+
256
+ if (typeof options.timePickerIncrement === 'number')
257
+ this.timePickerIncrement = options.timePickerIncrement;
258
+
259
+ if (typeof options.timePicker24Hour === 'boolean')
260
+ this.timePicker24Hour = options.timePicker24Hour;
261
+
262
+ if (typeof options.autoApply === 'boolean')
263
+ this.autoApply = options.autoApply;
264
+
265
+ if (typeof options.autoUpdateInput === 'boolean')
266
+ this.autoUpdateInput = options.autoUpdateInput;
267
+
268
+ if (typeof options.linkedCalendars === 'boolean')
269
+ this.linkedCalendars = options.linkedCalendars;
270
+
271
+ if (typeof options.isInvalidDate === 'function')
272
+ this.isInvalidDate = options.isInvalidDate;
273
+
274
+ if (typeof options.isCustomDate === 'function')
275
+ this.isCustomDate = options.isCustomDate;
276
+
277
+ if (typeof options.alwaysShowCalendars === 'boolean')
278
+ this.alwaysShowCalendars = options.alwaysShowCalendars;
279
+
280
+ // update day names order to firstDay
281
+ if (this.locale.firstDay != 0) {
282
+ var iterator = this.locale.firstDay;
283
+ while (iterator > 0) {
284
+ this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift());
285
+ iterator--;
286
+ }
287
+ }
288
+
289
+ var start, end, range;
290
+
291
+ //if no start/end dates set, check if an input element contains initial values
292
+ if (typeof options.startDate === 'undefined' && typeof options.endDate === 'undefined') {
293
+ if ($(this.element).is(':text')) {
294
+ var val = $(this.element).val(),
295
+ split = val.split(this.locale.separator);
296
+
297
+ start = end = null;
298
+
299
+ if (split.length == 2) {
300
+ start = moment(split[0], this.locale.format);
301
+ end = moment(split[1], this.locale.format);
302
+ } else if (this.singleDatePicker && val !== "") {
303
+ start = moment(val, this.locale.format);
304
+ end = moment(val, this.locale.format);
305
+ }
306
+ if (start !== null && end !== null) {
307
+ this.setStartDate(start);
308
+ this.setEndDate(end);
309
+ }
310
+ }
311
+ }
312
+
313
+ if (typeof options.ranges === 'object') {
314
+ for (range in options.ranges) {
315
+
316
+ if (typeof options.ranges[range][0] === 'string')
317
+ start = moment(options.ranges[range][0], this.locale.format);
318
+ else
319
+ start = moment(options.ranges[range][0]);
320
+
321
+ if (typeof options.ranges[range][1] === 'string')
322
+ end = moment(options.ranges[range][1], this.locale.format);
323
+ else
324
+ end = moment(options.ranges[range][1]);
325
+
326
+ // If the start or end date exceed those allowed by the minDate or maxSpan
327
+ // options, shorten the range to the allowable period.
328
+ if (this.minDate && start.isBefore(this.minDate))
329
+ start = this.minDate.clone();
330
+
331
+ var maxDate = this.maxDate;
332
+ if (this.maxSpan && maxDate && start.clone().add(this.maxSpan).isAfter(maxDate))
333
+ maxDate = start.clone().add(this.maxSpan);
334
+ if (maxDate && end.isAfter(maxDate))
335
+ end = maxDate.clone();
336
+
337
+ // If the end of the range is before the minimum or the start of the range is
338
+ // after the maximum, don't display this range option at all.
339
+ if ((this.minDate && end.isBefore(this.minDate, this.timepicker ? 'minute' : 'day'))
340
+ || (maxDate && start.isAfter(maxDate, this.timepicker ? 'minute' : 'day')))
341
+ continue;
342
+
343
+ //Support unicode chars in the range names.
344
+ var elem = document.createElement('textarea');
345
+ elem.innerHTML = range;
346
+ var rangeHtml = elem.value;
347
+
348
+ this.ranges[rangeHtml] = [start, end];
349
+ }
350
+
351
+ var list = '<ul>';
352
+ for (range in this.ranges) {
353
+ list += '<li data-range-key="' + range + '">' + range + '</li>';
354
+ }
355
+ if (this.showCustomRangeLabel) {
356
+ list += '<li data-range-key="' + this.locale.customRangeLabel + '">' + this.locale.customRangeLabel + '</li>';
357
+ }
358
+ list += '</ul>';
359
+ this.container.find('.ranges').prepend(list);
360
+ }
361
+
362
+ if (typeof cb === 'function') {
363
+ this.callback = cb;
364
+ }
365
+
366
+ if (!this.timePicker) {
367
+ this.startDate = this.startDate.startOf('day');
368
+ this.endDate = this.endDate.endOf('day');
369
+ this.container.find('.calendar-time').hide();
370
+ }
371
+
372
+ //can't be used together for now
373
+ if (this.timePicker && this.autoApply)
374
+ this.autoApply = false;
375
+
376
+ if (this.autoApply) {
377
+ this.container.addClass('auto-apply');
378
+ }
379
+
380
+ if (typeof options.ranges === 'object')
381
+ this.container.addClass('show-ranges');
382
+
383
+ if (this.singleDatePicker) {
384
+ this.container.addClass('single');
385
+ this.container.find('.drp-calendar.left').addClass('single');
386
+ this.container.find('.drp-calendar.left').show();
387
+ this.container.find('.drp-calendar.right').hide();
388
+ if (!this.timePicker) {
389
+ this.container.addClass('auto-apply');
390
+ }
391
+ }
392
+
393
+ if ((typeof options.ranges === 'undefined' && !this.singleDatePicker) || this.alwaysShowCalendars) {
394
+ this.container.addClass('show-calendar');
395
+ }
396
+
397
+ this.container.addClass('opens' + this.opens);
398
+
399
+ //apply CSS classes and labels to buttons
400
+ this.container.find('.applyBtn, .cancelBtn').addClass(this.buttonClasses);
401
+ if (this.applyButtonClasses.length)
402
+ this.container.find('.applyBtn').addClass(this.applyButtonClasses);
403
+ if (this.cancelButtonClasses.length)
404
+ this.container.find('.cancelBtn').addClass(this.cancelButtonClasses);
405
+ this.container.find('.applyBtn').html(this.locale.applyLabel);
406
+ this.container.find('.cancelBtn').html(this.locale.cancelLabel);
407
+
408
+ //
409
+ // event listeners
410
+ //
411
+
412
+ this.container.find('.drp-calendar')
413
+ .on('click.daterangepicker', '.prev', $.proxy(this.clickPrev, this))
414
+ .on('click.daterangepicker', '.next', $.proxy(this.clickNext, this))
415
+ .on('mousedown.daterangepicker', 'td.available', $.proxy(this.clickDate, this))
416
+ .on('change.daterangepicker', 'select.yearselect', $.proxy(this.monthOrYearChanged, this))
417
+ .on('change.daterangepicker', 'select.monthselect', $.proxy(this.monthOrYearChanged, this))
418
+ .on('change.daterangepicker', 'select.hourselect,select.minuteselect,select.secondselect,select.ampmselect', $.proxy(this.timeChanged, this))
419
+
420
+ this.container.find('.ranges')
421
+ .on('click.daterangepicker', 'li', $.proxy(this.clickRange, this))
422
+
423
+ this.container.find('.drp-buttons')
424
+ .on('click.daterangepicker', 'button.applyBtn', $.proxy(this.clickApply, this))
425
+ .on('click.daterangepicker', 'button.cancelBtn', $.proxy(this.clickCancel, this))
426
+
427
+ if (this.element.is('input') || this.element.is('button')) {
428
+ this.element.on({
429
+ 'click.daterangepicker': $.proxy(this.show, this),
430
+ 'focus.daterangepicker': $.proxy(this.show, this),
431
+ 'keyup.daterangepicker': $.proxy(this.elementChanged, this),
432
+ 'keydown.daterangepicker': $.proxy(this.keydown, this) //IE 11 compatibility
433
+ });
434
+ } else {
435
+ this.element.on('click.daterangepicker', $.proxy(this.toggle, this));
436
+ this.element.on('keydown.daterangepicker', $.proxy(this.toggle, this));
437
+ }
438
+
439
+ //
440
+ // if attached to a text input, set the initial value
441
+ //
442
+
443
+ this.updateElement();
444
+
445
+ };
446
+
447
+ DateRangePicker.prototype = {
448
+
449
+ constructor: DateRangePicker,
450
+
451
+ setStartDate: function(startDate) {
452
+ if (typeof startDate === 'string')
453
+ this.startDate = moment(startDate, this.locale.format);
454
+
455
+ if (typeof startDate === 'object')
456
+ this.startDate = moment(startDate);
457
+
458
+ if (!this.timePicker)
459
+ this.startDate = this.startDate.startOf('day');
460
+
461
+ if (this.timePicker && this.timePickerIncrement)
462
+ this.startDate.minute(Math.round(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);
463
+
464
+ if (this.minDate && this.startDate.isBefore(this.minDate)) {
465
+ this.startDate = this.minDate.clone();
466
+ if (this.timePicker && this.timePickerIncrement)
467
+ this.startDate.minute(Math.round(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);
468
+ }
469
+
470
+ if (this.maxDate && this.startDate.isAfter(this.maxDate)) {
471
+ this.startDate = this.maxDate.clone();
472
+ if (this.timePicker && this.timePickerIncrement)
473
+ this.startDate.minute(Math.floor(this.startDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);
474
+ }
475
+
476
+ if (!this.isShowing)
477
+ this.updateElement();
478
+
479
+ this.updateMonthsInView();
480
+ },
481
+
482
+ setEndDate: function(endDate) {
483
+ if (typeof endDate === 'string')
484
+ this.endDate = moment(endDate, this.locale.format);
485
+
486
+ if (typeof endDate === 'object')
487
+ this.endDate = moment(endDate);
488
+
489
+ if (!this.timePicker)
490
+ this.endDate = this.endDate.add(1,'d').startOf('day').subtract(1,'second');
491
+
492
+ if (this.timePicker && this.timePickerIncrement)
493
+ this.endDate.minute(Math.round(this.endDate.minute() / this.timePickerIncrement) * this.timePickerIncrement);
494
+
495
+ if (this.endDate.isBefore(this.startDate))
496
+ this.endDate = this.startDate.clone();
497
+
498
+ if (this.maxDate && this.endDate.isAfter(this.maxDate))
499
+ this.endDate = this.maxDate.clone();
500
+
501
+ if (this.maxSpan && this.startDate.clone().add(this.maxSpan).isBefore(this.endDate))
502
+ this.endDate = this.startDate.clone().add(this.maxSpan);
503
+
504
+ this.previousRightTime = this.endDate.clone();
505
+
506
+ this.container.find('.drp-selected').html(this.startDate.format(this.locale.format) + this.locale.separator + this.endDate.format(this.locale.format));
507
+
508
+ if (!this.isShowing)
509
+ this.updateElement();
510
+
511
+ this.updateMonthsInView();
512
+ },
513
+
514
+ isInvalidDate: function() {
515
+ return false;
516
+ },
517
+
518
+ isCustomDate: function() {
519
+ return false;
520
+ },
521
+
522
+ updateView: function() {
523
+ if (this.timePicker) {
524
+ this.renderTimePicker('left');
525
+ this.renderTimePicker('right');
526
+ if (!this.endDate) {
527
+ this.container.find('.right .calendar-time select').attr('disabled', 'disabled').addClass('disabled');
528
+ } else {
529
+ this.container.find('.right .calendar-time select').removeAttr('disabled').removeClass('disabled');
530
+ }
531
+ }
532
+ if (this.endDate)
533
+ this.container.find('.drp-selected').html(this.startDate.format(this.locale.format) + this.locale.separator + this.endDate.format(this.locale.format));
534
+ this.updateMonthsInView();
535
+ this.updateCalendars();
536
+ this.updateFormInputs();
537
+ },
538
+
539
+ updateMonthsInView: function() {
540
+ if (this.endDate) {
541
+
542
+ //if both dates are visible already, do nothing
543
+ if (!this.singleDatePicker && this.leftCalendar.month && this.rightCalendar.month &&
544
+ (this.startDate.format('YYYY-MM') == this.leftCalendar.month.format('YYYY-MM') || this.startDate.format('YYYY-MM') == this.rightCalendar.month.format('YYYY-MM'))
545
+ &&
546
+ (this.endDate.format('YYYY-MM') == this.leftCalendar.month.format('YYYY-MM') || this.endDate.format('YYYY-MM') == this.rightCalendar.month.format('YYYY-MM'))
547
+ ) {
548
+ return;
549
+ }
550
+
551
+ this.leftCalendar.month = this.startDate.clone().date(2);
552
+ if (!this.linkedCalendars && (this.endDate.month() != this.startDate.month() || this.endDate.year() != this.startDate.year())) {
553
+ this.rightCalendar.month = this.endDate.clone().date(2);
554
+ } else {
555
+ this.rightCalendar.month = this.startDate.clone().date(2).add(1, 'month');
556
+ }
557
+
558
+ } else {
559
+ if (this.leftCalendar.month.format('YYYY-MM') != this.startDate.format('YYYY-MM') && this.rightCalendar.month.format('YYYY-MM') != this.startDate.format('YYYY-MM')) {
560
+ this.leftCalendar.month = this.startDate.clone().date(2);
561
+ this.rightCalendar.month = this.startDate.clone().date(2).add(1, 'month');
562
+ }
563
+ }
564
+ if (this.maxDate && this.linkedCalendars && !this.singleDatePicker && this.rightCalendar.month > this.maxDate) {
565
+ this.rightCalendar.month = this.maxDate.clone().date(2);
566
+ this.leftCalendar.month = this.maxDate.clone().date(2).subtract(1, 'month');
567
+ }
568
+ },
569
+
570
+ updateCalendars: function() {
571
+
572
+ if (this.timePicker) {
573
+ var hour, minute, second;
574
+ if (this.endDate) {
575
+ hour = parseInt(this.container.find('.left .hourselect').val(), 10);
576
+ minute = parseInt(this.container.find('.left .minuteselect').val(), 10);
577
+ second = this.timePickerSeconds ? parseInt(this.container.find('.left .secondselect').val(), 10) : 0;
578
+ if (!this.timePicker24Hour) {
579
+ var ampm = this.container.find('.left .ampmselect').val();
580
+ if (ampm === 'PM' && hour < 12)
581
+ hour += 12;
582
+ if (ampm === 'AM' && hour === 12)
583
+ hour = 0;
584
+ }
585
+ } else {
586
+ hour = parseInt(this.container.find('.right .hourselect').val(), 10);
587
+ minute = parseInt(this.container.find('.right .minuteselect').val(), 10);
588
+ second = this.timePickerSeconds ? parseInt(this.container.find('.right .secondselect').val(), 10) : 0;
589
+ if (!this.timePicker24Hour) {
590
+ var ampm = this.container.find('.right .ampmselect').val();
591
+ if (ampm === 'PM' && hour < 12)
592
+ hour += 12;
593
+ if (ampm === 'AM' && hour === 12)
594
+ hour = 0;
595
+ }
596
+ }
597
+ this.leftCalendar.month.hour(hour).minute(minute).second(second);
598
+ this.rightCalendar.month.hour(hour).minute(minute).second(second);
599
+ }
600
+
601
+ this.renderCalendar('left');
602
+ this.renderCalendar('right');
603
+
604
+ //highlight any predefined range matching the current start and end dates
605
+ this.container.find('.ranges li').removeClass('active');
606
+ if (this.endDate == null) return;
607
+
608
+ this.calculateChosenLabel();
609
+ },
610
+
611
+ renderCalendar: function(side) {
612
+
613
+ //
614
+ // Build the matrix of dates that will populate the calendar
615
+ //
616
+
617
+ var calendar = side == 'left' ? this.leftCalendar : this.rightCalendar;
618
+ var month = calendar.month.month();
619
+ var year = calendar.month.year();
620
+ var hour = calendar.month.hour();
621
+ var minute = calendar.month.minute();
622
+ var second = calendar.month.second();
623
+ var daysInMonth = moment([year, month]).daysInMonth();
624
+ var firstDay = moment([year, month, 1]);
625
+ var lastDay = moment([year, month, daysInMonth]);
626
+ var lastMonth = moment(firstDay).subtract(1, 'month').month();
627
+ var lastYear = moment(firstDay).subtract(1, 'month').year();
628
+ var daysInLastMonth = moment([lastYear, lastMonth]).daysInMonth();
629
+ var dayOfWeek = firstDay.day();
630
+
631
+ //initialize a 6 rows x 7 columns array for the calendar
632
+ var calendar = [];
633
+ calendar.firstDay = firstDay;
634
+ calendar.lastDay = lastDay;
635
+
636
+ for (var i = 0; i < 6; i++) {
637
+ calendar[i] = [];
638
+ }
639
+
640
+ //populate the calendar with date objects
641
+ var startDay = daysInLastMonth - dayOfWeek + this.locale.firstDay + 1;
642
+ if (startDay > daysInLastMonth)
643
+ startDay -= 7;
644
+
645
+ if (dayOfWeek == this.locale.firstDay)
646
+ startDay = daysInLastMonth - 6;
647
+
648
+ var curDate = moment([lastYear, lastMonth, startDay, 12, minute, second]);
649
+
650
+ var col, row;
651
+ for (var i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add(24, 'hour')) {
652
+ if (i > 0 && col % 7 === 0) {
653
+ col = 0;
654
+ row++;
655
+ }
656
+ calendar[row][col] = curDate.clone().hour(hour).minute(minute).second(second);
657
+ curDate.hour(12);
658
+
659
+ if (this.minDate && calendar[row][col].format('YYYY-MM-DD') == this.minDate.format('YYYY-MM-DD') && calendar[row][col].isBefore(this.minDate) && side == 'left') {
660
+ calendar[row][col] = this.minDate.clone();
661
+ }
662
+
663
+ if (this.maxDate && calendar[row][col].format('YYYY-MM-DD') == this.maxDate.format('YYYY-MM-DD') && calendar[row][col].isAfter(this.maxDate) && side == 'right') {
664
+ calendar[row][col] = this.maxDate.clone();
665
+ }
666
+
667
+ }
668
+
669
+ //make the calendar object available to hoverDate/clickDate
670
+ if (side == 'left') {
671
+ this.leftCalendar.calendar = calendar;
672
+ } else {
673
+ this.rightCalendar.calendar = calendar;
674
+ }
675
+
676
+ //
677
+ // Display the calendar
678
+ //
679
+
680
+ var minDate = side == 'left' ? this.minDate : this.startDate;
681
+ var maxDate = this.maxDate;
682
+ var selected = side == 'left' ? this.startDate : this.endDate;
683
+ var arrow = this.locale.direction == 'ltr' ? {left: 'chevron-left', right: 'chevron-right'} : {left: 'chevron-right', right: 'chevron-left'};
684
+
685
+ var html = '<table class="table-condensed">';
686
+ html += '<thead>';
687
+ html += '<tr>';
688
+
689
+ // add empty cell for week number
690
+ if (this.showWeekNumbers || this.showISOWeekNumbers)
691
+ html += '<th></th>';
692
+
693
+ if ((!minDate || minDate.isBefore(calendar.firstDay)) && (!this.linkedCalendars || side == 'left')) {
694
+ html += '<th class="prev available"><span></span></th>';
695
+ } else {
696
+ html += '<th></th>';
697
+ }
698
+
699
+ var dateHtml = this.locale.monthNames[calendar[1][1].month()] + calendar[1][1].format(" YYYY");
700
+
701
+ if (this.showDropdowns) {
702
+ var currentMonth = calendar[1][1].month();
703
+ var currentYear = calendar[1][1].year();
704
+ var maxYear = (maxDate && maxDate.year()) || (this.maxYear);
705
+ var minYear = (minDate && minDate.year()) || (this.minYear);
706
+ var inMinYear = currentYear == minYear;
707
+ var inMaxYear = currentYear == maxYear;
708
+
709
+ var monthHtml = '<select class="monthselect">';
710
+ for (var m = 0; m < 12; m++) {
711
+ if ((!inMinYear || m >= minDate.month()) && (!inMaxYear || m <= maxDate.month())) {
712
+ monthHtml += "<option value='" + m + "'" +
713
+ (m === currentMonth ? " selected='selected'" : "") +
714
+ ">" + this.locale.monthNames[m] + "</option>";
715
+ } else {
716
+ monthHtml += "<option value='" + m + "'" +
717
+ (m === currentMonth ? " selected='selected'" : "") +
718
+ " disabled='disabled'>" + this.locale.monthNames[m] + "</option>";
719
+ }
720
+ }
721
+ monthHtml += "</select>";
722
+
723
+ var yearHtml = '<select class="yearselect">';
724
+ for (var y = minYear; y <= maxYear; y++) {
725
+ yearHtml += '<option value="' + y + '"' +
726
+ (y === currentYear ? ' selected="selected"' : '') +
727
+ '>' + y + '</option>';
728
+ }
729
+ yearHtml += '</select>';
730
+
731
+ dateHtml = monthHtml + yearHtml;
732
+ }
733
+
734
+ html += '<th colspan="5" class="month">' + dateHtml + '</th>';
735
+ if ((!maxDate || maxDate.isAfter(calendar.lastDay)) && (!this.linkedCalendars || side == 'right' || this.singleDatePicker)) {
736
+ html += '<th class="next available"><span></span></th>';
737
+ } else {
738
+ html += '<th></th>';
739
+ }
740
+
741
+ html += '</tr>';
742
+ html += '<tr>';
743
+
744
+ // add week number label
745
+ if (this.showWeekNumbers || this.showISOWeekNumbers)
746
+ html += '<th class="week">' + this.locale.weekLabel + '</th>';
747
+
748
+ $.each(this.locale.daysOfWeek, function(index, dayOfWeek) {
749
+ html += '<th>' + dayOfWeek + '</th>';
750
+ });
751
+
752
+ html += '</tr>';
753
+ html += '</thead>';
754
+ html += '<tbody>';
755
+
756
+ //adjust maxDate to reflect the maxSpan setting in order to
757
+ //grey out end dates beyond the maxSpan
758
+ if (this.endDate == null && this.maxSpan) {
759
+ var maxLimit = this.startDate.clone().add(this.maxSpan).endOf('day');
760
+ if (!maxDate || maxLimit.isBefore(maxDate)) {
761
+ maxDate = maxLimit;
762
+ }
763
+ }
764
+
765
+ for (var row = 0; row < 6; row++) {
766
+ html += '<tr>';
767
+
768
+ // add week number
769
+ if (this.showWeekNumbers)
770
+ html += '<td class="week">' + calendar[row][0].week() + '</td>';
771
+ else if (this.showISOWeekNumbers)
772
+ html += '<td class="week">' + calendar[row][0].isoWeek() + '</td>';
773
+
774
+ for (var col = 0; col < 7; col++) {
775
+
776
+ var classes = [];
777
+
778
+ //highlight today's date
779
+ if (calendar[row][col].isSame(new Date(), "day"))
780
+ classes.push('today');
781
+
782
+ //highlight weekends
783
+ if (calendar[row][col].isoWeekday() > 5)
784
+ classes.push('weekend');
785
+
786
+ //grey out the dates in other months displayed at beginning and end of this calendar
787
+ if (calendar[row][col].month() != calendar[1][1].month())
788
+ classes.push('off');
789
+
790
+ //don't allow selection of dates before the minimum date
791
+ if (this.minDate && calendar[row][col].isBefore(this.minDate, 'day'))
792
+ classes.push('off', 'disabled');
793
+
794
+ //don't allow selection of dates after the maximum date
795
+ if (maxDate && calendar[row][col].isAfter(maxDate, 'day'))
796
+ classes.push('off', 'disabled');
797
+
798
+ //don't allow selection of date if a custom function decides it's invalid
799
+ if (this.isInvalidDate(calendar[row][col]))
800
+ classes.push('off', 'disabled');
801
+
802
+ //highlight the currently selected start date
803
+ if (calendar[row][col].format('YYYY-MM-DD') == this.startDate.format('YYYY-MM-DD'))
804
+ classes.push('active', 'start-date');
805
+
806
+ //highlight the currently selected end date
807
+ if (this.endDate != null && calendar[row][col].format('YYYY-MM-DD') == this.endDate.format('YYYY-MM-DD'))
808
+ classes.push('active', 'end-date');
809
+
810
+ //highlight dates in-between the selected dates
811
+ if (this.endDate != null && calendar[row][col] > this.startDate && calendar[row][col] < this.endDate)
812
+ classes.push('in-range');
813
+
814
+ //apply custom classes for this date
815
+ var isCustom = this.isCustomDate(calendar[row][col]);
816
+ if (isCustom !== false) {
817
+ if (typeof isCustom === 'string')
818
+ classes.push(isCustom);
819
+ else
820
+ Array.prototype.push.apply(classes, isCustom);
821
+ }
822
+
823
+ var cname = '', disabled = false;
824
+ for (var i = 0; i < classes.length; i++) {
825
+ cname += classes[i] + ' ';
826
+ if (classes[i] == 'disabled')
827
+ disabled = true;
828
+ }
829
+ if (!disabled)
830
+ cname += 'available';
831
+
832
+ html += '<td class="' + cname.replace(/^\s+|\s+$/g, '') + '" data-title="' + 'r' + row + 'c' + col + '">' + calendar[row][col].date() + '</td>';
833
+
834
+ }
835
+ html += '</tr>';
836
+ }
837
+
838
+ html += '</tbody>';
839
+ html += '</table>';
840
+
841
+ this.container.find('.drp-calendar.' + side + ' .calendar-table').html(html);
842
+
843
+ },
844
+
845
+ renderTimePicker: function(side) {
846
+
847
+ // Don't bother updating the time picker if it's currently disabled
848
+ // because an end date hasn't been clicked yet
849
+ if (side == 'right' && !this.endDate) return;
850
+
851
+ var html, selected, minDate, maxDate = this.maxDate;
852
+
853
+ if (this.maxSpan && (!this.maxDate || this.startDate.clone().add(this.maxSpan).isAfter(this.maxDate)))
854
+ maxDate = this.startDate.clone().add(this.maxSpan);
855
+
856
+ if (side == 'left') {
857
+ selected = this.startDate.clone();
858
+ minDate = this.minDate;
859
+ } else if (side == 'right') {
860
+ selected = this.endDate.clone();
861
+ minDate = this.startDate;
862
+
863
+ //Preserve the time already selected
864
+ var timeSelector = this.container.find('.drp-calendar.right .calendar-time');
865
+ if (timeSelector.html() != '') {
866
+
867
+ selected.hour(selected.hour() || timeSelector.find('.hourselect option:selected').val());
868
+ selected.minute(selected.minute() || timeSelector.find('.minuteselect option:selected').val());
869
+ selected.second(selected.second() || timeSelector.find('.secondselect option:selected').val());
870
+
871
+ if (!this.timePicker24Hour) {
872
+ var ampm = timeSelector.find('.ampmselect option:selected').val();
873
+ if (ampm === 'PM' && selected.hour() < 12)
874
+ selected.hour(selected.hour() + 12);
875
+ if (ampm === 'AM' && selected.hour() === 12)
876
+ selected.hour(0);
877
+ }
878
+
879
+ }
880
+
881
+ if (selected.isBefore(this.startDate))
882
+ selected = this.startDate.clone();
883
+
884
+ if (maxDate && selected.isAfter(maxDate))
885
+ selected = maxDate.clone();
886
+
887
+ }
888
+
889
+ //
890
+ // hours
891
+ //
892
+
893
+ html = '<select class="hourselect">';
894
+
895
+ var start = this.timePicker24Hour ? 0 : 1;
896
+ var end = this.timePicker24Hour ? 23 : 12;
897
+
898
+ for (var i = start; i <= end; i++) {
899
+ var i_in_24 = i;
900
+ if (!this.timePicker24Hour)
901
+ i_in_24 = selected.hour() >= 12 ? (i == 12 ? 12 : i + 12) : (i == 12 ? 0 : i);
902
+
903
+ var time = selected.clone().hour(i_in_24);
904
+ var disabled = false;
905
+ if (minDate && time.minute(59).isBefore(minDate))
906
+ disabled = true;
907
+ if (maxDate && time.minute(0).isAfter(maxDate))
908
+ disabled = true;
909
+
910
+ if (i_in_24 == selected.hour() && !disabled) {
911
+ html += '<option value="' + i + '" selected="selected">' + i + '</option>';
912
+ } else if (disabled) {
913
+ html += '<option value="' + i + '" disabled="disabled" class="disabled">' + i + '</option>';
914
+ } else {
915
+ html += '<option value="' + i + '">' + i + '</option>';
916
+ }
917
+ }
918
+
919
+ html += '</select> ';
920
+
921
+ //
922
+ // minutes
923
+ //
924
+
925
+ html += ': <select class="minuteselect">';
926
+
927
+ for (var i = 0; i < 60; i += this.timePickerIncrement) {
928
+ var padded = i < 10 ? '0' + i : i;
929
+ var time = selected.clone().minute(i);
930
+
931
+ var disabled = false;
932
+ if (minDate && time.second(59).isBefore(minDate))
933
+ disabled = true;
934
+ if (maxDate && time.second(0).isAfter(maxDate))
935
+ disabled = true;
936
+
937
+ if (selected.minute() == i && !disabled) {
938
+ html += '<option value="' + i + '" selected="selected">' + padded + '</option>';
939
+ } else if (disabled) {
940
+ html += '<option value="' + i + '" disabled="disabled" class="disabled">' + padded + '</option>';
941
+ } else {
942
+ html += '<option value="' + i + '">' + padded + '</option>';
943
+ }
944
+ }
945
+
946
+ html += '</select> ';
947
+
948
+ //
949
+ // seconds
950
+ //
951
+
952
+ if (this.timePickerSeconds) {
953
+ html += ': <select class="secondselect">';
954
+
955
+ for (var i = 0; i < 60; i++) {
956
+ var padded = i < 10 ? '0' + i : i;
957
+ var time = selected.clone().second(i);
958
+
959
+ var disabled = false;
960
+ if (minDate && time.isBefore(minDate))
961
+ disabled = true;
962
+ if (maxDate && time.isAfter(maxDate))
963
+ disabled = true;
964
+
965
+ if (selected.second() == i && !disabled) {
966
+ html += '<option value="' + i + '" selected="selected">' + padded + '</option>';
967
+ } else if (disabled) {
968
+ html += '<option value="' + i + '" disabled="disabled" class="disabled">' + padded + '</option>';
969
+ } else {
970
+ html += '<option value="' + i + '">' + padded + '</option>';
971
+ }
972
+ }
973
+
974
+ html += '</select> ';
975
+ }
976
+
977
+ //
978
+ // AM/PM
979
+ //
980
+
981
+ if (!this.timePicker24Hour) {
982
+ html += '<select class="ampmselect">';
983
+
984
+ var am_html = '';
985
+ var pm_html = '';
986
+
987
+ if (minDate && selected.clone().hour(12).minute(0).second(0).isBefore(minDate))
988
+ am_html = ' disabled="disabled" class="disabled"';
989
+
990
+ if (maxDate && selected.clone().hour(0).minute(0).second(0).isAfter(maxDate))
991
+ pm_html = ' disabled="disabled" class="disabled"';
992
+
993
+ if (selected.hour() >= 12) {
994
+ html += '<option value="AM"' + am_html + '>AM</option><option value="PM" selected="selected"' + pm_html + '>PM</option>';
995
+ } else {
996
+ html += '<option value="AM" selected="selected"' + am_html + '>AM</option><option value="PM"' + pm_html + '>PM</option>';
997
+ }
998
+
999
+ html += '</select>';
1000
+ }
1001
+
1002
+ this.container.find('.drp-calendar.' + side + ' .calendar-time').html(html);
1003
+
1004
+ },
1005
+
1006
+ updateFormInputs: function() {
1007
+
1008
+ if (this.singleDatePicker || (this.endDate && (this.startDate.isBefore(this.endDate) || this.startDate.isSame(this.endDate)))) {
1009
+ this.container.find('button.applyBtn').removeAttr('disabled');
1010
+ } else {
1011
+ this.container.find('button.applyBtn').attr('disabled', 'disabled');
1012
+ }
1013
+
1014
+ },
1015
+
1016
+ move: function() {
1017
+ var parentOffset = { top: 0, left: 0 },
1018
+ containerTop;
1019
+ var parentRightEdge = $(window).width();
1020
+ if (!this.parentEl.is('body')) {
1021
+ parentOffset = {
1022
+ top: this.parentEl.offset().top - this.parentEl.scrollTop(),
1023
+ left: this.parentEl.offset().left - this.parentEl.scrollLeft()
1024
+ };
1025
+ parentRightEdge = this.parentEl[0].clientWidth + this.parentEl.offset().left;
1026
+ }
1027
+
1028
+ if (this.drops == 'up')
1029
+ containerTop = this.element.offset().top - this.container.outerHeight() - parentOffset.top;
1030
+ else
1031
+ containerTop = this.element.offset().top + this.element.outerHeight() - parentOffset.top;
1032
+ this.container[this.drops == 'up' ? 'addClass' : 'removeClass']('drop-up');
1033
+
1034
+ if (this.opens == 'left') {
1035
+ this.container.css({
1036
+ top: containerTop,
1037
+ right: parentRightEdge - this.element.offset().left - this.element.outerWidth(),
1038
+ left: 'auto'
1039
+ });
1040
+ if (this.container.offset().left < 0) {
1041
+ this.container.css({
1042
+ right: 'auto',
1043
+ left: 9
1044
+ });
1045
+ }
1046
+ } else if (this.opens == 'center') {
1047
+ this.container.css({
1048
+ top: containerTop,
1049
+ left: this.element.offset().left - parentOffset.left + this.element.outerWidth() / 2
1050
+ - this.container.outerWidth() / 2,
1051
+ right: 'auto'
1052
+ });
1053
+ if (this.container.offset().left < 0) {
1054
+ this.container.css({
1055
+ right: 'auto',
1056
+ left: 9
1057
+ });
1058
+ }
1059
+ } else {
1060
+ this.container.css({
1061
+ top: containerTop,
1062
+ left: this.element.offset().left - parentOffset.left,
1063
+ right: 'auto'
1064
+ });
1065
+ if (this.container.offset().left + this.container.outerWidth() > $(window).width()) {
1066
+ this.container.css({
1067
+ left: 'auto',
1068
+ right: 0
1069
+ });
1070
+ }
1071
+ }
1072
+ },
1073
+
1074
+ show: function(e) {
1075
+ if (this.isShowing) return;
1076
+
1077
+ // Create a click proxy that is private to this instance of datepicker, for unbinding
1078
+ this._outsideClickProxy = $.proxy(function(e) { this.outsideClick(e); }, this);
1079
+
1080
+ // Bind global datepicker mousedown for hiding and
1081
+ $(document)
1082
+ .on('mousedown.daterangepicker', this._outsideClickProxy)
1083
+ // also support mobile devices
1084
+ .on('touchend.daterangepicker', this._outsideClickProxy)
1085
+ // also explicitly play nice with Bootstrap dropdowns, which stopPropagation when clicking them
1086
+ .on('click.daterangepicker', '[data-toggle=dropdown]', this._outsideClickProxy)
1087
+ // and also close when focus changes to outside the picker (eg. tabbing between controls)
1088
+ .on('focusin.daterangepicker', this._outsideClickProxy);
1089
+
1090
+ // Reposition the picker if the window is resized while it's open
1091
+ $(window).on('resize.daterangepicker', $.proxy(function(e) { this.move(e); }, this));
1092
+
1093
+ this.oldStartDate = this.startDate.clone();
1094
+ this.oldEndDate = this.endDate.clone();
1095
+ this.previousRightTime = this.endDate.clone();
1096
+
1097
+ this.updateView();
1098
+ this.container.show();
1099
+ this.move();
1100
+ this.element.trigger('show.daterangepicker', this);
1101
+ this.isShowing = true;
1102
+ },
1103
+
1104
+ hide: function(e) {
1105
+ if (!this.isShowing) return;
1106
+
1107
+ //incomplete date selection, revert to last values
1108
+ if (!this.endDate) {
1109
+ this.startDate = this.oldStartDate.clone();
1110
+ this.endDate = this.oldEndDate.clone();
1111
+ }
1112
+
1113
+ //if a new date range was selected, invoke the user callback function
1114
+ if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate))
1115
+ this.callback(this.startDate.clone(), this.endDate.clone(), this.chosenLabel);
1116
+
1117
+ //if picker is attached to a text input, update it
1118
+ this.updateElement();
1119
+
1120
+ $(document).off('.daterangepicker');
1121
+ $(window).off('.daterangepicker');
1122
+ this.container.hide();
1123
+ this.element.trigger('hide.daterangepicker', this);
1124
+ this.isShowing = false;
1125
+ },
1126
+
1127
+ toggle: function(e) {
1128
+ if (this.isShowing) {
1129
+ this.hide();
1130
+ } else {
1131
+ this.show();
1132
+ }
1133
+ },
1134
+
1135
+ outsideClick: function(e) {
1136
+ var target = $(e.target);
1137
+ // if the page is clicked anywhere except within the daterangerpicker/button
1138
+ // itself then call this.hide()
1139
+ if (
1140
+ // ie modal dialog fix
1141
+ e.type == "focusin" ||
1142
+ target.closest(this.element).length ||
1143
+ target.closest(this.container).length ||
1144
+ target.closest('.calendar-table').length
1145
+ ) return;
1146
+ this.hide();
1147
+ this.element.trigger('outsideClick.daterangepicker', this);
1148
+ },
1149
+
1150
+ showCalendars: function() {
1151
+ this.container.addClass('show-calendar');
1152
+ this.move();
1153
+ this.element.trigger('showCalendar.daterangepicker', this);
1154
+ },
1155
+
1156
+ hideCalendars: function() {
1157
+ this.container.removeClass('show-calendar');
1158
+ this.element.trigger('hideCalendar.daterangepicker', this);
1159
+ },
1160
+
1161
+ clickRange: function(e) {
1162
+ var label = e.target.getAttribute('data-range-key');
1163
+ this.chosenLabel = label;
1164
+ if (label == this.locale.customRangeLabel) {
1165
+ this.showCalendars();
1166
+ } else {
1167
+ var dates = this.ranges[label];
1168
+ this.startDate = dates[0];
1169
+ this.endDate = dates[1];
1170
+
1171
+ if (!this.timePicker) {
1172
+ this.startDate.startOf('day');
1173
+ this.endDate.endOf('day');
1174
+ }
1175
+
1176
+ if (!this.alwaysShowCalendars)
1177
+ this.hideCalendars();
1178
+ this.clickApply();
1179
+ }
1180
+ },
1181
+
1182
+ clickPrev: function(e) {
1183
+ var cal = $(e.target).parents('.drp-calendar');
1184
+ if (cal.hasClass('left')) {
1185
+ this.leftCalendar.month.subtract(1, 'month');
1186
+ if (this.linkedCalendars)
1187
+ this.rightCalendar.month.subtract(1, 'month');
1188
+ } else {
1189
+ this.rightCalendar.month.subtract(1, 'month');
1190
+ }
1191
+ this.updateCalendars();
1192
+ },
1193
+
1194
+ clickNext: function(e) {
1195
+ var cal = $(e.target).parents('.drp-calendar');
1196
+ if (cal.hasClass('left')) {
1197
+ this.leftCalendar.month.add(1, 'month');
1198
+ } else {
1199
+ this.rightCalendar.month.add(1, 'month');
1200
+ if (this.linkedCalendars)
1201
+ this.leftCalendar.month.add(1, 'month');
1202
+ }
1203
+ this.updateCalendars();
1204
+ },
1205
+
1206
+ hoverDate: function(e) {
1207
+
1208
+ //ignore mouse movements while an above-calendar text input has focus
1209
+ //if (this.container.find('input[name=daterangepicker_start]').is(":focus") || this.container.find('input[name=daterangepicker_end]').is(":focus"))
1210
+ // return;
1211
+
1212
+ //ignore dates that can't be selected
1213
+ if (!$(e.target).hasClass('available')) return;
1214
+
1215
+ //have the text inputs above calendars reflect the date being hovered over
1216
+ var title = $(e.target).attr('data-title');
1217
+ var row = title.substr(1, 1);
1218
+ var col = title.substr(3, 1);
1219
+ var cal = $(e.target).parents('.drp-calendar');
1220
+ var date = cal.hasClass('left') ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col];
1221
+
1222
+ //highlight the dates between the start date and the date being hovered as a potential end date
1223
+ var leftCalendar = this.leftCalendar;
1224
+ var rightCalendar = this.rightCalendar;
1225
+ var startDate = this.startDate;
1226
+ if (!this.endDate) {
1227
+ this.container.find('.drp-calendar tbody td').each(function(index, el) {
1228
+
1229
+ //skip week numbers, only look at dates
1230
+ if ($(el).hasClass('week')) return;
1231
+
1232
+ var title = $(el).attr('data-title');
1233
+ var row = title.substr(1, 1);
1234
+ var col = title.substr(3, 1);
1235
+ var cal = $(el).parents('.drp-calendar');
1236
+ var dt = cal.hasClass('left') ? leftCalendar.calendar[row][col] : rightCalendar.calendar[row][col];
1237
+
1238
+ if ((dt.isAfter(startDate) && dt.isBefore(date)) || dt.isSame(date, 'day')) {
1239
+ $(el).addClass('in-range');
1240
+ } else {
1241
+ $(el).removeClass('in-range');
1242
+ }
1243
+
1244
+ });
1245
+ }
1246
+
1247
+ },
1248
+
1249
+ clickDate: function(e) {
1250
+
1251
+ if (!$(e.target).hasClass('available')) return;
1252
+
1253
+ var title = $(e.target).attr('data-title');
1254
+ var row = title.substr(1, 1);
1255
+ var col = title.substr(3, 1);
1256
+ var cal = $(e.target).parents('.drp-calendar');
1257
+ var date = cal.hasClass('left') ? this.leftCalendar.calendar[row][col] : this.rightCalendar.calendar[row][col];
1258
+
1259
+ //
1260
+ // this function needs to do a few things:
1261
+ // * alternate between selecting a start and end date for the range,
1262
+ // * if the time picker is enabled, apply the hour/minute/second from the select boxes to the clicked date
1263
+ // * if autoapply is enabled, and an end date was chosen, apply the selection
1264
+ // * if single date picker mode, and time picker isn't enabled, apply the selection immediately
1265
+ // * if one of the inputs above the calendars was focused, cancel that manual input
1266
+ //
1267
+
1268
+ if (this.endDate || date.isBefore(this.startDate, 'day')) { //picking start
1269
+ if (this.timePicker) {
1270
+ var hour = parseInt(this.container.find('.left .hourselect').val(), 10);
1271
+ if (!this.timePicker24Hour) {
1272
+ var ampm = this.container.find('.left .ampmselect').val();
1273
+ if (ampm === 'PM' && hour < 12)
1274
+ hour += 12;
1275
+ if (ampm === 'AM' && hour === 12)
1276
+ hour = 0;
1277
+ }
1278
+ var minute = parseInt(this.container.find('.left .minuteselect').val(), 10);
1279
+ var second = this.timePickerSeconds ? parseInt(this.container.find('.left .secondselect').val(), 10) : 0;
1280
+ date = date.clone().hour(hour).minute(minute).second(second);
1281
+ }
1282
+ this.endDate = null;
1283
+ this.setStartDate(date.clone());
1284
+ } else if (!this.endDate && date.isBefore(this.startDate)) {
1285
+ //special case: clicking the same date for start/end,
1286
+ //but the time of the end date is before the start date
1287
+ this.setEndDate(this.startDate.clone());
1288
+ } else { // picking end
1289
+ if (this.timePicker) {
1290
+ var hour = parseInt(this.container.find('.right .hourselect').val(), 10);
1291
+ if (!this.timePicker24Hour) {
1292
+ var ampm = this.container.find('.right .ampmselect').val();
1293
+ if (ampm === 'PM' && hour < 12)
1294
+ hour += 12;
1295
+ if (ampm === 'AM' && hour === 12)
1296
+ hour = 0;
1297
+ }
1298
+ var minute = parseInt(this.container.find('.right .minuteselect').val(), 10);
1299
+ var second = this.timePickerSeconds ? parseInt(this.container.find('.right .secondselect').val(), 10) : 0;
1300
+ date = date.clone().hour(hour).minute(minute).second(second);
1301
+ }
1302
+ this.setEndDate(date.clone());
1303
+ if (this.autoApply) {
1304
+ this.calculateChosenLabel();
1305
+ this.clickApply();
1306
+ }
1307
+ }
1308
+
1309
+ if (this.singleDatePicker) {
1310
+ this.setEndDate(this.startDate);
1311
+ if (!this.timePicker)
1312
+ this.clickApply();
1313
+ }
1314
+
1315
+ this.updateView();
1316
+
1317
+ //This is to cancel the blur event handler if the mouse was in one of the inputs
1318
+ e.stopPropagation();
1319
+
1320
+ },
1321
+
1322
+ calculateChosenLabel: function () {
1323
+ var customRange = true;
1324
+ var i = 0;
1325
+ for (var range in this.ranges) {
1326
+ if (this.timePicker) {
1327
+ var format = this.timePickerSeconds ? "YYYY-MM-DD hh:mm:ss" : "YYYY-MM-DD hh:mm";
1328
+ //ignore times when comparing dates if time picker seconds is not enabled
1329
+ if (this.startDate.format(format) == this.ranges[range][0].format(format) && this.endDate.format(format) == this.ranges[range][1].format(format)) {
1330
+ customRange = false;
1331
+ this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')').addClass('active').attr('data-range-key');
1332
+ break;
1333
+ }
1334
+ } else {
1335
+ //ignore times when comparing dates if time picker is not enabled
1336
+ if (this.startDate.format('YYYY-MM-DD') == this.ranges[range][0].format('YYYY-MM-DD') && this.endDate.format('YYYY-MM-DD') == this.ranges[range][1].format('YYYY-MM-DD')) {
1337
+ customRange = false;
1338
+ this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')').addClass('active').attr('data-range-key');
1339
+ break;
1340
+ }
1341
+ }
1342
+ i++;
1343
+ }
1344
+ if (customRange) {
1345
+ if (this.showCustomRangeLabel) {
1346
+ this.chosenLabel = this.container.find('.ranges li:last').addClass('active').attr('data-range-key');
1347
+ } else {
1348
+ this.chosenLabel = null;
1349
+ }
1350
+ this.showCalendars();
1351
+ }
1352
+ },
1353
+
1354
+ clickApply: function(e) {
1355
+ this.hide();
1356
+ this.element.trigger('apply.daterangepicker', this);
1357
+ },
1358
+
1359
+ clickCancel: function(e) {
1360
+ this.startDate = this.oldStartDate;
1361
+ this.endDate = this.oldEndDate;
1362
+ this.hide();
1363
+ this.element.trigger('cancel.daterangepicker', this);
1364
+ },
1365
+
1366
+ monthOrYearChanged: function(e) {
1367
+ var isLeft = $(e.target).closest('.drp-calendar').hasClass('left'),
1368
+ leftOrRight = isLeft ? 'left' : 'right',
1369
+ cal = this.container.find('.drp-calendar.'+leftOrRight);
1370
+
1371
+ // Month must be Number for new moment versions
1372
+ var month = parseInt(cal.find('.monthselect').val(), 10);
1373
+ var year = cal.find('.yearselect').val();
1374
+
1375
+ if (!isLeft) {
1376
+ if (year < this.startDate.year() || (year == this.startDate.year() && month < this.startDate.month())) {
1377
+ month = this.startDate.month();
1378
+ year = this.startDate.year();
1379
+ }
1380
+ }
1381
+
1382
+ if (this.minDate) {
1383
+ if (year < this.minDate.year() || (year == this.minDate.year() && month < this.minDate.month())) {
1384
+ month = this.minDate.month();
1385
+ year = this.minDate.year();
1386
+ }
1387
+ }
1388
+
1389
+ if (this.maxDate) {
1390
+ if (year > this.maxDate.year() || (year == this.maxDate.year() && month > this.maxDate.month())) {
1391
+ month = this.maxDate.month();
1392
+ year = this.maxDate.year();
1393
+ }
1394
+ }
1395
+
1396
+ if (isLeft) {
1397
+ this.leftCalendar.month.month(month).year(year);
1398
+ if (this.linkedCalendars)
1399
+ this.rightCalendar.month = this.leftCalendar.month.clone().add(1, 'month');
1400
+ } else {
1401
+ this.rightCalendar.month.month(month).year(year);
1402
+ if (this.linkedCalendars)
1403
+ this.leftCalendar.month = this.rightCalendar.month.clone().subtract(1, 'month');
1404
+ }
1405
+ this.updateCalendars();
1406
+ },
1407
+
1408
+ timeChanged: function(e) {
1409
+
1410
+ var cal = $(e.target).closest('.drp-calendar'),
1411
+ isLeft = cal.hasClass('left');
1412
+
1413
+ var hour = parseInt(cal.find('.hourselect').val(), 10);
1414
+ var minute = parseInt(cal.find('.minuteselect').val(), 10);
1415
+ var second = this.timePickerSeconds ? parseInt(cal.find('.secondselect').val(), 10) : 0;
1416
+
1417
+ if (!this.timePicker24Hour) {
1418
+ var ampm = cal.find('.ampmselect').val();
1419
+ if (ampm === 'PM' && hour < 12)
1420
+ hour += 12;
1421
+ if (ampm === 'AM' && hour === 12)
1422
+ hour = 0;
1423
+ }
1424
+
1425
+ if (isLeft) {
1426
+ var start = this.startDate.clone();
1427
+ start.hour(hour);
1428
+ start.minute(minute);
1429
+ start.second(second);
1430
+ this.setStartDate(start);
1431
+ if (this.singleDatePicker) {
1432
+ this.endDate = this.startDate.clone();
1433
+ } else if (this.endDate && this.endDate.format('YYYY-MM-DD') == start.format('YYYY-MM-DD') && this.endDate.isBefore(start)) {
1434
+ this.setEndDate(start.clone());
1435
+ }
1436
+ } else if (this.endDate) {
1437
+ var end = this.endDate.clone();
1438
+ end.hour(hour);
1439
+ end.minute(minute);
1440
+ end.second(second);
1441
+ this.setEndDate(end);
1442
+ }
1443
+
1444
+ //update the calendars so all clickable dates reflect the new time component
1445
+ this.updateCalendars();
1446
+
1447
+ //update the form inputs above the calendars with the new time
1448
+ this.updateFormInputs();
1449
+
1450
+ //re-render the time pickers because changing one selection can affect what's enabled in another
1451
+ this.renderTimePicker('left');
1452
+ this.renderTimePicker('right');
1453
+
1454
+ },
1455
+
1456
+ elementChanged: function() {
1457
+ if (!this.element.is('input')) return;
1458
+ if (!this.element.val().length) return;
1459
+
1460
+ var dateString = this.element.val().split(this.locale.separator),
1461
+ start = null,
1462
+ end = null;
1463
+
1464
+ if (dateString.length === 2) {
1465
+ start = moment(dateString[0], this.locale.format);
1466
+ end = moment(dateString[1], this.locale.format);
1467
+ }
1468
+
1469
+ if (this.singleDatePicker || start === null || end === null) {
1470
+ start = moment(this.element.val(), this.locale.format);
1471
+ end = start;
1472
+ }
1473
+
1474
+ if (!start.isValid() || !end.isValid()) return;
1475
+
1476
+ this.setStartDate(start);
1477
+ this.setEndDate(end);
1478
+ this.updateView();
1479
+ },
1480
+
1481
+ keydown: function(e) {
1482
+ //hide on tab or enter
1483
+ if ((e.keyCode === 9) || (e.keyCode === 13)) {
1484
+ this.hide();
1485
+ }
1486
+
1487
+ //hide on esc and prevent propagation
1488
+ if (e.keyCode === 27) {
1489
+ e.preventDefault();
1490
+ e.stopPropagation();
1491
+
1492
+ this.hide();
1493
+ }
1494
+ },
1495
+
1496
+ updateElement: function() {
1497
+ if (this.element.is('input') && this.autoUpdateInput) {
1498
+ var newValue = this.startDate.format(this.locale.format);
1499
+ if (!this.singleDatePicker) {
1500
+ newValue += this.locale.separator + this.endDate.format(this.locale.format);
1501
+ }
1502
+ if (newValue !== this.element.val()) {
1503
+ this.element.val(newValue).trigger('change');
1504
+ }
1505
+ }
1506
+ },
1507
+
1508
+ remove: function() {
1509
+ this.container.remove();
1510
+ this.element.off('.daterangepicker');
1511
+ this.element.removeData();
1512
+ }
1513
+
1514
+ };
1515
+
1516
+ $.fn.daterangepicker = function(options, callback) {
1517
+ var implementOptions = $.extend(true, {}, $.fn.daterangepicker.defaultOptions, options);
1518
+ this.each(function() {
1519
+ var el = $(this);
1520
+ if (el.data('daterangepicker'))
1521
+ el.data('daterangepicker').remove();
1522
+ el.data('daterangepicker', new DateRangePicker(el, implementOptions, callback));
1523
+ });
1524
+ return this;
1525
+ };
1526
+
1527
+ return DateRangePicker;
1528
+
1529
+ }));