rails_admin 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -7
  3. data/README.md +11 -3
  4. data/app/assets/javascripts/rails_admin/jquery.migrate.js +3 -0
  5. data/app/assets/javascripts/rails_admin/ra.filter-box.js +35 -16
  6. data/app/assets/javascripts/rails_admin/ra.filtering-multiselect.js +7 -7
  7. data/app/assets/javascripts/rails_admin/ra.nested-form-hooks.js +7 -5
  8. data/app/assets/javascripts/rails_admin/ra.widgets.js +11 -3
  9. data/app/assets/javascripts/rails_admin/rails_admin.js +2 -1
  10. data/app/assets/javascripts/rails_admin/ui.js +2 -2
  11. data/app/helpers/rails_admin/form_builder.rb +10 -9
  12. data/app/helpers/rails_admin/main_helper.rb +2 -1
  13. data/app/views/rails_admin/main/_form_enumeration.html.haml +4 -3
  14. data/app/views/rails_admin/main/_form_filtering_multiselect.html.haml +7 -6
  15. data/app/views/rails_admin/main/index.html.haml +2 -2
  16. data/config/locales/rails_admin.en.yml +1 -1
  17. data/lib/rails_admin/adapters/active_record.rb +8 -2
  18. data/lib/rails_admin/adapters/active_record/abstract_object.rb +9 -3
  19. data/lib/rails_admin/config.rb +0 -4
  20. data/lib/rails_admin/config/fields/base.rb +4 -0
  21. data/lib/rails_admin/config/fields/types/active_storage.rb +5 -1
  22. data/lib/rails_admin/config/fields/types/file_upload.rb +5 -1
  23. data/lib/rails_admin/config/fields/types/multiple_active_storage.rb +5 -1
  24. data/lib/rails_admin/config/fields/types/multiple_file_upload.rb +2 -1
  25. data/lib/rails_admin/config/fields/types/shrine.rb +13 -11
  26. data/lib/rails_admin/config/lazy_model.rb +4 -4
  27. data/lib/rails_admin/config/model.rb +2 -2
  28. data/lib/rails_admin/config/proxyable/proxy.rb +4 -4
  29. data/lib/rails_admin/extension.rb +1 -1
  30. data/lib/rails_admin/extensions/paper_trail/auditing_adapter.rb +1 -1
  31. data/lib/rails_admin/version.rb +2 -2
  32. data/lib/tasks/rails_admin.rake +16 -7
  33. data/vendor/assets/javascripts/rails_admin/bootstrap-datetimepicker.js +317 -150
  34. data/vendor/assets/javascripts/rails_admin/bootstrap/bootstrap-affix.js +50 -28
  35. data/vendor/assets/javascripts/rails_admin/bootstrap/bootstrap-alert.js +10 -7
  36. data/vendor/assets/javascripts/rails_admin/bootstrap/bootstrap-button.js +35 -20
  37. data/vendor/assets/javascripts/rails_admin/bootstrap/bootstrap-carousel.js +48 -25
  38. data/vendor/assets/javascripts/rails_admin/bootstrap/bootstrap-collapse.js +70 -28
  39. data/vendor/assets/javascripts/rails_admin/bootstrap/bootstrap-dropdown.js +56 -42
  40. data/vendor/assets/javascripts/rails_admin/bootstrap/bootstrap-modal.js +118 -40
  41. data/vendor/assets/javascripts/rails_admin/bootstrap/bootstrap-popover.js +26 -16
  42. data/vendor/assets/javascripts/rails_admin/bootstrap/bootstrap-scrollspy.js +29 -27
  43. data/vendor/assets/javascripts/rails_admin/bootstrap/bootstrap-tab.js +46 -19
  44. data/vendor/assets/javascripts/rails_admin/bootstrap/bootstrap-tooltip.js +280 -60
  45. data/vendor/assets/javascripts/rails_admin/bootstrap/bootstrap-transition.js +5 -5
  46. data/vendor/assets/javascripts/rails_admin/jquery.pjax.js +317 -160
  47. data/vendor/assets/javascripts/rails_admin/moment-with-locales.js +11210 -9290
  48. metadata +4 -4
  49. data/config/initializers/devise_patch.rb +0 -9
@@ -82,9 +82,6 @@ module RailsAdmin
82
82
  attr_accessor :navigation_static_links
83
83
  attr_accessor :navigation_static_label
84
84
 
85
- # yell about fields that are not marked as accessible
86
- attr_accessor :yell_for_non_accessible_fields
87
-
88
85
  # Setup authentication to be run as a before filter
89
86
  # This is run inside the controller instance so you can setup any authentication you need to
90
87
  #
@@ -271,7 +268,6 @@ module RailsAdmin
271
268
  def reset
272
269
  @compact_show_view = true
273
270
  @browser_validations = true
274
- @yell_for_non_accessible_fields = true
275
271
  @authenticate = nil
276
272
  @authorize = nil
277
273
  @audit = nil
@@ -227,6 +227,10 @@ module RailsAdmin
227
227
  bindings[:view].render partial: "rails_admin/main/#{partial}", locals: {field: self, form: bindings[:form]}
228
228
  end
229
229
 
230
+ register_instance_option :default_filter_operator do
231
+ nil
232
+ end
233
+
230
234
  def editable?
231
235
  !(@properties && @properties.read_only?)
232
236
  end
@@ -8,7 +8,11 @@ module RailsAdmin
8
8
  RailsAdmin::Config::Fields::Types.register(self)
9
9
 
10
10
  register_instance_option :thumb_method do
11
- {resize: '100x100>'}
11
+ if ::ActiveStorage::VERSION::MAJOR >= 6
12
+ {resize_to_limit: [100, 100]}
13
+ else
14
+ {resize: '100x100>'}
15
+ end
12
16
  end
13
17
 
14
18
  register_instance_option :delete_method do
@@ -31,6 +31,10 @@ module RailsAdmin
31
31
  resource_url.to_s
32
32
  end
33
33
 
34
+ register_instance_option :link_name do
35
+ value
36
+ end
37
+
34
38
  register_instance_option :pretty_value do
35
39
  if value.presence
36
40
  v = bindings[:view]
@@ -40,7 +44,7 @@ module RailsAdmin
40
44
  image_html = v.image_tag(thumb_url, class: 'img-thumbnail')
41
45
  url != thumb_url ? v.link_to(image_html, url, target: '_blank', rel: 'noopener noreferrer') : image_html
42
46
  else
43
- v.link_to(value, url, target: '_blank', rel: 'noopener noreferrer')
47
+ v.link_to(link_name, url, target: '_blank', rel: 'noopener noreferrer')
44
48
  end
45
49
  end
46
50
  end
@@ -9,7 +9,11 @@ module RailsAdmin
9
9
 
10
10
  class ActiveStorageAttachment < RailsAdmin::Config::Fields::Types::MultipleFileUpload::AbstractAttachment
11
11
  register_instance_option :thumb_method do
12
- {resize: '100x100>'}
12
+ if ::ActiveStorage::VERSION::MAJOR >= 6
13
+ {resize_to_limit: [100, 100]}
14
+ else
15
+ {resize: '100x100>'}
16
+ end
13
17
  end
14
18
 
15
19
  register_instance_option :delete_value do
@@ -38,7 +38,8 @@ module RailsAdmin
38
38
  image_html = v.image_tag(thumb_url, class: 'img-thumbnail')
39
39
  url != thumb_url ? v.link_to(image_html, url, target: '_blank', rel: 'noopener noreferrer') : image_html
40
40
  else
41
- v.link_to(value, url, target: '_blank', rel: 'noopener noreferrer')
41
+ display_value = value.respond_to?(:filename) ? value.filename : value
42
+ v.link_to(display_value, url, target: '_blank', rel: 'noopener noreferrer')
42
43
  end
43
44
  end
44
45
  end
@@ -10,14 +10,16 @@ module RailsAdmin
10
10
  register_instance_option :thumb_method do
11
11
  unless defined? @thumb_method
12
12
  @thumb_method = begin
13
- next nil unless value.is_a?(Hash)
13
+ next nil unless bindings[:object].respond_to?("#{name}_derivatives")
14
14
 
15
- if value.key?(:thumb)
15
+ derivatives = bindings[:object].public_send("#{name}_derivatives")
16
+
17
+ if derivatives.key?(:thumb)
16
18
  :thumb
17
- elsif value.key?(:thumbnail)
19
+ elsif derivatives.key?(:thumbnail)
18
20
  :thumbnail
19
21
  else
20
- value.keys.first
22
+ derivatives.keys.first
21
23
  end
22
24
  end
23
25
  end
@@ -29,21 +31,21 @@ module RailsAdmin
29
31
  end
30
32
 
31
33
  register_instance_option :cache_method do
32
- name
34
+ name if bindings[:object].try("cached_#{name}_data")
33
35
  end
34
36
 
35
37
  register_instance_option :cache_value do
36
- bindings[:object].public_send("cached_#{name}_data") if bindings[:object].respond_to?("cached_#{name}_data")
38
+ bindings[:object].try("cached_#{name}_data")
39
+ end
40
+
41
+ register_instance_option :link_name do
42
+ value.original_filename
37
43
  end
38
44
 
39
45
  def resource_url(thumb = nil)
40
46
  return nil unless value
41
47
 
42
- if value.is_a?(Hash)
43
- value[thumb || value.keys.first].url
44
- else
45
- value.url
46
- end
48
+ thumb && bindings[:object].public_send(:"#{name}", thumb).try(:url) || value.url
47
49
  end
48
50
  end
49
51
  end
@@ -56,12 +56,12 @@ module RailsAdmin
56
56
  @model
57
57
  end
58
58
 
59
- def method_missing(method, *args, &block)
60
- target.send(method, *args, &block)
59
+ def method_missing(method_name, *args, &block)
60
+ target.send(method_name, *args, &block)
61
61
  end
62
62
 
63
- def respond_to?(method, include_private = false)
64
- super || target.respond_to?(method, include_private)
63
+ def respond_to?(method_name, include_private = false)
64
+ super || target.respond_to?(method_name, include_private)
65
65
  end
66
66
  end
67
67
  end
@@ -100,8 +100,8 @@ module RailsAdmin
100
100
 
101
101
  # Act as a proxy for the base section configuration that actually
102
102
  # store the configurations.
103
- def method_missing(m, *args, &block)
104
- send(:base).send(m, *args, &block)
103
+ def method_missing(method_name, *args, &block)
104
+ send(:base).send(method_name, *args, &block)
105
105
  end
106
106
  end
107
107
  end
@@ -17,18 +17,18 @@ module RailsAdmin
17
17
  self
18
18
  end
19
19
 
20
- def method_missing(name, *args, &block)
21
- if @object.respond_to?(name)
20
+ def method_missing(method_name, *args, &block)
21
+ if @object.respond_to?(method_name)
22
22
  reset = @object.bindings
23
23
  begin
24
24
  @object.bindings = @bindings
25
- response = @object.__send__(name, *args, &block)
25
+ response = @object.__send__(method_name, *args, &block)
26
26
  ensure
27
27
  @object.bindings = reset
28
28
  end
29
29
  response
30
30
  else
31
- super(name, *args, &block)
31
+ super(method_name, *args, &block)
32
32
  end
33
33
  end
34
34
  end
@@ -33,7 +33,7 @@ module RailsAdmin
33
33
  (AUTHORIZATION_ADAPTERS.values + AUDITING_ADAPTERS.values).each do |klass|
34
34
  begin
35
35
  klass.setup if klass.respond_to? :setup
36
- rescue # rubocop:disable Lint/HandleExceptions
36
+ rescue # rubocop:disable Lint/HandleExceptions, Style/RescueStandardError
37
37
  # ignore errors
38
38
  end
39
39
  end
@@ -116,7 +116,7 @@ module RailsAdmin
116
116
 
117
117
  current_page = page.presence || '1'
118
118
 
119
- versions = object.nil? ? versions_for_model(model) : object.versions
119
+ versions = object.nil? ? versions_for_model(model) : object.public_send(model.model.versions_association_name)
120
120
  versions = versions.where('event LIKE ?', "%#{query}%") if query.present?
121
121
  versions = versions.order(sort_reverse == 'true' ? "#{sort} DESC" : sort)
122
122
  versions = all ? versions : versions.send(Kaminari.config.page_method_name, current_page).per(per_page)
@@ -1,8 +1,8 @@
1
1
  module RailsAdmin
2
2
  class Version
3
3
  MAJOR = 2
4
- MINOR = 0
5
- PATCH = 1
4
+ MINOR = 2
5
+ PATCH = 0
6
6
  PRE = nil
7
7
 
8
8
  class << self
@@ -12,18 +12,27 @@ namespace :rails_admin do
12
12
  desc 'CI env for Travis'
13
13
  task :prepare_ci_env do
14
14
  adapter = ENV['CI_DB_ADAPTER'] || 'sqlite3'
15
- database = ENV['CI_DB_DATABASE'] || ('sqlite3' == adapter ? 'db/development.sqlite3' : 'ci_rails_admin')
15
+ database = ('sqlite3' == adapter ? 'db/development.sqlite3' : 'ci_rails_admin')
16
+ username =
17
+ case adapter
18
+ when 'postgresql'
19
+ 'postgres'
20
+ when 'mysql2'
21
+ 'root'
22
+ else
23
+ ''
24
+ end
16
25
 
17
26
  configuration = {
18
27
  'test' => {
19
28
  'adapter' => adapter,
20
29
  'database' => database,
21
- 'username' => ENV['CI_DB_USERNAME'],
22
- 'password' => ENV['CI_DB_PASSWORD'],
23
- 'host' => ENV['CI_DB_HOST'] || 'localhost',
24
- 'encoding' => ENV['CI_DB_ENCODING'] || 'utf8',
25
- 'pool' => (ENV['CI_DB_POOL'] || 5).to_int,
26
- 'timeout' => (ENV['CI_DB_TIMEOUT'] || 5000).to_int,
30
+ 'username' => username,
31
+ 'password' => ('postgresql' == adapter ? 'postgres' : ''),
32
+ 'host' => '127.0.0.1',
33
+ 'encoding' => 'utf8',
34
+ 'pool' => 5,
35
+ 'timeout' => 5000,
27
36
  },
28
37
  }
29
38
 
@@ -1,33 +1,8 @@
1
- /*! version : 4.14.30
2
- =========================================================
3
- bootstrap-datetimejs
4
- https://github.com/Eonasdan/bootstrap-datetimepicker
5
- Copyright (c) 2015 Jonathan Peterson
6
- =========================================================
7
- */
8
- /*
9
- The MIT License (MIT)
10
-
11
- Copyright (c) 2015 Jonathan Peterson
12
-
13
- Permission is hereby granted, free of charge, to any person obtaining a copy
14
- of this software and associated documentation files (the "Software"), to deal
15
- in the Software without restriction, including without limitation the rights
16
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
- copies of the Software, and to permit persons to whom the Software is
18
- furnished to do so, subject to the following conditions:
19
-
20
- The above copyright notice and this permission notice shall be included in
21
- all copies or substantial portions of the Software.
22
-
23
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
- THE SOFTWARE.
30
- */
1
+ /*!
2
+ * Bootstrap Datetime Picker v4.17.49
3
+ * Copyright 2015-2020 Jonathan Peterson
4
+ * Licensed under MIT (https://github.com/Eonasdan/bootstrap-datetimepicker/blob/master/LICENSE)
5
+ */
31
6
  /*global define:false */
32
7
  /*global exports:false */
33
8
  /*global require:false */
@@ -39,7 +14,7 @@
39
14
  // AMD is used - Register as an anonymous module.
40
15
  define(['jquery', 'moment'], factory);
41
16
  } else if (typeof exports === 'object') {
42
- factory(require('jquery'), require('moment'));
17
+ module.exports = factory(require('jquery'), require('moment'));
43
18
  } else {
44
19
  // Neither AMD nor CommonJS used. Use global variables.
45
20
  if (typeof jQuery === 'undefined') {
@@ -58,8 +33,8 @@
58
33
 
59
34
  var dateTimePicker = function (element, options) {
60
35
  var picker = {},
61
- date = moment().startOf('d'),
62
- viewDate = date.clone(),
36
+ date,
37
+ viewDate,
63
38
  unset = true,
64
39
  input,
65
40
  component = false,
@@ -132,6 +107,34 @@
132
107
  * Private functions
133
108
  *
134
109
  ********************************************************************************/
110
+
111
+ hasTimeZone = function () {
112
+ return moment.tz !== undefined && options.timeZone !== undefined && options.timeZone !== null && options.timeZone !== '';
113
+ },
114
+
115
+ getMoment = function (d) {
116
+ var returnMoment;
117
+
118
+ if (d === undefined || d === null) {
119
+ returnMoment = moment(); //TODO should this use format? and locale?
120
+ } else if (moment.isDate(d) || moment.isMoment(d)) {
121
+ // If the date that is passed in is already a Date() or moment() object,
122
+ // pass it directly to moment.
123
+ returnMoment = moment(d);
124
+ } else if (hasTimeZone()) { // There is a string to parse and a default time zone
125
+ // parse with the tz function which takes a default time zone if it is not in the format string
126
+ returnMoment = moment.tz(d, parseFormats, options.useStrict, options.timeZone);
127
+ } else {
128
+ returnMoment = moment(d, parseFormats, options.useStrict);
129
+ }
130
+
131
+ if (hasTimeZone()) {
132
+ returnMoment.tz(options.timeZone);
133
+ }
134
+
135
+ return returnMoment;
136
+ },
137
+
135
138
  isEnabled = function (granularity) {
136
139
  if (typeof granularity !== 'string' || granularity.length > 1) {
137
140
  throw new TypeError('isEnabled expects a single character string parameter');
@@ -154,6 +157,7 @@
154
157
  return false;
155
158
  }
156
159
  },
160
+
157
161
  hasTime = function () {
158
162
  return (isEnabled('h') || isEnabled('m') || isEnabled('s'));
159
163
  },
@@ -209,13 +213,11 @@
209
213
 
210
214
  if (isEnabled('h')) {
211
215
  topRow.append($('<td>')
212
- .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Increment Hour'}).addClass('btn').attr('data-action', 'incrementHours')
213
- .append($('<span>').addClass(options.icons.up))));
216
+ .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementHour }).addClass('btn').attr('data-action', 'incrementHours').append($('<span>').addClass(options.icons.up))));
214
217
  middleRow.append($('<td>')
215
- .append($('<span>').addClass('timepicker-hour').attr({'data-time-component':'hours', 'title':'Pick Hour'}).attr('data-action', 'showHours')));
218
+ .append($('<span>').addClass('timepicker-hour').attr({ 'data-time-component': 'hours', 'title': options.tooltips.pickHour }).attr('data-action', 'showHours')));
216
219
  bottomRow.append($('<td>')
217
- .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Decrement Hour'}).addClass('btn').attr('data-action', 'decrementHours')
218
- .append($('<span>').addClass(options.icons.down))));
220
+ .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementHour }).addClass('btn').attr('data-action', 'decrementHours').append($('<span>').addClass(options.icons.down))));
219
221
  }
220
222
  if (isEnabled('m')) {
221
223
  if (isEnabled('h')) {
@@ -224,12 +226,12 @@
224
226
  bottomRow.append($('<td>').addClass('separator'));
225
227
  }
226
228
  topRow.append($('<td>')
227
- .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Increment Minute'}).addClass('btn').attr('data-action', 'incrementMinutes')
229
+ .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementMinute }).addClass('btn').attr('data-action', 'incrementMinutes')
228
230
  .append($('<span>').addClass(options.icons.up))));
229
231
  middleRow.append($('<td>')
230
- .append($('<span>').addClass('timepicker-minute').attr({'data-time-component': 'minutes', 'title':'Pick Minute'}).attr('data-action', 'showMinutes')));
232
+ .append($('<span>').addClass('timepicker-minute').attr({ 'data-time-component': 'minutes', 'title': options.tooltips.pickMinute }).attr('data-action', 'showMinutes')));
231
233
  bottomRow.append($('<td>')
232
- .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Decrement Minute'}).addClass('btn').attr('data-action', 'decrementMinutes')
234
+ .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementMinute }).addClass('btn').attr('data-action', 'decrementMinutes')
233
235
  .append($('<span>').addClass(options.icons.down))));
234
236
  }
235
237
  if (isEnabled('s')) {
@@ -239,19 +241,19 @@
239
241
  bottomRow.append($('<td>').addClass('separator'));
240
242
  }
241
243
  topRow.append($('<td>')
242
- .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Increment Second'}).addClass('btn').attr('data-action', 'incrementSeconds')
244
+ .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementSecond }).addClass('btn').attr('data-action', 'incrementSeconds')
243
245
  .append($('<span>').addClass(options.icons.up))));
244
246
  middleRow.append($('<td>')
245
- .append($('<span>').addClass('timepicker-second').attr({'data-time-component': 'seconds', 'title':'Pick Second'}).attr('data-action', 'showSeconds')));
247
+ .append($('<span>').addClass('timepicker-second').attr({ 'data-time-component': 'seconds', 'title': options.tooltips.pickSecond }).attr('data-action', 'showSeconds')));
246
248
  bottomRow.append($('<td>')
247
- .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Decrement Second'}).addClass('btn').attr('data-action', 'decrementSeconds')
249
+ .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementSecond }).addClass('btn').attr('data-action', 'decrementSeconds')
248
250
  .append($('<span>').addClass(options.icons.down))));
249
251
  }
250
252
 
251
253
  if (!use24Hours) {
252
254
  topRow.append($('<td>').addClass('separator'));
253
255
  middleRow.append($('<td>')
254
- .append($('<button>').addClass('btn btn-primary').attr({'data-action': 'togglePeriod', tabindex: '-1', 'title':'Toggle Period'})));
256
+ .append($('<button>').addClass('btn btn-primary').attr({ 'data-action': 'togglePeriod', tabindex: '-1', 'title': options.tooltips.togglePeriod })));
255
257
  bottomRow.append($('<td>').addClass('separator'));
256
258
  }
257
259
 
@@ -285,16 +287,16 @@
285
287
  getToolbar = function () {
286
288
  var row = [];
287
289
  if (options.showTodayButton) {
288
- row.push($('<td>').append($('<a>').attr({'data-action':'today', 'title':'Go to today'}).append($('<span>').addClass(options.icons.today))));
290
+ row.push($('<td>').append($('<a>').attr({ 'data-action': 'today', 'title': options.tooltips.today }).append($('<span>').addClass(options.icons.today))));
289
291
  }
290
292
  if (!options.sideBySide && hasDate() && hasTime()) {
291
- row.push($('<td>').append($('<a>').attr({'data-action':'togglePicker', 'title':'Select Time'}).append($('<span>').addClass(options.icons.time))));
293
+ row.push($('<td>').append($('<a>').attr({ 'data-action': 'togglePicker', 'title': options.tooltips.selectTime }).append($('<span>').addClass(options.icons.time))));
292
294
  }
293
295
  if (options.showClear) {
294
- row.push($('<td>').append($('<a>').attr({'data-action':'clear', 'title':'Clear selection'}).append($('<span>').addClass(options.icons.clear))));
296
+ row.push($('<td>').append($('<a>').attr({ 'data-action': 'clear', 'title': options.tooltips.clear }).append($('<span>').addClass(options.icons.clear))));
295
297
  }
296
298
  if (options.showClose) {
297
- row.push($('<td>').append($('<a>').attr({'data-action':'close', 'title':'Close the picker'}).append($('<span>').addClass(options.icons.close))));
299
+ row.push($('<td>').append($('<a>').attr({ 'data-action': 'close', 'title': options.tooltips.close }).append($('<span>').addClass(options.icons.close))));
298
300
  }
299
301
  return $('<table>').addClass('table-condensed').append($('<tbody>').append($('<tr>').append(row)));
300
302
  },
@@ -313,17 +315,24 @@
313
315
  if (use24Hours) {
314
316
  template.addClass('usetwentyfour');
315
317
  }
318
+
316
319
  if (isEnabled('s') && !use24Hours) {
317
320
  template.addClass('wider');
318
321
  }
322
+
319
323
  if (options.sideBySide && hasDate() && hasTime()) {
320
324
  template.addClass('timepicker-sbs');
325
+ if (options.toolbarPlacement === 'top') {
326
+ template.append(toolbar);
327
+ }
321
328
  template.append(
322
329
  $('<div>').addClass('row')
323
- .append(dateView.addClass('col-sm-6'))
324
- .append(timeView.addClass('col-sm-6'))
330
+ .append(dateView.addClass('col-md-6'))
331
+ .append(timeView.addClass('col-md-6'))
325
332
  );
326
- template.append(toolbar);
333
+ if (options.toolbarPlacement === 'bottom') {
334
+ template.append(toolbar);
335
+ }
327
336
  return template;
328
337
  }
329
338
 
@@ -419,20 +428,20 @@
419
428
  widget.removeClass('pull-right');
420
429
  }
421
430
 
422
- // find the first parent element that has a relative css positioning
423
- if (parent.css('position') !== 'relative') {
431
+ // find the first parent element that has a non-static css positioning
432
+ if (parent.css('position') === 'static') {
424
433
  parent = parent.parents().filter(function () {
425
- return $(this).css('position') === 'relative';
434
+ return $(this).css('position') !== 'static';
426
435
  }).first();
427
436
  }
428
437
 
429
438
  if (parent.length === 0) {
430
- throw new Error('datetimepicker component should be placed within a relative positioned container');
439
+ throw new Error('datetimepicker component should be placed within a non-static positioned container');
431
440
  }
432
441
 
433
442
  widget.css({
434
443
  top: vertical === 'top' ? 'auto' : position.top + element.outerHeight(),
435
- bottom: vertical === 'top' ? position.top + element.outerHeight() : 'auto',
444
+ bottom: vertical === 'top' ? parent.outerHeight() - (parent === element ? 0 : position.top) : 'auto',
436
445
  left: horizontal === 'left' ? (parent === element ? 0 : position.left) : 'auto',
437
446
  right: horizontal === 'left' ? 'auto' : parent.outerWidth() - element.outerWidth() - (parent === element ? 0 : position.left)
438
447
  });
@@ -552,9 +561,9 @@
552
561
  monthsViewHeader = monthsView.find('th'),
553
562
  months = monthsView.find('tbody').find('span');
554
563
 
555
- monthsViewHeader.eq(0).find('span').attr('title', 'Previous Year');
556
- monthsViewHeader.eq(1).attr('title', 'Select Year');
557
- monthsViewHeader.eq(2).find('span').attr('title', 'Next Year');
564
+ monthsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevYear);
565
+ monthsViewHeader.eq(1).attr('title', options.tooltips.selectYear);
566
+ monthsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextYear);
558
567
 
559
568
  monthsView.find('.disabled').removeClass('disabled');
560
569
 
@@ -587,9 +596,9 @@
587
596
  endYear = viewDate.clone().add(6, 'y'),
588
597
  html = '';
589
598
 
590
- yearsViewHeader.eq(0).find('span').attr('title', 'Previous Decade');
591
- yearsViewHeader.eq(1).attr('title', 'Select Decade');
592
- yearsViewHeader.eq(2).find('span').attr('title', 'Next Decade');
599
+ yearsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevDecade);
600
+ yearsViewHeader.eq(1).attr('title', options.tooltips.selectDecade);
601
+ yearsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextDecade);
593
602
 
594
603
  yearsView.find('.disabled').removeClass('disabled');
595
604
 
@@ -614,33 +623,41 @@
614
623
  updateDecades = function () {
615
624
  var decadesView = widget.find('.datepicker-decades'),
616
625
  decadesViewHeader = decadesView.find('th'),
617
- startDecade = viewDate.isBefore(moment({y: 1999})) ? moment({y: 1899}) : moment({y: 1999}),
626
+ startDecade = moment({ y: viewDate.year() - (viewDate.year() % 100) - 1 }),
618
627
  endDecade = startDecade.clone().add(100, 'y'),
628
+ startedAt = startDecade.clone(),
629
+ minDateDecade = false,
630
+ maxDateDecade = false,
631
+ endDecadeYear,
619
632
  html = '';
620
633
 
621
- decadesViewHeader.eq(0).find('span').attr('title', 'Previous Century');
622
- decadesViewHeader.eq(2).find('span').attr('title', 'Next Century');
634
+ decadesViewHeader.eq(0).find('span').attr('title', options.tooltips.prevCentury);
635
+ decadesViewHeader.eq(2).find('span').attr('title', options.tooltips.nextCentury);
623
636
 
624
637
  decadesView.find('.disabled').removeClass('disabled');
625
638
 
626
- if (startDecade.isSame(moment({y: 1900})) || (options.minDate && options.minDate.isAfter(startDecade, 'y'))) {
639
+ if (startDecade.isSame(moment({ y: 1900 })) || (options.minDate && options.minDate.isAfter(startDecade, 'y'))) {
627
640
  decadesViewHeader.eq(0).addClass('disabled');
628
641
  }
629
642
 
630
643
  decadesViewHeader.eq(1).text(startDecade.year() + '-' + endDecade.year());
631
644
 
632
- if (startDecade.isSame(moment({y: 2000})) || (options.maxDate && options.maxDate.isBefore(endDecade, 'y'))) {
645
+ if (startDecade.isSame(moment({ y: 2000 })) || (options.maxDate && options.maxDate.isBefore(endDecade, 'y'))) {
633
646
  decadesViewHeader.eq(2).addClass('disabled');
634
647
  }
635
648
 
636
649
  while (!startDecade.isAfter(endDecade, 'y')) {
637
- html += '<span data-action="selectDecade" class="decade' + (startDecade.isSame(date, 'y') ? ' active' : '') +
638
- (!isValid(startDecade, 'y') ? ' disabled' : '') + '" data-selection="' + (startDecade.year() + 6) + '">' + (startDecade.year() + 1) + ' - ' + (startDecade.year() + 12) + '</span>';
650
+ endDecadeYear = startDecade.year() + 12;
651
+ minDateDecade = options.minDate && options.minDate.isAfter(startDecade, 'y') && options.minDate.year() <= endDecadeYear;
652
+ maxDateDecade = options.maxDate && options.maxDate.isAfter(startDecade, 'y') && options.maxDate.year() <= endDecadeYear;
653
+ html += '<span data-action="selectDecade" class="decade' + (date.isAfter(startDecade) && date.year() <= endDecadeYear ? ' active' : '') +
654
+ (!isValid(startDecade, 'y') && !minDateDecade && !maxDateDecade ? ' disabled' : '') + '" data-selection="' + (startDecade.year() + 6) + '">' + (startDecade.year() + 1) + ' - ' + (startDecade.year() + 12) + '</span>';
639
655
  startDecade.add(12, 'y');
640
656
  }
641
657
  html += '<span></span><span></span><span></span>'; //push the dangling block over, at least this way it's even
642
658
 
643
659
  decadesView.find('td').html(html);
660
+ decadesViewHeader.eq(1).text((startedAt.year() + 1) + '-' + (startDecade.year()));
644
661
  },
645
662
 
646
663
  fillDate = function () {
@@ -649,16 +666,16 @@
649
666
  currentDate,
650
667
  html = [],
651
668
  row,
652
- clsName,
669
+ clsNames = [],
653
670
  i;
654
671
 
655
672
  if (!hasDate()) {
656
673
  return;
657
674
  }
658
675
 
659
- daysViewHeader.eq(0).find('span').attr('title', 'Previous Month');
660
- daysViewHeader.eq(1).attr('title', 'Select Month');
661
- daysViewHeader.eq(2).find('span').attr('title', 'Next Month');
676
+ daysViewHeader.eq(0).find('span').attr('title', options.tooltips.prevMonth);
677
+ daysViewHeader.eq(1).attr('title', options.tooltips.selectMonth);
678
+ daysViewHeader.eq(2).find('span').attr('title', options.tooltips.nextMonth);
662
679
 
663
680
  daysView.find('.disabled').removeClass('disabled');
664
681
  daysViewHeader.eq(1).text(viewDate.format(options.dayViewHeaderFormat));
@@ -680,26 +697,31 @@
680
697
  }
681
698
  html.push(row);
682
699
  }
683
- clsName = '';
700
+ clsNames = ['day'];
684
701
  if (currentDate.isBefore(viewDate, 'M')) {
685
- clsName += ' old';
702
+ clsNames.push('old');
686
703
  }
687
704
  if (currentDate.isAfter(viewDate, 'M')) {
688
- clsName += ' new';
705
+ clsNames.push('new');
689
706
  }
690
707
  if (currentDate.isSame(date, 'd') && !unset) {
691
- clsName += ' active';
708
+ clsNames.push('active');
692
709
  }
693
710
  if (!isValid(currentDate, 'd')) {
694
- clsName += ' disabled';
711
+ clsNames.push('disabled');
695
712
  }
696
- if (currentDate.isSame(moment(), 'd')) {
697
- clsName += ' today';
713
+ if (currentDate.isSame(getMoment(), 'd')) {
714
+ clsNames.push('today');
698
715
  }
699
716
  if (currentDate.day() === 0 || currentDate.day() === 6) {
700
- clsName += ' weekend';
717
+ clsNames.push('weekend');
701
718
  }
702
- row.append('<td data-action="selectDay" data-day="' + currentDate.format('L') + '" class="day' + clsName + '">' + currentDate.date() + '</td>');
719
+ notifyEvent({
720
+ type: 'dp.classify',
721
+ date: currentDate,
722
+ classNames: clsNames
723
+ });
724
+ row.append('<td data-action="selectDay" data-day="' + currentDate.format('L') + '" class="' + clsNames.join(' ') + '">' + currentDate.date() + '</td>');
703
725
  currentDate.add(1, 'd');
704
726
  }
705
727
 
@@ -819,8 +841,16 @@
819
841
 
820
842
  targetMoment = targetMoment.clone().locale(options.locale);
821
843
 
844
+ if (hasTimeZone()) {
845
+ targetMoment.tz(options.timeZone);
846
+ }
847
+
822
848
  if (options.stepping !== 1) {
823
- targetMoment.minutes((Math.round(targetMoment.minutes() / options.stepping) * options.stepping) % 60).seconds(0);
849
+ targetMoment.minutes((Math.round(targetMoment.minutes() / options.stepping) * options.stepping)).seconds(0);
850
+
851
+ while (options.minDate && targetMoment.isBefore(options.minDate)) {
852
+ targetMoment.add(options.stepping, 'minutes');
853
+ }
824
854
  }
825
855
 
826
856
  if (isValid(targetMoment)) {
@@ -838,16 +868,25 @@
838
868
  } else {
839
869
  if (!options.keepInvalid) {
840
870
  input.val(unset ? '' : date.format(actualFormat));
871
+ } else {
872
+ notifyEvent({
873
+ type: 'dp.change',
874
+ date: targetMoment,
875
+ oldDate: oldDate
876
+ });
841
877
  }
842
878
  notifyEvent({
843
879
  type: 'dp.error',
844
- date: targetMoment
880
+ date: targetMoment,
881
+ oldDate: oldDate
845
882
  });
846
883
  }
847
884
  },
848
885
 
886
+ /**
887
+ * Hides the widget. Possibly will emit dp.hide
888
+ */
849
889
  hide = function () {
850
- ///<summary>Hides the widget. Possibly will emit dp.hide</summary>
851
890
  var transitioning = false;
852
891
  if (!widget) {
853
892
  return picker;
@@ -880,6 +919,11 @@
880
919
  type: 'dp.hide',
881
920
  date: date.clone()
882
921
  });
922
+
923
+ input.blur();
924
+
925
+ viewDate = date.clone();
926
+
883
927
  return picker;
884
928
  },
885
929
 
@@ -887,6 +931,18 @@
887
931
  setValue(null);
888
932
  },
889
933
 
934
+ parseInputDate = function (inputDate) {
935
+ if (options.parseInputDate === undefined) {
936
+ if (!moment.isMoment(inputDate) || inputDate instanceof Date) {
937
+ inputDate = getMoment(inputDate);
938
+ }
939
+ } else {
940
+ inputDate = options.parseInputDate(inputDate);
941
+ }
942
+ //inputDate.locale(options.locale);
943
+ return inputDate;
944
+ },
945
+
890
946
  /********************************************************************************
891
947
  *
892
948
  * Widget UI interaction functions
@@ -1099,8 +1155,9 @@
1099
1155
  clear: clear,
1100
1156
 
1101
1157
  today: function () {
1102
- if (isValid(moment(), 'd')) {
1103
- setValue(moment());
1158
+ var todaysDate = getMoment();
1159
+ if (isValid(todaysDate, 'd')) {
1160
+ setValue(todaysDate);
1104
1161
  }
1105
1162
  },
1106
1163
 
@@ -1115,8 +1172,10 @@
1115
1172
  return false;
1116
1173
  },
1117
1174
 
1175
+ /**
1176
+ * Shows the widget. Possibly will emit dp.show and dp.change
1177
+ */
1118
1178
  show = function () {
1119
- ///<summary>Shows the widget. Possibly will emit dp.show and dp.change</summary>
1120
1179
  var currentMoment,
1121
1180
  useCurrentGranularity = {
1122
1181
  'year': function (m) {
@@ -1141,14 +1200,13 @@
1141
1200
  }
1142
1201
  if (input.val() !== undefined && input.val().trim().length !== 0) {
1143
1202
  setValue(parseInputDate(input.val().trim()));
1144
- } else if (options.useCurrent && unset && ((input.is('input') && input.val().trim().length === 0) || options.inline)) {
1145
- currentMoment = moment();
1203
+ } else if (unset && options.useCurrent && (options.inline || (input.is('input') && input.val().trim().length === 0))) {
1204
+ currentMoment = getMoment();
1146
1205
  if (typeof options.useCurrent === 'string') {
1147
1206
  currentMoment = useCurrentGranularity[options.useCurrent](currentMoment);
1148
1207
  }
1149
1208
  setValue(currentMoment);
1150
1209
  }
1151
-
1152
1210
  widget = getTemplate();
1153
1211
 
1154
1212
  fillDow();
@@ -1168,9 +1226,8 @@
1168
1226
  if (component && component.hasClass('btn')) {
1169
1227
  component.toggleClass('active');
1170
1228
  }
1171
- widget.show();
1172
1229
  place();
1173
-
1230
+ widget.show();
1174
1231
  if (options.focusOnShow && !input.is(':focus')) {
1175
1232
  input.focus();
1176
1233
  }
@@ -1181,25 +1238,13 @@
1181
1238
  return picker;
1182
1239
  },
1183
1240
 
1241
+ /**
1242
+ * Shows or hides the widget
1243
+ */
1184
1244
  toggle = function () {
1185
- /// <summary>Shows or hides the widget</summary>
1186
1245
  return (widget ? hide() : show());
1187
1246
  },
1188
1247
 
1189
- parseInputDate = function (inputDate) {
1190
- if (options.parseInputDate === undefined) {
1191
- if (moment.isMoment(inputDate) || inputDate instanceof Date) {
1192
- inputDate = moment(inputDate);
1193
- } else {
1194
- inputDate = moment(inputDate, parseFormats, options.useStrict);
1195
- }
1196
- } else {
1197
- inputDate = options.parseInputDate(inputDate);
1198
- }
1199
- inputDate.locale(options.locale);
1200
- return inputDate;
1201
- },
1202
-
1203
1248
  keydown = function (e) {
1204
1249
  var handler = null,
1205
1250
  index,
@@ -1284,7 +1329,7 @@
1284
1329
  detachDatePickerElementEvents = function () {
1285
1330
  input.off({
1286
1331
  'change': change,
1287
- 'blur': hide,
1332
+ 'blur': blur,
1288
1333
  'keydown': keydown,
1289
1334
  'keyup': keyup,
1290
1335
  'focus': options.allowInputToggle ? hide : ''
@@ -1467,7 +1512,7 @@
1467
1512
  }
1468
1513
 
1469
1514
  if ((typeof newFormat !== 'string') && ((typeof newFormat !== 'boolean') || (newFormat !== false))) {
1470
- throw new TypeError('format() expects a sting or boolean:false parameter ' + newFormat);
1515
+ throw new TypeError('format() expects a string or boolean:false parameter ' + newFormat);
1471
1516
  }
1472
1517
 
1473
1518
  options.format = newFormat;
@@ -1477,6 +1522,20 @@
1477
1522
  return picker;
1478
1523
  };
1479
1524
 
1525
+ picker.timeZone = function (newZone) {
1526
+ if (arguments.length === 0) {
1527
+ return options.timeZone;
1528
+ }
1529
+
1530
+ if (typeof newZone !== 'string') {
1531
+ throw new TypeError('newZone() expects a string parameter');
1532
+ }
1533
+
1534
+ options.timeZone = newZone;
1535
+
1536
+ return picker;
1537
+ };
1538
+
1480
1539
  picker.dayViewHeaderFormat = function (newFormat) {
1481
1540
  if (arguments.length === 0) {
1482
1541
  return options.dayViewHeaderFormat;
@@ -1589,8 +1648,8 @@
1589
1648
  var tries = 0;
1590
1649
  while (!isValid(date, 'd')) {
1591
1650
  date.add(1, 'd');
1592
- if (tries === 7) {
1593
- throw 'Tried 7 times to find a valid date';
1651
+ if (tries === 31) {
1652
+ throw 'Tried 31 times to find a valid date';
1594
1653
  }
1595
1654
  tries++;
1596
1655
  }
@@ -1613,7 +1672,7 @@
1613
1672
 
1614
1673
  if (typeof maxDate === 'string') {
1615
1674
  if (maxDate === 'now' || maxDate === 'moment') {
1616
- maxDate = moment();
1675
+ maxDate = getMoment();
1617
1676
  }
1618
1677
  }
1619
1678
 
@@ -1630,7 +1689,7 @@
1630
1689
  setValue(options.maxDate);
1631
1690
  }
1632
1691
  if (viewDate.isAfter(parsedDate)) {
1633
- viewDate = parsedDate.clone();
1692
+ viewDate = parsedDate.clone().subtract(options.stepping, 'm');
1634
1693
  }
1635
1694
  update();
1636
1695
  return picker;
@@ -1649,7 +1708,7 @@
1649
1708
 
1650
1709
  if (typeof minDate === 'string') {
1651
1710
  if (minDate === 'now' || minDate === 'moment') {
1652
- minDate = moment();
1711
+ minDate = getMoment();
1653
1712
  }
1654
1713
  }
1655
1714
 
@@ -1666,7 +1725,7 @@
1666
1725
  setValue(options.minDate);
1667
1726
  }
1668
1727
  if (viewDate.isBefore(parsedDate)) {
1669
- viewDate = parsedDate.clone();
1728
+ viewDate = parsedDate.clone().add(options.stepping, 'm');
1670
1729
  }
1671
1730
  update();
1672
1731
  return picker;
@@ -1691,7 +1750,9 @@
1691
1750
 
1692
1751
  if (typeof defaultDate === 'string') {
1693
1752
  if (defaultDate === 'now' || defaultDate === 'moment') {
1694
- defaultDate = moment();
1753
+ defaultDate = getMoment();
1754
+ } else {
1755
+ defaultDate = getMoment(defaultDate);
1695
1756
  }
1696
1757
  }
1697
1758
 
@@ -1705,7 +1766,7 @@
1705
1766
 
1706
1767
  options.defaultDate = parsedDate;
1707
1768
 
1708
- if (options.defaultDate && options.inline || (input.val().trim() === '' && input.attr('placeholder') === undefined)) {
1769
+ if ((options.defaultDate && options.inline) || input.val().trim() === '') {
1709
1770
  setValue(options.defaultDate);
1710
1771
  }
1711
1772
  return picker;
@@ -1798,6 +1859,22 @@
1798
1859
  return picker;
1799
1860
  };
1800
1861
 
1862
+ picker.tooltips = function (tooltips) {
1863
+ if (arguments.length === 0) {
1864
+ return $.extend({}, options.tooltips);
1865
+ }
1866
+
1867
+ if (!(tooltips instanceof Object)) {
1868
+ throw new TypeError('tooltips() expects parameter to be an Object');
1869
+ }
1870
+ $.extend(options.tooltips, tooltips);
1871
+ if (widget) {
1872
+ hide();
1873
+ show();
1874
+ }
1875
+ return picker;
1876
+ };
1877
+
1801
1878
  picker.useStrict = function (useStrict) {
1802
1879
  if (arguments.length === 0) {
1803
1880
  return options.useStrict;
@@ -2012,10 +2089,18 @@
2012
2089
  };
2013
2090
 
2014
2091
  picker.keyBinds = function (keyBinds) {
2092
+ if (arguments.length === 0) {
2093
+ return options.keyBinds;
2094
+ }
2095
+
2015
2096
  options.keyBinds = keyBinds;
2016
2097
  return picker;
2017
2098
  };
2018
2099
 
2100
+ picker.getMoment = function (d) {
2101
+ return getMoment(d);
2102
+ };
2103
+
2019
2104
  picker.debug = function (debug) {
2020
2105
  if (typeof debug !== 'boolean') {
2021
2106
  throw new TypeError('debug() expects a boolean parameter');
@@ -2193,16 +2278,12 @@
2193
2278
  update();
2194
2279
  return picker;
2195
2280
  };
2196
-
2281
+ /**
2282
+ * Returns the component's model current viewDate, a moment object or null if not set. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration.
2283
+ * @param {Takes string, viewDate, moment, null parameter.} newDate
2284
+ * @returns {viewDate.clone()}
2285
+ */
2197
2286
  picker.viewDate = function (newDate) {
2198
- ///<signature helpKeyword="$.fn.datetimepicker.viewDate">
2199
- ///<summary>Returns the component's model current viewDate, a moment object or null if not set.</summary>
2200
- ///<returns type="Moment">viewDate.clone()</returns>
2201
- ///</signature>
2202
- ///<signature>
2203
- ///<summary>Sets the components model current moment to it. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration.</summary>
2204
- ///<param name="newDate" locid="$.fn.datetimepicker.date_p:newDate">Takes string, viewDate, moment, null parameter.</param>
2205
- ///</signature>
2206
2287
  if (arguments.length === 0) {
2207
2288
  return viewDate.clone();
2208
2289
  }
@@ -2226,7 +2307,7 @@
2226
2307
  input = element;
2227
2308
  } else {
2228
2309
  input = element.find(options.datepickerInput);
2229
- if (input.size() === 0) {
2310
+ if (input.length === 0) {
2230
2311
  input = element.find('input');
2231
2312
  } else if (!input.is('input')) {
2232
2313
  throw new Error('CSS class "' + options.datepickerInput + '" cannot be applied to non input element');
@@ -2235,8 +2316,8 @@
2235
2316
 
2236
2317
  if (element.hasClass('input-group')) {
2237
2318
  // in case there is more then one 'input-group-addon' Issue #48
2238
- if (element.find('.datepickerbutton').size() === 0) {
2239
- component = element.find('[class^="input-group-"]');
2319
+ if (element.find('.datepickerbutton').length === 0) {
2320
+ component = element.find('.input-group-addon');
2240
2321
  } else {
2241
2322
  component = element.find('.datepickerbutton');
2242
2323
  }
@@ -2246,6 +2327,10 @@
2246
2327
  throw new Error('Could not initialize DateTimePicker without an input element');
2247
2328
  }
2248
2329
 
2330
+ // Set defaults for date here now instead of in var declaration
2331
+ date = getMoment();
2332
+ viewDate = date.clone();
2333
+
2249
2334
  $.extend(true, options, dataToOptions());
2250
2335
 
2251
2336
  picker.options(options);
@@ -2275,18 +2360,68 @@
2275
2360
  *
2276
2361
  ********************************************************************************/
2277
2362
 
2363
+ /**
2364
+ * See (http://jquery.com/).
2365
+ * @name jQuery
2366
+ * @class
2367
+ * See the jQuery Library (http://jquery.com/) for full details. This just
2368
+ * documents the function and classes that are added to jQuery by this plug-in.
2369
+ */
2370
+ /**
2371
+ * See (http://jquery.com/)
2372
+ * @name fn
2373
+ * @class
2374
+ * See the jQuery Library (http://jquery.com/) for full details. This just
2375
+ * documents the function and classes that are added to jQuery by this plug-in.
2376
+ * @memberOf jQuery
2377
+ */
2378
+ /**
2379
+ * Show comments
2380
+ * @class datetimepicker
2381
+ * @memberOf jQuery.fn
2382
+ */
2278
2383
  $.fn.datetimepicker = function (options) {
2279
- return this.each(function () {
2280
- var $this = $(this);
2281
- if (!$this.data('DateTimePicker')) {
2282
- // create a private copy of the defaults object
2283
- options = $.extend(true, {}, $.fn.datetimepicker.defaults, options);
2284
- $this.data('DateTimePicker', dateTimePicker($this, options));
2285
- }
2286
- });
2384
+ options = options || {};
2385
+
2386
+ var args = Array.prototype.slice.call(arguments, 1),
2387
+ isInstance = true,
2388
+ thisMethods = ['destroy', 'hide', 'show', 'toggle'],
2389
+ returnValue;
2390
+
2391
+ if (typeof options === 'object') {
2392
+ return this.each(function () {
2393
+ var $this = $(this),
2394
+ _options;
2395
+ if (!$this.data('DateTimePicker')) {
2396
+ // create a private copy of the defaults object
2397
+ _options = $.extend(true, {}, $.fn.datetimepicker.defaults, options);
2398
+ $this.data('DateTimePicker', dateTimePicker($this, _options));
2399
+ }
2400
+ });
2401
+ } else if (typeof options === 'string') {
2402
+ this.each(function () {
2403
+ var $this = $(this),
2404
+ instance = $this.data('DateTimePicker');
2405
+ if (!instance) {
2406
+ throw new Error('bootstrap-datetimepicker("' + options + '") method was called on an element that is not using DateTimePicker');
2407
+ }
2408
+
2409
+ returnValue = instance[options].apply(instance, args);
2410
+ isInstance = returnValue === instance;
2411
+ });
2412
+
2413
+ if (isInstance || $.inArray(options, thisMethods) > -1) {
2414
+ return this;
2415
+ }
2416
+
2417
+ return returnValue;
2418
+ }
2419
+
2420
+ throw new TypeError('Invalid arguments for DateTimePicker: ' + options);
2287
2421
  };
2288
2422
 
2289
2423
  $.fn.datetimepicker.defaults = {
2424
+ timeZone: '',
2290
2425
  format: false,
2291
2426
  dayViewHeaderFormat: 'MMMM YYYY',
2292
2427
  extraFormats: false,
@@ -2310,6 +2445,33 @@
2310
2445
  clear: 'glyphicon glyphicon-trash',
2311
2446
  close: 'glyphicon glyphicon-remove'
2312
2447
  },
2448
+ tooltips: {
2449
+ today: 'Go to today',
2450
+ clear: 'Clear selection',
2451
+ close: 'Close the picker',
2452
+ selectMonth: 'Select Month',
2453
+ prevMonth: 'Previous Month',
2454
+ nextMonth: 'Next Month',
2455
+ selectYear: 'Select Year',
2456
+ prevYear: 'Previous Year',
2457
+ nextYear: 'Next Year',
2458
+ selectDecade: 'Select Decade',
2459
+ prevDecade: 'Previous Decade',
2460
+ nextDecade: 'Next Decade',
2461
+ prevCentury: 'Previous Century',
2462
+ nextCentury: 'Next Century',
2463
+ pickHour: 'Pick Hour',
2464
+ incrementHour: 'Increment Hour',
2465
+ decrementHour: 'Decrement Hour',
2466
+ pickMinute: 'Pick Minute',
2467
+ incrementMinute: 'Increment Minute',
2468
+ decrementMinute: 'Decrement Minute',
2469
+ pickSecond: 'Pick Second',
2470
+ incrementSecond: 'Increment Second',
2471
+ decrementSecond: 'Decrement Second',
2472
+ togglePeriod: 'Toggle Period',
2473
+ selectTime: 'Select Time'
2474
+ },
2313
2475
  useStrict: false,
2314
2476
  sideBySide: false,
2315
2477
  daysOfWeekDisabled: false,
@@ -2335,11 +2497,11 @@
2335
2497
  if (!widget) {
2336
2498
  return;
2337
2499
  }
2338
- var d = this.date() || moment();
2500
+ var d = this.date() || this.getMoment();
2339
2501
  if (widget.find('.datepicker').is(':visible')) {
2340
2502
  this.date(d.clone().subtract(7, 'd'));
2341
2503
  } else {
2342
- this.date(d.clone().add(1, 'm'));
2504
+ this.date(d.clone().add(this.stepping(), 'm'));
2343
2505
  }
2344
2506
  },
2345
2507
  down: function (widget) {
@@ -2347,18 +2509,18 @@
2347
2509
  this.show();
2348
2510
  return;
2349
2511
  }
2350
- var d = this.date() || moment();
2512
+ var d = this.date() || this.getMoment();
2351
2513
  if (widget.find('.datepicker').is(':visible')) {
2352
2514
  this.date(d.clone().add(7, 'd'));
2353
2515
  } else {
2354
- this.date(d.clone().subtract(1, 'm'));
2516
+ this.date(d.clone().subtract(this.stepping(), 'm'));
2355
2517
  }
2356
2518
  },
2357
2519
  'control up': function (widget) {
2358
2520
  if (!widget) {
2359
2521
  return;
2360
2522
  }
2361
- var d = this.date() || moment();
2523
+ var d = this.date() || this.getMoment();
2362
2524
  if (widget.find('.datepicker').is(':visible')) {
2363
2525
  this.date(d.clone().subtract(1, 'y'));
2364
2526
  } else {
@@ -2369,7 +2531,7 @@
2369
2531
  if (!widget) {
2370
2532
  return;
2371
2533
  }
2372
- var d = this.date() || moment();
2534
+ var d = this.date() || this.getMoment();
2373
2535
  if (widget.find('.datepicker').is(':visible')) {
2374
2536
  this.date(d.clone().add(1, 'y'));
2375
2537
  } else {
@@ -2380,7 +2542,7 @@
2380
2542
  if (!widget) {
2381
2543
  return;
2382
2544
  }
2383
- var d = this.date() || moment();
2545
+ var d = this.date() || this.getMoment();
2384
2546
  if (widget.find('.datepicker').is(':visible')) {
2385
2547
  this.date(d.clone().subtract(1, 'd'));
2386
2548
  }
@@ -2389,7 +2551,7 @@
2389
2551
  if (!widget) {
2390
2552
  return;
2391
2553
  }
2392
- var d = this.date() || moment();
2554
+ var d = this.date() || this.getMoment();
2393
2555
  if (widget.find('.datepicker').is(':visible')) {
2394
2556
  this.date(d.clone().add(1, 'd'));
2395
2557
  }
@@ -2398,7 +2560,7 @@
2398
2560
  if (!widget) {
2399
2561
  return;
2400
2562
  }
2401
- var d = this.date() || moment();
2563
+ var d = this.date() || this.getMoment();
2402
2564
  if (widget.find('.datepicker').is(':visible')) {
2403
2565
  this.date(d.clone().subtract(1, 'M'));
2404
2566
  }
@@ -2407,7 +2569,7 @@
2407
2569
  if (!widget) {
2408
2570
  return;
2409
2571
  }
2410
- var d = this.date() || moment();
2572
+ var d = this.date() || this.getMoment();
2411
2573
  if (widget.find('.datepicker').is(':visible')) {
2412
2574
  this.date(d.clone().add(1, 'M'));
2413
2575
  }
@@ -2423,12 +2585,15 @@
2423
2585
  // if(toggle.length > 0) toggle.click();
2424
2586
  //},
2425
2587
  'control space': function (widget) {
2588
+ if (!widget) {
2589
+ return;
2590
+ }
2426
2591
  if (widget.find('.timepicker').is(':visible')) {
2427
2592
  widget.find('.btn[data-action="togglePeriod"]').click();
2428
2593
  }
2429
2594
  },
2430
2595
  t: function () {
2431
- this.date(moment());
2596
+ this.date(this.getMoment());
2432
2597
  },
2433
2598
  'delete': function () {
2434
2599
  this.clear();
@@ -2441,4 +2606,6 @@
2441
2606
  enabledHours: false,
2442
2607
  viewDate: false
2443
2608
  };
2609
+
2610
+ return $.fn.datetimepicker;
2444
2611
  }));