rails_admin 0.7.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rails_admin might be problematic. Click here for more details.

Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +13 -2
  3. data/README.md +2 -2
  4. data/app/assets/javascripts/rails_admin/ra.filter-box.js +53 -39
  5. data/app/assets/javascripts/rails_admin/ra.filtering-multiselect.js +50 -32
  6. data/app/assets/javascripts/rails_admin/ra.i18n.coffee +5 -3
  7. data/app/assets/javascripts/rails_admin/ra.widgets.coffee +21 -6
  8. data/app/assets/javascripts/rails_admin/rails_admin.js +2 -3
  9. data/app/assets/stylesheets/rails_admin/base/theming.scss +6 -7
  10. data/app/assets/stylesheets/rails_admin/custom/mixins.scss +3 -4
  11. data/app/assets/stylesheets/rails_admin/custom/theming.scss +4 -4
  12. data/app/assets/stylesheets/rails_admin/custom/variables.scss +4 -4
  13. data/app/assets/stylesheets/rails_admin/rails_admin.scss.erb +3 -3
  14. data/app/assets/stylesheets/rails_admin/themes/default/mixins.scss +2 -2
  15. data/app/assets/stylesheets/rails_admin/themes/default/theming.scss +3 -3
  16. data/app/assets/stylesheets/rails_admin/themes/default/variables.scss +3 -3
  17. data/app/controllers/rails_admin/application_controller.rb +0 -2
  18. data/app/controllers/rails_admin/main_controller.rb +6 -5
  19. data/app/helpers/rails_admin/application_helper.rb +3 -3
  20. data/app/helpers/rails_admin/main_helper.rb +45 -0
  21. data/app/views/layouts/rails_admin/application.html.haml +2 -2
  22. data/app/views/layouts/rails_admin/pjax.html.haml +2 -2
  23. data/app/views/rails_admin/main/_form_datetime.html.haml +4 -1
  24. data/app/views/rails_admin/main/_form_filtering_multiselect.html.haml +1 -0
  25. data/app/views/rails_admin/main/_submit_buttons.html.haml +1 -1
  26. data/app/views/rails_admin/main/export.html.haml +13 -13
  27. data/app/views/rails_admin/main/index.html.haml +20 -64
  28. data/config/locales/rails_admin.en.yml +0 -1
  29. data/lib/generators/rails_admin/templates/initializer.erb +3 -0
  30. data/lib/rails_admin.rb +2 -1
  31. data/lib/rails_admin/abstract_model.rb +14 -16
  32. data/lib/rails_admin/adapters/active_record.rb +17 -8
  33. data/lib/rails_admin/adapters/active_record/association.rb +5 -0
  34. data/lib/rails_admin/adapters/mongoid.rb +7 -12
  35. data/lib/rails_admin/adapters/mongoid/association.rb +5 -0
  36. data/lib/rails_admin/config/actions/export.rb +1 -1
  37. data/lib/rails_admin/config/fields.rb +1 -0
  38. data/lib/rails_admin/config/fields/association.rb +5 -0
  39. data/lib/rails_admin/config/fields/base.rb +4 -0
  40. data/lib/rails_admin/config/fields/factories/paperclip.rb +1 -1
  41. data/lib/rails_admin/config/fields/factories/refile.rb +25 -0
  42. data/lib/rails_admin/config/fields/types/active_record_enum.rb +5 -5
  43. data/lib/rails_admin/config/fields/types/all.rb +1 -0
  44. data/lib/rails_admin/config/fields/types/bson_object_id.rb +16 -2
  45. data/lib/rails_admin/config/fields/types/date.rb +19 -8
  46. data/lib/rails_admin/config/fields/types/datetime.rb +33 -117
  47. data/lib/rails_admin/config/fields/types/json.rb +5 -2
  48. data/lib/rails_admin/config/fields/types/refile.rb +27 -0
  49. data/lib/rails_admin/config/fields/types/serialized.rb +5 -2
  50. data/lib/rails_admin/config/fields/types/time.rb +6 -18
  51. data/lib/rails_admin/config/has_fields.rb +2 -2
  52. data/lib/rails_admin/config/lazy_model.rb +2 -2
  53. data/lib/rails_admin/config/proxyable/proxy.rb +2 -4
  54. data/lib/rails_admin/extensions/pundit.rb +3 -0
  55. data/lib/rails_admin/extensions/pundit/authorization_adapter.rb +63 -0
  56. data/lib/rails_admin/support/datetime.rb +99 -0
  57. data/lib/rails_admin/support/hash_helper.rb +28 -0
  58. data/lib/rails_admin/support/i18n.rb +41 -0
  59. data/lib/rails_admin/version.rb +2 -2
  60. data/vendor/assets/javascripts/rails_admin/bootstrap-datetimepicker.js +2444 -0
  61. data/vendor/assets/javascripts/rails_admin/moment-with-locales.js +9977 -0
  62. data/vendor/assets/stylesheets/rails_admin/_bootstrap-datetimepicker.scss +343 -0
  63. data/vendor/assets/stylesheets/rails_admin/bootstrap-datetimepicker-build.scss +16 -0
  64. metadata +14 -9
  65. data/app/assets/javascripts/rails_admin/jquery.ui.timepicker.js +0 -1437
  66. data/app/assets/javascripts/rails_admin/ra.datetimepicker.js +0 -83
  67. data/app/assets/stylesheets/rails_admin/jquery.ui.timepicker.scss +0 -68
  68. data/app/assets/stylesheets/rails_admin/ra.calendar-additions.scss +0 -45
  69. data/lib/rails_admin/i18n_support.rb +0 -39
  70. data/lib/rails_admin/support/core_extensions.rb +0 -30
@@ -13,9 +13,12 @@ module RailsAdmin
13
13
  value.present? ? JSON.pretty_generate(value) : nil
14
14
  end
15
15
 
16
+ def parse_value(value)
17
+ value.present? ? JSON.parse(value) : nil
18
+ end
19
+
16
20
  def parse_input(params)
17
- return unless params[name].is_a?(::String)
18
- params[name] = (params[name].blank? ? nil : JSON.parse(params[name]))
21
+ params[name] = parse_value(params[name]) if params[name].is_a?(::String)
19
22
  end
20
23
  end
21
24
  end
@@ -0,0 +1,27 @@
1
+ require 'rails_admin/config/fields/base'
2
+ require 'rails_admin/config/fields/types/file_upload'
3
+
4
+ module RailsAdmin
5
+ module Config
6
+ module Fields
7
+ module Types
8
+ class Refile < RailsAdmin::Config::Fields::Types::FileUpload
9
+ RailsAdmin::Config::Fields::Types.register(self)
10
+
11
+ register_instance_option :thumb_method do
12
+ [:limit, 100, 100]
13
+ end
14
+
15
+ register_instance_option :delete_method do
16
+ "remove_#{name}"
17
+ end
18
+
19
+ def resource_url(thumb = [])
20
+ return nil unless value
21
+ Object.const_get(:Refile).attachment_url(bindings[:object], name, *thumb)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -12,9 +12,12 @@ module RailsAdmin
12
12
  YAML.dump(value) unless value.nil?
13
13
  end
14
14
 
15
+ def parse_value(value)
16
+ value.present? ? (SafeYAML.load(value) || nil) : nil
17
+ end
18
+
15
19
  def parse_input(params)
16
- return unless params[name].is_a?(::String)
17
- params[name] = (params[name].blank? ? nil : (SafeYAML.load(params[name]) || nil))
20
+ params[name] = parse_value(params[name]) if params[name].is_a?(::String)
18
21
  end
19
22
  end
20
23
  end
@@ -5,29 +5,17 @@ module RailsAdmin
5
5
  module Fields
6
6
  module Types
7
7
  class Time < RailsAdmin::Config::Fields::Types::Datetime
8
- # Register field type for the type loader
9
8
  RailsAdmin::Config::Fields::Types.register(self)
10
9
 
11
- @format = :short
12
- @i18n_scope = [:time, :formats]
13
- @js_plugin_options = {
14
- 'showDate' => false,
15
- }
16
-
17
- # Register field type for the type loader
18
- RailsAdmin::Config::Fields::Types.register(self)
19
-
20
- def parse_input(params)
21
- params[name] = self.class.normalize(params[name], localized_time_format) if params[name].present?
22
- end
23
-
24
- # Parse normalized date (time) strings using UTC
25
- def self.parse_date_string(date_string)
26
- ::DateTime.parse(date_string)
10
+ def parse_value(value)
11
+ parent_value = super(value)
12
+ return unless parent_value
13
+ value_with_tz = parent_value.in_time_zone
14
+ ::DateTime.parse(value_with_tz.strftime('%Y-%m-%d %H:%M:%S'))
27
15
  end
28
16
 
29
17
  register_instance_option :strftime_format do
30
- (localized_format.include? '%p') ? '%I:%M %p' : '%H:%M' # rubocop:disable ParenthesesAroundCondition
18
+ '%H:%M'
31
19
  end
32
20
  end
33
21
  end
@@ -21,11 +21,11 @@ module RailsAdmin
21
21
  elsif type && type != (field.nil? ? nil : field.type)
22
22
  if field
23
23
  properties = field.properties
24
- _fields.delete(field)
24
+ field = _fields[_fields.index(field)] = RailsAdmin::Config::Fields::Types.load(type).new(self, name, properties)
25
25
  else
26
26
  properties = abstract_model.properties.detect { |p| name == p.name }
27
+ field = (_fields << RailsAdmin::Config::Fields::Types.load(type).new(self, name, properties)).last
27
28
  end
28
- field = (_fields << RailsAdmin::Config::Fields::Types.load(type).new(self, name, properties)).last
29
29
  end
30
30
 
31
31
  # If field has not been yet defined add some default properties
@@ -2,7 +2,7 @@ require 'rails_admin/config/model'
2
2
 
3
3
  module RailsAdmin
4
4
  module Config
5
- class LazyModel
5
+ class LazyModel < BasicObject
6
6
  def initialize(entity, &block)
7
7
  @entity = entity
8
8
  @deferred_block = block
@@ -10,7 +10,7 @@ module RailsAdmin
10
10
 
11
11
  def target
12
12
  unless @model
13
- @model = RailsAdmin::Config::Model.new(@entity)
13
+ @model = ::RailsAdmin::Config::Model.new(@entity)
14
14
  @model.instance_eval(&@deferred_block) if @deferred_block
15
15
  end
16
16
  @model
@@ -1,9 +1,7 @@
1
1
  module RailsAdmin
2
2
  module Config
3
3
  module Proxyable
4
- class Proxy
5
- instance_methods.each { |m| undef_method m unless m =~ /^(__|instance_eval|object_id)/ }
6
-
4
+ class Proxy < BasicObject
7
5
  attr_reader :bindings
8
6
 
9
7
  def initialize(object, bindings = {})
@@ -13,7 +11,7 @@ module RailsAdmin
13
11
 
14
12
  # Bind variables to be used by the configuration options
15
13
  def bind(key, value = nil)
16
- if key.is_a?(Hash)
14
+ if key.is_a?(::Hash)
17
15
  @bindings = key
18
16
  else
19
17
  @bindings[key] = value
@@ -0,0 +1,3 @@
1
+ require 'rails_admin/extensions/pundit/authorization_adapter'
2
+
3
+ RailsAdmin.add_extension(:pundit, RailsAdmin::Extensions::Pundit, authorization: true)
@@ -0,0 +1,63 @@
1
+ module RailsAdmin
2
+ module Extensions
3
+ module Pundit
4
+ # This adapter is for the Pundit[https://github.com/elabs/pundit] authorization library.
5
+ # You can create another adapter for different authorization behavior, just be certain it
6
+ # responds to each of the public methods here.
7
+ class AuthorizationAdapter
8
+ # See the +authorize_with+ config method for where the initialization happens.
9
+ def initialize(controller)
10
+ @controller = controller
11
+ end
12
+
13
+ # This method is called in every controller action and should raise an exception
14
+ # when the authorization fails. The first argument is the name of the controller
15
+ # action as a symbol (:create, :bulk_delete, etc.). The second argument is the
16
+ # AbstractModel instance that applies. The third argument is the actual model
17
+ # instance if it is available.
18
+ def authorize(action, abstract_model = nil, model_object = nil)
19
+ record = model_object || abstract_model && abstract_model.model
20
+ fail ::Pundit::NotAuthorizedError.new("not allowed to #{action} this #{record}") unless policy(record).send(action_for_pundit(action)) if action
21
+ end
22
+
23
+ # This method is called primarily from the view to determine whether the given user
24
+ # has access to perform the action on a given model. It should return true when authorized.
25
+ # This takes the same arguments as +authorize+. The difference is that this will
26
+ # return a boolean whereas +authorize+ will raise an exception when not authorized.
27
+ def authorized?(action, abstract_model = nil, model_object = nil)
28
+ record = model_object || abstract_model && abstract_model.model
29
+ policy(record).send(action_for_pundit(action)) if action
30
+ end
31
+
32
+ # This is called when needing to scope a database query. It is called within the list
33
+ # and bulk_delete/destroy actions and should return a scope which limits the records
34
+ # to those which the user can perform the given action on.
35
+ def query(_action, abstract_model)
36
+ @controller.policy_scope(abstract_model.model.all)
37
+ rescue ::Pundit::NotDefinedError
38
+ abstract_model.model.all
39
+ end
40
+
41
+ # This is called in the new/create actions to determine the initial attributes for new
42
+ # records. It should return a hash of attributes which match what the user
43
+ # is authorized to create.
44
+ def attributes_for(action, abstract_model)
45
+ record = abstract_model && abstract_model.model
46
+ policy(record).try(:attributes_for, action) || {}
47
+ end
48
+
49
+ private
50
+
51
+ def policy(record)
52
+ @controller.policy(record)
53
+ rescue ::Pundit::NotDefinedError
54
+ ::ApplicationPolicy.new(@controller.send(:pundit_user), record)
55
+ end
56
+
57
+ def action_for_pundit(action)
58
+ action[-1, 1] == '?' ? action : "#{action}?"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,99 @@
1
+ require 'rails_admin/support/i18n'
2
+
3
+ module RailsAdmin
4
+ module Support
5
+ class Datetime
6
+ # Ruby format options as a key and momentjs format options as a value
7
+ MOMENTJS_TRANSLATIONS = {
8
+ '%a' => 'ddd', # The abbreviated weekday name ("Sun")
9
+ '%A' => 'dddd', # The full weekday name ("Sunday")
10
+ '%b' => 'MMM', # The abbreviated month name ("Jan")
11
+ '%B' => 'MMMM', # The full month name ("January")
12
+ '%d' => 'DD', # Day of the month (01..31)
13
+ '%D' => 'MM/DD/YY', # American date format mm/dd/yy
14
+ '%e' => 'D', # Day of the month (1..31)
15
+ '%F' => 'YY-MM-DD', # ISO 8601 date format
16
+ '%H' => 'HH', # Hour of the day, 24-hour clock (00..23)
17
+ '%I' => 'hh', # Hour of the day, 12-hour clock (01..12)
18
+ '%m' => 'MM', # Month of the year (01..12)
19
+ '%-m' => 'M', # Month of the year (1..12)
20
+ '%M' => 'mm', # Minute of the hour (00..59)
21
+ '%p' => 'A', # Meridian indicator ('AM' or 'PM')
22
+ '%S' => 'ss', # Second of the minute (00..60)
23
+ '%Y' => 'YYYY', # Year with century
24
+ '%y' => 'YY', # Year without a century (00..99)
25
+ }
26
+
27
+ class << self
28
+ include RailsAdmin::Support::I18n
29
+
30
+ def delocalize(date_string, format)
31
+ return date_string if ::I18n.locale.to_s == 'en'
32
+ format.to_s.scan(/%[AaBbp]/) do |match|
33
+ case match
34
+ when '%A'
35
+ english = ::I18n.t('date.day_names', locale: :en)
36
+ day_names.each_with_index { |d, i| date_string = date_string.gsub(/#{d}/, english[i]) }
37
+ when '%a'
38
+ english = ::I18n.t('date.abbr_day_names', locale: :en)
39
+ abbr_day_names.each_with_index { |d, i| date_string = date_string.gsub(/#{d}/, english[i]) }
40
+ when '%B'
41
+ english = ::I18n.t('date.month_names', locale: :en)[1..-1]
42
+ month_names.each_with_index { |m, i| date_string = date_string.gsub(/#{m}/, english[i]) }
43
+ when '%b'
44
+ english = ::I18n.t('date.abbr_month_names', locale: :en)[1..-1]
45
+ abbr_month_names.each_with_index { |m, i| date_string = date_string.gsub(/#{m}/, english[i]) }
46
+ when '%p'
47
+ date_string = date_string.gsub(/#{::I18n.t('date.time.am', default: "am")}/, 'am')
48
+ date_string = date_string.gsub(/#{::I18n.t('date.time.pm', default: "pm")}/, 'pm')
49
+ end
50
+ end
51
+ date_string
52
+ end
53
+
54
+ def normalize(date_string, format)
55
+ return unless date_string
56
+ delocalize(date_string, format)
57
+ parse_date_string(date_string)
58
+ end
59
+
60
+ # Parse normalized date strings using time zone
61
+ def parse_date_string(date_string)
62
+ ::Time.zone.parse(date_string)
63
+ end
64
+ end
65
+
66
+ attr_reader :strftime_format
67
+
68
+ def initialize(strftime_format)
69
+ @strftime_format = strftime_format
70
+ end
71
+
72
+ # Ruby to javascript formatting options translator
73
+ def to_momentjs
74
+ strftime_format.gsub(/\w[^.(!?%)\W]{1,}/, '[\0]').gsub(/%(\w|\-\w)/) do |match|
75
+ MOMENTJS_TRANSLATIONS[match]
76
+ end
77
+ end
78
+
79
+ # Delocalize a l10n datetime strings
80
+ def delocalize(value)
81
+ self.class.delocalize(value, strftime_format)
82
+ end
83
+
84
+ def parse_string(value)
85
+ return if value.blank?
86
+ return value if %w(DateTime Date Time).include?(value.class.name)
87
+ return if (delocalized_value = delocalize(value)).blank?
88
+
89
+ begin
90
+ # Adjust with the correct timezone and daylight savint time
91
+ datetime_with_wrong_tz = ::DateTime.strptime(delocalized_value, strftime_format)
92
+ Time.zone.parse(datetime_with_wrong_tz.strftime('%Y-%m-%d %H:%M:%S'))
93
+ rescue ArgumentError
94
+ nil
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,28 @@
1
+ module RailsAdmin
2
+ class HashHelper
3
+ def self.symbolize(obj)
4
+ case obj
5
+ when Array
6
+ obj.each_with_object([]) do |val, res|
7
+ res << case val
8
+ when Hash, Array then symbolize(val)
9
+ when String then val.to_sym
10
+ else val
11
+ end
12
+ end
13
+ when Hash
14
+ obj.each_with_object({}) do |(key, val), res|
15
+ nkey = key.is_a?(String) ? key.to_sym : key
16
+ nval = case val
17
+ when Hash, Array then symbolize(val)
18
+ when String then val.to_sym
19
+ else val
20
+ end
21
+ res[nkey] = nval
22
+ end
23
+ else
24
+ obj
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,41 @@
1
+ require 'i18n'
2
+
3
+ module RailsAdmin
4
+ module Support
5
+ module I18n
6
+ def abbr_day_names
7
+ ::I18n.t('date.abbr_day_names', raise: true)
8
+ rescue ::I18n::ArgumentError
9
+ ::I18n.t('date.abbr_day_names', locale: :en)
10
+ end
11
+
12
+ def abbr_month_names
13
+ begin
14
+ names = ::I18n.t('date.abbr_month_names', raise: true)
15
+ rescue ::I18n::ArgumentError
16
+ names = ::I18n.t('date.abbr_month_names', locale: :en)
17
+ end
18
+ names[1..-1]
19
+ end
20
+
21
+ def date_format
22
+ ::I18n.t('date.formats.default', default: ::I18n.t('date.formats.default', locale: :en))
23
+ end
24
+
25
+ def day_names
26
+ ::I18n.t('date.day_names', raise: true)
27
+ rescue ::I18n::ArgumentError
28
+ ::I18n.t('date.day_names', locale: :en)
29
+ end
30
+
31
+ def month_names
32
+ begin
33
+ names = ::I18n.t('date.month_names', raise: true)
34
+ rescue ::I18n::ArgumentError
35
+ names = ::I18n.t('date.month_names', locale: :en)
36
+ end
37
+ names[1..-1]
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,8 +1,8 @@
1
1
  module RailsAdmin
2
2
  class Version
3
3
  MAJOR = 0
4
- MINOR = 7
5
- PATCH = 0
4
+ MINOR = 8
5
+ PATCH = 1
6
6
  PRE = nil
7
7
 
8
8
  class << self
@@ -0,0 +1,2444 @@
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
+ */
31
+ /*global define:false */
32
+ /*global exports:false */
33
+ /*global require:false */
34
+ /*global jQuery:false */
35
+ /*global moment:false */
36
+ (function (factory) {
37
+ 'use strict';
38
+ if (typeof define === 'function' && define.amd) {
39
+ // AMD is used - Register as an anonymous module.
40
+ define(['jquery', 'moment'], factory);
41
+ } else if (typeof exports === 'object') {
42
+ factory(require('jquery'), require('moment'));
43
+ } else {
44
+ // Neither AMD nor CommonJS used. Use global variables.
45
+ if (typeof jQuery === 'undefined') {
46
+ throw 'bootstrap-datetimepicker requires jQuery to be loaded first';
47
+ }
48
+ if (typeof moment === 'undefined') {
49
+ throw 'bootstrap-datetimepicker requires Moment.js to be loaded first';
50
+ }
51
+ factory(jQuery, moment);
52
+ }
53
+ }(function ($, moment) {
54
+ 'use strict';
55
+ if (!moment) {
56
+ throw new Error('bootstrap-datetimepicker requires Moment.js to be loaded first');
57
+ }
58
+
59
+ var dateTimePicker = function (element, options) {
60
+ var picker = {},
61
+ date = moment().startOf('d'),
62
+ viewDate = date.clone(),
63
+ unset = true,
64
+ input,
65
+ component = false,
66
+ widget = false,
67
+ use24Hours,
68
+ minViewModeNumber = 0,
69
+ actualFormat,
70
+ parseFormats,
71
+ currentViewMode,
72
+ datePickerModes = [
73
+ {
74
+ clsName: 'days',
75
+ navFnc: 'M',
76
+ navStep: 1
77
+ },
78
+ {
79
+ clsName: 'months',
80
+ navFnc: 'y',
81
+ navStep: 1
82
+ },
83
+ {
84
+ clsName: 'years',
85
+ navFnc: 'y',
86
+ navStep: 10
87
+ },
88
+ {
89
+ clsName: 'decades',
90
+ navFnc: 'y',
91
+ navStep: 100
92
+ }
93
+ ],
94
+ viewModes = ['days', 'months', 'years', 'decades'],
95
+ verticalModes = ['top', 'bottom', 'auto'],
96
+ horizontalModes = ['left', 'right', 'auto'],
97
+ toolbarPlacements = ['default', 'top', 'bottom'],
98
+ keyMap = {
99
+ 'up': 38,
100
+ 38: 'up',
101
+ 'down': 40,
102
+ 40: 'down',
103
+ 'left': 37,
104
+ 37: 'left',
105
+ 'right': 39,
106
+ 39: 'right',
107
+ 'tab': 9,
108
+ 9: 'tab',
109
+ 'escape': 27,
110
+ 27: 'escape',
111
+ 'enter': 13,
112
+ 13: 'enter',
113
+ 'pageUp': 33,
114
+ 33: 'pageUp',
115
+ 'pageDown': 34,
116
+ 34: 'pageDown',
117
+ 'shift': 16,
118
+ 16: 'shift',
119
+ 'control': 17,
120
+ 17: 'control',
121
+ 'space': 32,
122
+ 32: 'space',
123
+ 't': 84,
124
+ 84: 't',
125
+ 'delete': 46,
126
+ 46: 'delete'
127
+ },
128
+ keyState = {},
129
+
130
+ /********************************************************************************
131
+ *
132
+ * Private functions
133
+ *
134
+ ********************************************************************************/
135
+ isEnabled = function (granularity) {
136
+ if (typeof granularity !== 'string' || granularity.length > 1) {
137
+ throw new TypeError('isEnabled expects a single character string parameter');
138
+ }
139
+ switch (granularity) {
140
+ case 'y':
141
+ return actualFormat.indexOf('Y') !== -1;
142
+ case 'M':
143
+ return actualFormat.indexOf('M') !== -1;
144
+ case 'd':
145
+ return actualFormat.toLowerCase().indexOf('d') !== -1;
146
+ case 'h':
147
+ case 'H':
148
+ return actualFormat.toLowerCase().indexOf('h') !== -1;
149
+ case 'm':
150
+ return actualFormat.indexOf('m') !== -1;
151
+ case 's':
152
+ return actualFormat.indexOf('s') !== -1;
153
+ default:
154
+ return false;
155
+ }
156
+ },
157
+ hasTime = function () {
158
+ return (isEnabled('h') || isEnabled('m') || isEnabled('s'));
159
+ },
160
+
161
+ hasDate = function () {
162
+ return (isEnabled('y') || isEnabled('M') || isEnabled('d'));
163
+ },
164
+
165
+ getDatePickerTemplate = function () {
166
+ var headTemplate = $('<thead>')
167
+ .append($('<tr>')
168
+ .append($('<th>').addClass('prev').attr('data-action', 'previous')
169
+ .append($('<span>').addClass(options.icons.previous))
170
+ )
171
+ .append($('<th>').addClass('picker-switch').attr('data-action', 'pickerSwitch').attr('colspan', (options.calendarWeeks ? '6' : '5')))
172
+ .append($('<th>').addClass('next').attr('data-action', 'next')
173
+ .append($('<span>').addClass(options.icons.next))
174
+ )
175
+ ),
176
+ contTemplate = $('<tbody>')
177
+ .append($('<tr>')
178
+ .append($('<td>').attr('colspan', (options.calendarWeeks ? '8' : '7')))
179
+ );
180
+
181
+ return [
182
+ $('<div>').addClass('datepicker-days')
183
+ .append($('<table>').addClass('table-condensed')
184
+ .append(headTemplate)
185
+ .append($('<tbody>'))
186
+ ),
187
+ $('<div>').addClass('datepicker-months')
188
+ .append($('<table>').addClass('table-condensed')
189
+ .append(headTemplate.clone())
190
+ .append(contTemplate.clone())
191
+ ),
192
+ $('<div>').addClass('datepicker-years')
193
+ .append($('<table>').addClass('table-condensed')
194
+ .append(headTemplate.clone())
195
+ .append(contTemplate.clone())
196
+ ),
197
+ $('<div>').addClass('datepicker-decades')
198
+ .append($('<table>').addClass('table-condensed')
199
+ .append(headTemplate.clone())
200
+ .append(contTemplate.clone())
201
+ )
202
+ ];
203
+ },
204
+
205
+ getTimePickerMainTemplate = function () {
206
+ var topRow = $('<tr>'),
207
+ middleRow = $('<tr>'),
208
+ bottomRow = $('<tr>');
209
+
210
+ if (isEnabled('h')) {
211
+ 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))));
214
+ middleRow.append($('<td>')
215
+ .append($('<span>').addClass('timepicker-hour').attr({'data-time-component':'hours', 'title':'Pick Hour'}).attr('data-action', 'showHours')));
216
+ 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))));
219
+ }
220
+ if (isEnabled('m')) {
221
+ if (isEnabled('h')) {
222
+ topRow.append($('<td>').addClass('separator'));
223
+ middleRow.append($('<td>').addClass('separator').html(':'));
224
+ bottomRow.append($('<td>').addClass('separator'));
225
+ }
226
+ topRow.append($('<td>')
227
+ .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Increment Minute'}).addClass('btn').attr('data-action', 'incrementMinutes')
228
+ .append($('<span>').addClass(options.icons.up))));
229
+ middleRow.append($('<td>')
230
+ .append($('<span>').addClass('timepicker-minute').attr({'data-time-component': 'minutes', 'title':'Pick Minute'}).attr('data-action', 'showMinutes')));
231
+ bottomRow.append($('<td>')
232
+ .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Decrement Minute'}).addClass('btn').attr('data-action', 'decrementMinutes')
233
+ .append($('<span>').addClass(options.icons.down))));
234
+ }
235
+ if (isEnabled('s')) {
236
+ if (isEnabled('m')) {
237
+ topRow.append($('<td>').addClass('separator'));
238
+ middleRow.append($('<td>').addClass('separator').html(':'));
239
+ bottomRow.append($('<td>').addClass('separator'));
240
+ }
241
+ topRow.append($('<td>')
242
+ .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Increment Second'}).addClass('btn').attr('data-action', 'incrementSeconds')
243
+ .append($('<span>').addClass(options.icons.up))));
244
+ middleRow.append($('<td>')
245
+ .append($('<span>').addClass('timepicker-second').attr({'data-time-component': 'seconds', 'title':'Pick Second'}).attr('data-action', 'showSeconds')));
246
+ bottomRow.append($('<td>')
247
+ .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Decrement Second'}).addClass('btn').attr('data-action', 'decrementSeconds')
248
+ .append($('<span>').addClass(options.icons.down))));
249
+ }
250
+
251
+ if (!use24Hours) {
252
+ topRow.append($('<td>').addClass('separator'));
253
+ middleRow.append($('<td>')
254
+ .append($('<button>').addClass('btn btn-primary').attr({'data-action': 'togglePeriod', tabindex: '-1', 'title':'Toggle Period'})));
255
+ bottomRow.append($('<td>').addClass('separator'));
256
+ }
257
+
258
+ return $('<div>').addClass('timepicker-picker')
259
+ .append($('<table>').addClass('table-condensed')
260
+ .append([topRow, middleRow, bottomRow]));
261
+ },
262
+
263
+ getTimePickerTemplate = function () {
264
+ var hoursView = $('<div>').addClass('timepicker-hours')
265
+ .append($('<table>').addClass('table-condensed')),
266
+ minutesView = $('<div>').addClass('timepicker-minutes')
267
+ .append($('<table>').addClass('table-condensed')),
268
+ secondsView = $('<div>').addClass('timepicker-seconds')
269
+ .append($('<table>').addClass('table-condensed')),
270
+ ret = [getTimePickerMainTemplate()];
271
+
272
+ if (isEnabled('h')) {
273
+ ret.push(hoursView);
274
+ }
275
+ if (isEnabled('m')) {
276
+ ret.push(minutesView);
277
+ }
278
+ if (isEnabled('s')) {
279
+ ret.push(secondsView);
280
+ }
281
+
282
+ return ret;
283
+ },
284
+
285
+ getToolbar = function () {
286
+ var row = [];
287
+ if (options.showTodayButton) {
288
+ row.push($('<td>').append($('<a>').attr({'data-action':'today', 'title':'Go to today'}).append($('<span>').addClass(options.icons.today))));
289
+ }
290
+ if (!options.sideBySide && hasDate() && hasTime()) {
291
+ row.push($('<td>').append($('<a>').attr({'data-action':'togglePicker', 'title':'Select Time'}).append($('<span>').addClass(options.icons.time))));
292
+ }
293
+ if (options.showClear) {
294
+ row.push($('<td>').append($('<a>').attr({'data-action':'clear', 'title':'Clear selection'}).append($('<span>').addClass(options.icons.clear))));
295
+ }
296
+ if (options.showClose) {
297
+ row.push($('<td>').append($('<a>').attr({'data-action':'close', 'title':'Close the picker'}).append($('<span>').addClass(options.icons.close))));
298
+ }
299
+ return $('<table>').addClass('table-condensed').append($('<tbody>').append($('<tr>').append(row)));
300
+ },
301
+
302
+ getTemplate = function () {
303
+ var template = $('<div>').addClass('bootstrap-datetimepicker-widget dropdown-menu'),
304
+ dateView = $('<div>').addClass('datepicker').append(getDatePickerTemplate()),
305
+ timeView = $('<div>').addClass('timepicker').append(getTimePickerTemplate()),
306
+ content = $('<ul>').addClass('list-unstyled'),
307
+ toolbar = $('<li>').addClass('picker-switch' + (options.collapse ? ' accordion-toggle' : '')).append(getToolbar());
308
+
309
+ if (options.inline) {
310
+ template.removeClass('dropdown-menu');
311
+ }
312
+
313
+ if (use24Hours) {
314
+ template.addClass('usetwentyfour');
315
+ }
316
+ if (isEnabled('s') && !use24Hours) {
317
+ template.addClass('wider');
318
+ }
319
+ if (options.sideBySide && hasDate() && hasTime()) {
320
+ template.addClass('timepicker-sbs');
321
+ template.append(
322
+ $('<div>').addClass('row')
323
+ .append(dateView.addClass('col-sm-6'))
324
+ .append(timeView.addClass('col-sm-6'))
325
+ );
326
+ template.append(toolbar);
327
+ return template;
328
+ }
329
+
330
+ if (options.toolbarPlacement === 'top') {
331
+ content.append(toolbar);
332
+ }
333
+ if (hasDate()) {
334
+ content.append($('<li>').addClass((options.collapse && hasTime() ? 'collapse in' : '')).append(dateView));
335
+ }
336
+ if (options.toolbarPlacement === 'default') {
337
+ content.append(toolbar);
338
+ }
339
+ if (hasTime()) {
340
+ content.append($('<li>').addClass((options.collapse && hasDate() ? 'collapse' : '')).append(timeView));
341
+ }
342
+ if (options.toolbarPlacement === 'bottom') {
343
+ content.append(toolbar);
344
+ }
345
+ return template.append(content);
346
+ },
347
+
348
+ dataToOptions = function () {
349
+ var eData,
350
+ dataOptions = {};
351
+
352
+ if (element.is('input') || options.inline) {
353
+ eData = element.data();
354
+ } else {
355
+ eData = element.find('input').data();
356
+ }
357
+
358
+ if (eData.dateOptions && eData.dateOptions instanceof Object) {
359
+ dataOptions = $.extend(true, dataOptions, eData.dateOptions);
360
+ }
361
+
362
+ $.each(options, function (key) {
363
+ var attributeName = 'date' + key.charAt(0).toUpperCase() + key.slice(1);
364
+ if (eData[attributeName] !== undefined) {
365
+ dataOptions[key] = eData[attributeName];
366
+ }
367
+ });
368
+ return dataOptions;
369
+ },
370
+
371
+ place = function () {
372
+ var position = (component || element).position(),
373
+ offset = (component || element).offset(),
374
+ vertical = options.widgetPositioning.vertical,
375
+ horizontal = options.widgetPositioning.horizontal,
376
+ parent;
377
+
378
+ if (options.widgetParent) {
379
+ parent = options.widgetParent.append(widget);
380
+ } else if (element.is('input')) {
381
+ parent = element.after(widget).parent();
382
+ } else if (options.inline) {
383
+ parent = element.append(widget);
384
+ return;
385
+ } else {
386
+ parent = element;
387
+ element.children().first().after(widget);
388
+ }
389
+
390
+ // Top and bottom logic
391
+ if (vertical === 'auto') {
392
+ if (offset.top + widget.height() * 1.5 >= $(window).height() + $(window).scrollTop() &&
393
+ widget.height() + element.outerHeight() < offset.top) {
394
+ vertical = 'top';
395
+ } else {
396
+ vertical = 'bottom';
397
+ }
398
+ }
399
+
400
+ // Left and right logic
401
+ if (horizontal === 'auto') {
402
+ if (parent.width() < offset.left + widget.outerWidth() / 2 &&
403
+ offset.left + widget.outerWidth() > $(window).width()) {
404
+ horizontal = 'right';
405
+ } else {
406
+ horizontal = 'left';
407
+ }
408
+ }
409
+
410
+ if (vertical === 'top') {
411
+ widget.addClass('top').removeClass('bottom');
412
+ } else {
413
+ widget.addClass('bottom').removeClass('top');
414
+ }
415
+
416
+ if (horizontal === 'right') {
417
+ widget.addClass('pull-right');
418
+ } else {
419
+ widget.removeClass('pull-right');
420
+ }
421
+
422
+ // find the first parent element that has a relative css positioning
423
+ if (parent.css('position') !== 'relative') {
424
+ parent = parent.parents().filter(function () {
425
+ return $(this).css('position') === 'relative';
426
+ }).first();
427
+ }
428
+
429
+ if (parent.length === 0) {
430
+ throw new Error('datetimepicker component should be placed within a relative positioned container');
431
+ }
432
+
433
+ widget.css({
434
+ top: vertical === 'top' ? 'auto' : position.top + element.outerHeight(),
435
+ bottom: vertical === 'top' ? position.top + element.outerHeight() : 'auto',
436
+ left: horizontal === 'left' ? (parent === element ? 0 : position.left) : 'auto',
437
+ right: horizontal === 'left' ? 'auto' : parent.outerWidth() - element.outerWidth() - (parent === element ? 0 : position.left)
438
+ });
439
+ },
440
+
441
+ notifyEvent = function (e) {
442
+ if (e.type === 'dp.change' && ((e.date && e.date.isSame(e.oldDate)) || (!e.date && !e.oldDate))) {
443
+ return;
444
+ }
445
+ element.trigger(e);
446
+ },
447
+
448
+ viewUpdate = function (e) {
449
+ if (e === 'y') {
450
+ e = 'YYYY';
451
+ }
452
+ notifyEvent({
453
+ type: 'dp.update',
454
+ change: e,
455
+ viewDate: viewDate.clone()
456
+ });
457
+ },
458
+
459
+ showMode = function (dir) {
460
+ if (!widget) {
461
+ return;
462
+ }
463
+ if (dir) {
464
+ currentViewMode = Math.max(minViewModeNumber, Math.min(3, currentViewMode + dir));
465
+ }
466
+ widget.find('.datepicker > div').hide().filter('.datepicker-' + datePickerModes[currentViewMode].clsName).show();
467
+ },
468
+
469
+ fillDow = function () {
470
+ var row = $('<tr>'),
471
+ currentDate = viewDate.clone().startOf('w').startOf('d');
472
+
473
+ if (options.calendarWeeks === true) {
474
+ row.append($('<th>').addClass('cw').text('#'));
475
+ }
476
+
477
+ while (currentDate.isBefore(viewDate.clone().endOf('w'))) {
478
+ row.append($('<th>').addClass('dow').text(currentDate.format('dd')));
479
+ currentDate.add(1, 'd');
480
+ }
481
+ widget.find('.datepicker-days thead').append(row);
482
+ },
483
+
484
+ isInDisabledDates = function (testDate) {
485
+ return options.disabledDates[testDate.format('YYYY-MM-DD')] === true;
486
+ },
487
+
488
+ isInEnabledDates = function (testDate) {
489
+ return options.enabledDates[testDate.format('YYYY-MM-DD')] === true;
490
+ },
491
+
492
+ isInDisabledHours = function (testDate) {
493
+ return options.disabledHours[testDate.format('H')] === true;
494
+ },
495
+
496
+ isInEnabledHours = function (testDate) {
497
+ return options.enabledHours[testDate.format('H')] === true;
498
+ },
499
+
500
+ isValid = function (targetMoment, granularity) {
501
+ if (!targetMoment.isValid()) {
502
+ return false;
503
+ }
504
+ if (options.disabledDates && granularity === 'd' && isInDisabledDates(targetMoment)) {
505
+ return false;
506
+ }
507
+ if (options.enabledDates && granularity === 'd' && !isInEnabledDates(targetMoment)) {
508
+ return false;
509
+ }
510
+ if (options.minDate && targetMoment.isBefore(options.minDate, granularity)) {
511
+ return false;
512
+ }
513
+ if (options.maxDate && targetMoment.isAfter(options.maxDate, granularity)) {
514
+ return false;
515
+ }
516
+ if (options.daysOfWeekDisabled && granularity === 'd' && options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) {
517
+ return false;
518
+ }
519
+ if (options.disabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && isInDisabledHours(targetMoment)) {
520
+ return false;
521
+ }
522
+ if (options.enabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && !isInEnabledHours(targetMoment)) {
523
+ return false;
524
+ }
525
+ if (options.disabledTimeIntervals && (granularity === 'h' || granularity === 'm' || granularity === 's')) {
526
+ var found = false;
527
+ $.each(options.disabledTimeIntervals, function () {
528
+ if (targetMoment.isBetween(this[0], this[1])) {
529
+ found = true;
530
+ return false;
531
+ }
532
+ });
533
+ if (found) {
534
+ return false;
535
+ }
536
+ }
537
+ return true;
538
+ },
539
+
540
+ fillMonths = function () {
541
+ var spans = [],
542
+ monthsShort = viewDate.clone().startOf('y').startOf('d');
543
+ while (monthsShort.isSame(viewDate, 'y')) {
544
+ spans.push($('<span>').attr('data-action', 'selectMonth').addClass('month').text(monthsShort.format('MMM')));
545
+ monthsShort.add(1, 'M');
546
+ }
547
+ widget.find('.datepicker-months td').empty().append(spans);
548
+ },
549
+
550
+ updateMonths = function () {
551
+ var monthsView = widget.find('.datepicker-months'),
552
+ monthsViewHeader = monthsView.find('th'),
553
+ months = monthsView.find('tbody').find('span');
554
+
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');
558
+
559
+ monthsView.find('.disabled').removeClass('disabled');
560
+
561
+ if (!isValid(viewDate.clone().subtract(1, 'y'), 'y')) {
562
+ monthsViewHeader.eq(0).addClass('disabled');
563
+ }
564
+
565
+ monthsViewHeader.eq(1).text(viewDate.year());
566
+
567
+ if (!isValid(viewDate.clone().add(1, 'y'), 'y')) {
568
+ monthsViewHeader.eq(2).addClass('disabled');
569
+ }
570
+
571
+ months.removeClass('active');
572
+ if (date.isSame(viewDate, 'y') && !unset) {
573
+ months.eq(date.month()).addClass('active');
574
+ }
575
+
576
+ months.each(function (index) {
577
+ if (!isValid(viewDate.clone().month(index), 'M')) {
578
+ $(this).addClass('disabled');
579
+ }
580
+ });
581
+ },
582
+
583
+ updateYears = function () {
584
+ var yearsView = widget.find('.datepicker-years'),
585
+ yearsViewHeader = yearsView.find('th'),
586
+ startYear = viewDate.clone().subtract(5, 'y'),
587
+ endYear = viewDate.clone().add(6, 'y'),
588
+ html = '';
589
+
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');
593
+
594
+ yearsView.find('.disabled').removeClass('disabled');
595
+
596
+ if (options.minDate && options.minDate.isAfter(startYear, 'y')) {
597
+ yearsViewHeader.eq(0).addClass('disabled');
598
+ }
599
+
600
+ yearsViewHeader.eq(1).text(startYear.year() + '-' + endYear.year());
601
+
602
+ if (options.maxDate && options.maxDate.isBefore(endYear, 'y')) {
603
+ yearsViewHeader.eq(2).addClass('disabled');
604
+ }
605
+
606
+ while (!startYear.isAfter(endYear, 'y')) {
607
+ html += '<span data-action="selectYear" class="year' + (startYear.isSame(date, 'y') && !unset ? ' active' : '') + (!isValid(startYear, 'y') ? ' disabled' : '') + '">' + startYear.year() + '</span>';
608
+ startYear.add(1, 'y');
609
+ }
610
+
611
+ yearsView.find('td').html(html);
612
+ },
613
+
614
+ updateDecades = function () {
615
+ var decadesView = widget.find('.datepicker-decades'),
616
+ decadesViewHeader = decadesView.find('th'),
617
+ startDecade = viewDate.isBefore(moment({y: 1999})) ? moment({y: 1899}) : moment({y: 1999}),
618
+ endDecade = startDecade.clone().add(100, 'y'),
619
+ html = '';
620
+
621
+ decadesViewHeader.eq(0).find('span').attr('title', 'Previous Century');
622
+ decadesViewHeader.eq(2).find('span').attr('title', 'Next Century');
623
+
624
+ decadesView.find('.disabled').removeClass('disabled');
625
+
626
+ if (startDecade.isSame(moment({y: 1900})) || (options.minDate && options.minDate.isAfter(startDecade, 'y'))) {
627
+ decadesViewHeader.eq(0).addClass('disabled');
628
+ }
629
+
630
+ decadesViewHeader.eq(1).text(startDecade.year() + '-' + endDecade.year());
631
+
632
+ if (startDecade.isSame(moment({y: 2000})) || (options.maxDate && options.maxDate.isBefore(endDecade, 'y'))) {
633
+ decadesViewHeader.eq(2).addClass('disabled');
634
+ }
635
+
636
+ 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>';
639
+ startDecade.add(12, 'y');
640
+ }
641
+ html += '<span></span><span></span><span></span>'; //push the dangling block over, at least this way it's even
642
+
643
+ decadesView.find('td').html(html);
644
+ },
645
+
646
+ fillDate = function () {
647
+ var daysView = widget.find('.datepicker-days'),
648
+ daysViewHeader = daysView.find('th'),
649
+ currentDate,
650
+ html = [],
651
+ row,
652
+ clsName,
653
+ i;
654
+
655
+ if (!hasDate()) {
656
+ return;
657
+ }
658
+
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');
662
+
663
+ daysView.find('.disabled').removeClass('disabled');
664
+ daysViewHeader.eq(1).text(viewDate.format(options.dayViewHeaderFormat));
665
+
666
+ if (!isValid(viewDate.clone().subtract(1, 'M'), 'M')) {
667
+ daysViewHeader.eq(0).addClass('disabled');
668
+ }
669
+ if (!isValid(viewDate.clone().add(1, 'M'), 'M')) {
670
+ daysViewHeader.eq(2).addClass('disabled');
671
+ }
672
+
673
+ currentDate = viewDate.clone().startOf('M').startOf('w').startOf('d');
674
+
675
+ for (i = 0; i < 42; i++) { //always display 42 days (should show 6 weeks)
676
+ if (currentDate.weekday() === 0) {
677
+ row = $('<tr>');
678
+ if (options.calendarWeeks) {
679
+ row.append('<td class="cw">' + currentDate.week() + '</td>');
680
+ }
681
+ html.push(row);
682
+ }
683
+ clsName = '';
684
+ if (currentDate.isBefore(viewDate, 'M')) {
685
+ clsName += ' old';
686
+ }
687
+ if (currentDate.isAfter(viewDate, 'M')) {
688
+ clsName += ' new';
689
+ }
690
+ if (currentDate.isSame(date, 'd') && !unset) {
691
+ clsName += ' active';
692
+ }
693
+ if (!isValid(currentDate, 'd')) {
694
+ clsName += ' disabled';
695
+ }
696
+ if (currentDate.isSame(moment(), 'd')) {
697
+ clsName += ' today';
698
+ }
699
+ if (currentDate.day() === 0 || currentDate.day() === 6) {
700
+ clsName += ' weekend';
701
+ }
702
+ row.append('<td data-action="selectDay" data-day="' + currentDate.format('L') + '" class="day' + clsName + '">' + currentDate.date() + '</td>');
703
+ currentDate.add(1, 'd');
704
+ }
705
+
706
+ daysView.find('tbody').empty().append(html);
707
+
708
+ updateMonths();
709
+
710
+ updateYears();
711
+
712
+ updateDecades();
713
+ },
714
+
715
+ fillHours = function () {
716
+ var table = widget.find('.timepicker-hours table'),
717
+ currentHour = viewDate.clone().startOf('d'),
718
+ html = [],
719
+ row = $('<tr>');
720
+
721
+ if (viewDate.hour() > 11 && !use24Hours) {
722
+ currentHour.hour(12);
723
+ }
724
+ while (currentHour.isSame(viewDate, 'd') && (use24Hours || (viewDate.hour() < 12 && currentHour.hour() < 12) || viewDate.hour() > 11)) {
725
+ if (currentHour.hour() % 4 === 0) {
726
+ row = $('<tr>');
727
+ html.push(row);
728
+ }
729
+ row.append('<td data-action="selectHour" class="hour' + (!isValid(currentHour, 'h') ? ' disabled' : '') + '">' + currentHour.format(use24Hours ? 'HH' : 'hh') + '</td>');
730
+ currentHour.add(1, 'h');
731
+ }
732
+ table.empty().append(html);
733
+ },
734
+
735
+ fillMinutes = function () {
736
+ var table = widget.find('.timepicker-minutes table'),
737
+ currentMinute = viewDate.clone().startOf('h'),
738
+ html = [],
739
+ row = $('<tr>'),
740
+ step = options.stepping === 1 ? 5 : options.stepping;
741
+
742
+ while (viewDate.isSame(currentMinute, 'h')) {
743
+ if (currentMinute.minute() % (step * 4) === 0) {
744
+ row = $('<tr>');
745
+ html.push(row);
746
+ }
747
+ row.append('<td data-action="selectMinute" class="minute' + (!isValid(currentMinute, 'm') ? ' disabled' : '') + '">' + currentMinute.format('mm') + '</td>');
748
+ currentMinute.add(step, 'm');
749
+ }
750
+ table.empty().append(html);
751
+ },
752
+
753
+ fillSeconds = function () {
754
+ var table = widget.find('.timepicker-seconds table'),
755
+ currentSecond = viewDate.clone().startOf('m'),
756
+ html = [],
757
+ row = $('<tr>');
758
+
759
+ while (viewDate.isSame(currentSecond, 'm')) {
760
+ if (currentSecond.second() % 20 === 0) {
761
+ row = $('<tr>');
762
+ html.push(row);
763
+ }
764
+ row.append('<td data-action="selectSecond" class="second' + (!isValid(currentSecond, 's') ? ' disabled' : '') + '">' + currentSecond.format('ss') + '</td>');
765
+ currentSecond.add(5, 's');
766
+ }
767
+
768
+ table.empty().append(html);
769
+ },
770
+
771
+ fillTime = function () {
772
+ var toggle, newDate, timeComponents = widget.find('.timepicker span[data-time-component]');
773
+
774
+ if (!use24Hours) {
775
+ toggle = widget.find('.timepicker [data-action=togglePeriod]');
776
+ newDate = date.clone().add((date.hours() >= 12) ? -12 : 12, 'h');
777
+
778
+ toggle.text(date.format('A'));
779
+
780
+ if (isValid(newDate, 'h')) {
781
+ toggle.removeClass('disabled');
782
+ } else {
783
+ toggle.addClass('disabled');
784
+ }
785
+ }
786
+ timeComponents.filter('[data-time-component=hours]').text(date.format(use24Hours ? 'HH' : 'hh'));
787
+ timeComponents.filter('[data-time-component=minutes]').text(date.format('mm'));
788
+ timeComponents.filter('[data-time-component=seconds]').text(date.format('ss'));
789
+
790
+ fillHours();
791
+ fillMinutes();
792
+ fillSeconds();
793
+ },
794
+
795
+ update = function () {
796
+ if (!widget) {
797
+ return;
798
+ }
799
+ fillDate();
800
+ fillTime();
801
+ },
802
+
803
+ setValue = function (targetMoment) {
804
+ var oldDate = unset ? null : date;
805
+
806
+ // case of calling setValue(null or false)
807
+ if (!targetMoment) {
808
+ unset = true;
809
+ input.val('');
810
+ element.data('date', '');
811
+ notifyEvent({
812
+ type: 'dp.change',
813
+ date: false,
814
+ oldDate: oldDate
815
+ });
816
+ update();
817
+ return;
818
+ }
819
+
820
+ targetMoment = targetMoment.clone().locale(options.locale);
821
+
822
+ if (options.stepping !== 1) {
823
+ targetMoment.minutes((Math.round(targetMoment.minutes() / options.stepping) * options.stepping) % 60).seconds(0);
824
+ }
825
+
826
+ if (isValid(targetMoment)) {
827
+ date = targetMoment;
828
+ viewDate = date.clone();
829
+ input.val(date.format(actualFormat));
830
+ element.data('date', date.format(actualFormat));
831
+ unset = false;
832
+ update();
833
+ notifyEvent({
834
+ type: 'dp.change',
835
+ date: date.clone(),
836
+ oldDate: oldDate
837
+ });
838
+ } else {
839
+ if (!options.keepInvalid) {
840
+ input.val(unset ? '' : date.format(actualFormat));
841
+ }
842
+ notifyEvent({
843
+ type: 'dp.error',
844
+ date: targetMoment
845
+ });
846
+ }
847
+ },
848
+
849
+ hide = function () {
850
+ ///<summary>Hides the widget. Possibly will emit dp.hide</summary>
851
+ var transitioning = false;
852
+ if (!widget) {
853
+ return picker;
854
+ }
855
+ // Ignore event if in the middle of a picker transition
856
+ widget.find('.collapse').each(function () {
857
+ var collapseData = $(this).data('collapse');
858
+ if (collapseData && collapseData.transitioning) {
859
+ transitioning = true;
860
+ return false;
861
+ }
862
+ return true;
863
+ });
864
+ if (transitioning) {
865
+ return picker;
866
+ }
867
+ if (component && component.hasClass('btn')) {
868
+ component.toggleClass('active');
869
+ }
870
+ widget.hide();
871
+
872
+ $(window).off('resize', place);
873
+ widget.off('click', '[data-action]');
874
+ widget.off('mousedown', false);
875
+
876
+ widget.remove();
877
+ widget = false;
878
+
879
+ notifyEvent({
880
+ type: 'dp.hide',
881
+ date: date.clone()
882
+ });
883
+ return picker;
884
+ },
885
+
886
+ clear = function () {
887
+ setValue(null);
888
+ },
889
+
890
+ /********************************************************************************
891
+ *
892
+ * Widget UI interaction functions
893
+ *
894
+ ********************************************************************************/
895
+ actions = {
896
+ next: function () {
897
+ var navFnc = datePickerModes[currentViewMode].navFnc;
898
+ viewDate.add(datePickerModes[currentViewMode].navStep, navFnc);
899
+ fillDate();
900
+ viewUpdate(navFnc);
901
+ },
902
+
903
+ previous: function () {
904
+ var navFnc = datePickerModes[currentViewMode].navFnc;
905
+ viewDate.subtract(datePickerModes[currentViewMode].navStep, navFnc);
906
+ fillDate();
907
+ viewUpdate(navFnc);
908
+ },
909
+
910
+ pickerSwitch: function () {
911
+ showMode(1);
912
+ },
913
+
914
+ selectMonth: function (e) {
915
+ var month = $(e.target).closest('tbody').find('span').index($(e.target));
916
+ viewDate.month(month);
917
+ if (currentViewMode === minViewModeNumber) {
918
+ setValue(date.clone().year(viewDate.year()).month(viewDate.month()));
919
+ if (!options.inline) {
920
+ hide();
921
+ }
922
+ } else {
923
+ showMode(-1);
924
+ fillDate();
925
+ }
926
+ viewUpdate('M');
927
+ },
928
+
929
+ selectYear: function (e) {
930
+ var year = parseInt($(e.target).text(), 10) || 0;
931
+ viewDate.year(year);
932
+ if (currentViewMode === minViewModeNumber) {
933
+ setValue(date.clone().year(viewDate.year()));
934
+ if (!options.inline) {
935
+ hide();
936
+ }
937
+ } else {
938
+ showMode(-1);
939
+ fillDate();
940
+ }
941
+ viewUpdate('YYYY');
942
+ },
943
+
944
+ selectDecade: function (e) {
945
+ var year = parseInt($(e.target).data('selection'), 10) || 0;
946
+ viewDate.year(year);
947
+ if (currentViewMode === minViewModeNumber) {
948
+ setValue(date.clone().year(viewDate.year()));
949
+ if (!options.inline) {
950
+ hide();
951
+ }
952
+ } else {
953
+ showMode(-1);
954
+ fillDate();
955
+ }
956
+ viewUpdate('YYYY');
957
+ },
958
+
959
+ selectDay: function (e) {
960
+ var day = viewDate.clone();
961
+ if ($(e.target).is('.old')) {
962
+ day.subtract(1, 'M');
963
+ }
964
+ if ($(e.target).is('.new')) {
965
+ day.add(1, 'M');
966
+ }
967
+ setValue(day.date(parseInt($(e.target).text(), 10)));
968
+ if (!hasTime() && !options.keepOpen && !options.inline) {
969
+ hide();
970
+ }
971
+ },
972
+
973
+ incrementHours: function () {
974
+ var newDate = date.clone().add(1, 'h');
975
+ if (isValid(newDate, 'h')) {
976
+ setValue(newDate);
977
+ }
978
+ },
979
+
980
+ incrementMinutes: function () {
981
+ var newDate = date.clone().add(options.stepping, 'm');
982
+ if (isValid(newDate, 'm')) {
983
+ setValue(newDate);
984
+ }
985
+ },
986
+
987
+ incrementSeconds: function () {
988
+ var newDate = date.clone().add(1, 's');
989
+ if (isValid(newDate, 's')) {
990
+ setValue(newDate);
991
+ }
992
+ },
993
+
994
+ decrementHours: function () {
995
+ var newDate = date.clone().subtract(1, 'h');
996
+ if (isValid(newDate, 'h')) {
997
+ setValue(newDate);
998
+ }
999
+ },
1000
+
1001
+ decrementMinutes: function () {
1002
+ var newDate = date.clone().subtract(options.stepping, 'm');
1003
+ if (isValid(newDate, 'm')) {
1004
+ setValue(newDate);
1005
+ }
1006
+ },
1007
+
1008
+ decrementSeconds: function () {
1009
+ var newDate = date.clone().subtract(1, 's');
1010
+ if (isValid(newDate, 's')) {
1011
+ setValue(newDate);
1012
+ }
1013
+ },
1014
+
1015
+ togglePeriod: function () {
1016
+ setValue(date.clone().add((date.hours() >= 12) ? -12 : 12, 'h'));
1017
+ },
1018
+
1019
+ togglePicker: function (e) {
1020
+ var $this = $(e.target),
1021
+ $parent = $this.closest('ul'),
1022
+ expanded = $parent.find('.in'),
1023
+ closed = $parent.find('.collapse:not(.in)'),
1024
+ collapseData;
1025
+
1026
+ if (expanded && expanded.length) {
1027
+ collapseData = expanded.data('collapse');
1028
+ if (collapseData && collapseData.transitioning) {
1029
+ return;
1030
+ }
1031
+ if (expanded.collapse) { // if collapse plugin is available through bootstrap.js then use it
1032
+ expanded.collapse('hide');
1033
+ closed.collapse('show');
1034
+ } else { // otherwise just toggle in class on the two views
1035
+ expanded.removeClass('in');
1036
+ closed.addClass('in');
1037
+ }
1038
+ if ($this.is('span')) {
1039
+ $this.toggleClass(options.icons.time + ' ' + options.icons.date);
1040
+ } else {
1041
+ $this.find('span').toggleClass(options.icons.time + ' ' + options.icons.date);
1042
+ }
1043
+
1044
+ // NOTE: uncomment if toggled state will be restored in show()
1045
+ //if (component) {
1046
+ // component.find('span').toggleClass(options.icons.time + ' ' + options.icons.date);
1047
+ //}
1048
+ }
1049
+ },
1050
+
1051
+ showPicker: function () {
1052
+ widget.find('.timepicker > div:not(.timepicker-picker)').hide();
1053
+ widget.find('.timepicker .timepicker-picker').show();
1054
+ },
1055
+
1056
+ showHours: function () {
1057
+ widget.find('.timepicker .timepicker-picker').hide();
1058
+ widget.find('.timepicker .timepicker-hours').show();
1059
+ },
1060
+
1061
+ showMinutes: function () {
1062
+ widget.find('.timepicker .timepicker-picker').hide();
1063
+ widget.find('.timepicker .timepicker-minutes').show();
1064
+ },
1065
+
1066
+ showSeconds: function () {
1067
+ widget.find('.timepicker .timepicker-picker').hide();
1068
+ widget.find('.timepicker .timepicker-seconds').show();
1069
+ },
1070
+
1071
+ selectHour: function (e) {
1072
+ var hour = parseInt($(e.target).text(), 10);
1073
+
1074
+ if (!use24Hours) {
1075
+ if (date.hours() >= 12) {
1076
+ if (hour !== 12) {
1077
+ hour += 12;
1078
+ }
1079
+ } else {
1080
+ if (hour === 12) {
1081
+ hour = 0;
1082
+ }
1083
+ }
1084
+ }
1085
+ setValue(date.clone().hours(hour));
1086
+ actions.showPicker.call(picker);
1087
+ },
1088
+
1089
+ selectMinute: function (e) {
1090
+ setValue(date.clone().minutes(parseInt($(e.target).text(), 10)));
1091
+ actions.showPicker.call(picker);
1092
+ },
1093
+
1094
+ selectSecond: function (e) {
1095
+ setValue(date.clone().seconds(parseInt($(e.target).text(), 10)));
1096
+ actions.showPicker.call(picker);
1097
+ },
1098
+
1099
+ clear: clear,
1100
+
1101
+ today: function () {
1102
+ if (isValid(moment(), 'd')) {
1103
+ setValue(moment());
1104
+ }
1105
+ },
1106
+
1107
+ close: hide
1108
+ },
1109
+
1110
+ doAction = function (e) {
1111
+ if ($(e.currentTarget).is('.disabled')) {
1112
+ return false;
1113
+ }
1114
+ actions[$(e.currentTarget).data('action')].apply(picker, arguments);
1115
+ return false;
1116
+ },
1117
+
1118
+ show = function () {
1119
+ ///<summary>Shows the widget. Possibly will emit dp.show and dp.change</summary>
1120
+ var currentMoment,
1121
+ useCurrentGranularity = {
1122
+ 'year': function (m) {
1123
+ return m.month(0).date(1).hours(0).seconds(0).minutes(0);
1124
+ },
1125
+ 'month': function (m) {
1126
+ return m.date(1).hours(0).seconds(0).minutes(0);
1127
+ },
1128
+ 'day': function (m) {
1129
+ return m.hours(0).seconds(0).minutes(0);
1130
+ },
1131
+ 'hour': function (m) {
1132
+ return m.seconds(0).minutes(0);
1133
+ },
1134
+ 'minute': function (m) {
1135
+ return m.seconds(0);
1136
+ }
1137
+ };
1138
+
1139
+ if (input.prop('disabled') || (!options.ignoreReadonly && input.prop('readonly')) || widget) {
1140
+ return picker;
1141
+ }
1142
+ if (input.val() !== undefined && input.val().trim().length !== 0) {
1143
+ setValue(parseInputDate(input.val().trim()));
1144
+ } else if (options.useCurrent && unset && ((input.is('input') && input.val().trim().length === 0) || options.inline)) {
1145
+ currentMoment = moment();
1146
+ if (typeof options.useCurrent === 'string') {
1147
+ currentMoment = useCurrentGranularity[options.useCurrent](currentMoment);
1148
+ }
1149
+ setValue(currentMoment);
1150
+ }
1151
+
1152
+ widget = getTemplate();
1153
+
1154
+ fillDow();
1155
+ fillMonths();
1156
+
1157
+ widget.find('.timepicker-hours').hide();
1158
+ widget.find('.timepicker-minutes').hide();
1159
+ widget.find('.timepicker-seconds').hide();
1160
+
1161
+ update();
1162
+ showMode();
1163
+
1164
+ $(window).on('resize', place);
1165
+ widget.on('click', '[data-action]', doAction); // this handles clicks on the widget
1166
+ widget.on('mousedown', false);
1167
+
1168
+ if (component && component.hasClass('btn')) {
1169
+ component.toggleClass('active');
1170
+ }
1171
+ widget.show();
1172
+ place();
1173
+
1174
+ if (options.focusOnShow && !input.is(':focus')) {
1175
+ input.focus();
1176
+ }
1177
+
1178
+ notifyEvent({
1179
+ type: 'dp.show'
1180
+ });
1181
+ return picker;
1182
+ },
1183
+
1184
+ toggle = function () {
1185
+ /// <summary>Shows or hides the widget</summary>
1186
+ return (widget ? hide() : show());
1187
+ },
1188
+
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
+ keydown = function (e) {
1204
+ var handler = null,
1205
+ index,
1206
+ index2,
1207
+ pressedKeys = [],
1208
+ pressedModifiers = {},
1209
+ currentKey = e.which,
1210
+ keyBindKeys,
1211
+ allModifiersPressed,
1212
+ pressed = 'p';
1213
+
1214
+ keyState[currentKey] = pressed;
1215
+
1216
+ for (index in keyState) {
1217
+ if (keyState.hasOwnProperty(index) && keyState[index] === pressed) {
1218
+ pressedKeys.push(index);
1219
+ if (parseInt(index, 10) !== currentKey) {
1220
+ pressedModifiers[index] = true;
1221
+ }
1222
+ }
1223
+ }
1224
+
1225
+ for (index in options.keyBinds) {
1226
+ if (options.keyBinds.hasOwnProperty(index) && typeof (options.keyBinds[index]) === 'function') {
1227
+ keyBindKeys = index.split(' ');
1228
+ if (keyBindKeys.length === pressedKeys.length && keyMap[currentKey] === keyBindKeys[keyBindKeys.length - 1]) {
1229
+ allModifiersPressed = true;
1230
+ for (index2 = keyBindKeys.length - 2; index2 >= 0; index2--) {
1231
+ if (!(keyMap[keyBindKeys[index2]] in pressedModifiers)) {
1232
+ allModifiersPressed = false;
1233
+ break;
1234
+ }
1235
+ }
1236
+ if (allModifiersPressed) {
1237
+ handler = options.keyBinds[index];
1238
+ break;
1239
+ }
1240
+ }
1241
+ }
1242
+ }
1243
+
1244
+ if (handler) {
1245
+ handler.call(picker, widget);
1246
+ e.stopPropagation();
1247
+ e.preventDefault();
1248
+ }
1249
+ },
1250
+
1251
+ keyup = function (e) {
1252
+ keyState[e.which] = 'r';
1253
+ e.stopPropagation();
1254
+ e.preventDefault();
1255
+ },
1256
+
1257
+ change = function (e) {
1258
+ var val = $(e.target).val().trim(),
1259
+ parsedDate = val ? parseInputDate(val) : null;
1260
+ setValue(parsedDate);
1261
+ e.stopImmediatePropagation();
1262
+ return false;
1263
+ },
1264
+
1265
+ attachDatePickerElementEvents = function () {
1266
+ input.on({
1267
+ 'change': change,
1268
+ 'blur': options.debug ? '' : hide,
1269
+ 'keydown': keydown,
1270
+ 'keyup': keyup,
1271
+ 'focus': options.allowInputToggle ? show : ''
1272
+ });
1273
+
1274
+ if (element.is('input')) {
1275
+ input.on({
1276
+ 'focus': show
1277
+ });
1278
+ } else if (component) {
1279
+ component.on('click', toggle);
1280
+ component.on('mousedown', false);
1281
+ }
1282
+ },
1283
+
1284
+ detachDatePickerElementEvents = function () {
1285
+ input.off({
1286
+ 'change': change,
1287
+ 'blur': hide,
1288
+ 'keydown': keydown,
1289
+ 'keyup': keyup,
1290
+ 'focus': options.allowInputToggle ? hide : ''
1291
+ });
1292
+
1293
+ if (element.is('input')) {
1294
+ input.off({
1295
+ 'focus': show
1296
+ });
1297
+ } else if (component) {
1298
+ component.off('click', toggle);
1299
+ component.off('mousedown', false);
1300
+ }
1301
+ },
1302
+
1303
+ indexGivenDates = function (givenDatesArray) {
1304
+ // Store given enabledDates and disabledDates as keys.
1305
+ // This way we can check their existence in O(1) time instead of looping through whole array.
1306
+ // (for example: options.enabledDates['2014-02-27'] === true)
1307
+ var givenDatesIndexed = {};
1308
+ $.each(givenDatesArray, function () {
1309
+ var dDate = parseInputDate(this);
1310
+ if (dDate.isValid()) {
1311
+ givenDatesIndexed[dDate.format('YYYY-MM-DD')] = true;
1312
+ }
1313
+ });
1314
+ return (Object.keys(givenDatesIndexed).length) ? givenDatesIndexed : false;
1315
+ },
1316
+
1317
+ indexGivenHours = function (givenHoursArray) {
1318
+ // Store given enabledHours and disabledHours as keys.
1319
+ // This way we can check their existence in O(1) time instead of looping through whole array.
1320
+ // (for example: options.enabledHours['2014-02-27'] === true)
1321
+ var givenHoursIndexed = {};
1322
+ $.each(givenHoursArray, function () {
1323
+ givenHoursIndexed[this] = true;
1324
+ });
1325
+ return (Object.keys(givenHoursIndexed).length) ? givenHoursIndexed : false;
1326
+ },
1327
+
1328
+ initFormatting = function () {
1329
+ var format = options.format || 'L LT';
1330
+
1331
+ actualFormat = format.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput) {
1332
+ var newinput = date.localeData().longDateFormat(formatInput) || formatInput;
1333
+ return newinput.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput2) { //temp fix for #740
1334
+ return date.localeData().longDateFormat(formatInput2) || formatInput2;
1335
+ });
1336
+ });
1337
+
1338
+
1339
+ parseFormats = options.extraFormats ? options.extraFormats.slice() : [];
1340
+ if (parseFormats.indexOf(format) < 0 && parseFormats.indexOf(actualFormat) < 0) {
1341
+ parseFormats.push(actualFormat);
1342
+ }
1343
+
1344
+ use24Hours = (actualFormat.toLowerCase().indexOf('a') < 1 && actualFormat.replace(/\[.*?\]/g, '').indexOf('h') < 1);
1345
+
1346
+ if (isEnabled('y')) {
1347
+ minViewModeNumber = 2;
1348
+ }
1349
+ if (isEnabled('M')) {
1350
+ minViewModeNumber = 1;
1351
+ }
1352
+ if (isEnabled('d')) {
1353
+ minViewModeNumber = 0;
1354
+ }
1355
+
1356
+ currentViewMode = Math.max(minViewModeNumber, currentViewMode);
1357
+
1358
+ if (!unset) {
1359
+ setValue(date);
1360
+ }
1361
+ };
1362
+
1363
+ /********************************************************************************
1364
+ *
1365
+ * Public API functions
1366
+ * =====================
1367
+ *
1368
+ * Important: Do not expose direct references to private objects or the options
1369
+ * object to the outer world. Always return a clone when returning values or make
1370
+ * a clone when setting a private variable.
1371
+ *
1372
+ ********************************************************************************/
1373
+ picker.destroy = function () {
1374
+ ///<summary>Destroys the widget and removes all attached event listeners</summary>
1375
+ hide();
1376
+ detachDatePickerElementEvents();
1377
+ element.removeData('DateTimePicker');
1378
+ element.removeData('date');
1379
+ };
1380
+
1381
+ picker.toggle = toggle;
1382
+
1383
+ picker.show = show;
1384
+
1385
+ picker.hide = hide;
1386
+
1387
+ picker.disable = function () {
1388
+ ///<summary>Disables the input element, the component is attached to, by adding a disabled="true" attribute to it.
1389
+ ///If the widget was visible before that call it is hidden. Possibly emits dp.hide</summary>
1390
+ hide();
1391
+ if (component && component.hasClass('btn')) {
1392
+ component.addClass('disabled');
1393
+ }
1394
+ input.prop('disabled', true);
1395
+ return picker;
1396
+ };
1397
+
1398
+ picker.enable = function () {
1399
+ ///<summary>Enables the input element, the component is attached to, by removing disabled attribute from it.</summary>
1400
+ if (component && component.hasClass('btn')) {
1401
+ component.removeClass('disabled');
1402
+ }
1403
+ input.prop('disabled', false);
1404
+ return picker;
1405
+ };
1406
+
1407
+ picker.ignoreReadonly = function (ignoreReadonly) {
1408
+ if (arguments.length === 0) {
1409
+ return options.ignoreReadonly;
1410
+ }
1411
+ if (typeof ignoreReadonly !== 'boolean') {
1412
+ throw new TypeError('ignoreReadonly () expects a boolean parameter');
1413
+ }
1414
+ options.ignoreReadonly = ignoreReadonly;
1415
+ return picker;
1416
+ };
1417
+
1418
+ picker.options = function (newOptions) {
1419
+ if (arguments.length === 0) {
1420
+ return $.extend(true, {}, options);
1421
+ }
1422
+
1423
+ if (!(newOptions instanceof Object)) {
1424
+ throw new TypeError('options() options parameter should be an object');
1425
+ }
1426
+ $.extend(true, options, newOptions);
1427
+ $.each(options, function (key, value) {
1428
+ if (picker[key] !== undefined) {
1429
+ picker[key](value);
1430
+ } else {
1431
+ throw new TypeError('option ' + key + ' is not recognized!');
1432
+ }
1433
+ });
1434
+ return picker;
1435
+ };
1436
+
1437
+ picker.date = function (newDate) {
1438
+ ///<signature helpKeyword="$.fn.datetimepicker.date">
1439
+ ///<summary>Returns the component's model current date, a moment object or null if not set.</summary>
1440
+ ///<returns type="Moment">date.clone()</returns>
1441
+ ///</signature>
1442
+ ///<signature>
1443
+ ///<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>
1444
+ ///<param name="newDate" locid="$.fn.datetimepicker.date_p:newDate">Takes string, Date, moment, null parameter.</param>
1445
+ ///</signature>
1446
+ if (arguments.length === 0) {
1447
+ if (unset) {
1448
+ return null;
1449
+ }
1450
+ return date.clone();
1451
+ }
1452
+
1453
+ if (newDate !== null && typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) {
1454
+ throw new TypeError('date() parameter must be one of [null, string, moment or Date]');
1455
+ }
1456
+
1457
+ setValue(newDate === null ? null : parseInputDate(newDate));
1458
+ return picker;
1459
+ };
1460
+
1461
+ picker.format = function (newFormat) {
1462
+ ///<summary>test su</summary>
1463
+ ///<param name="newFormat">info about para</param>
1464
+ ///<returns type="string|boolean">returns foo</returns>
1465
+ if (arguments.length === 0) {
1466
+ return options.format;
1467
+ }
1468
+
1469
+ if ((typeof newFormat !== 'string') && ((typeof newFormat !== 'boolean') || (newFormat !== false))) {
1470
+ throw new TypeError('format() expects a sting or boolean:false parameter ' + newFormat);
1471
+ }
1472
+
1473
+ options.format = newFormat;
1474
+ if (actualFormat) {
1475
+ initFormatting(); // reinit formatting
1476
+ }
1477
+ return picker;
1478
+ };
1479
+
1480
+ picker.dayViewHeaderFormat = function (newFormat) {
1481
+ if (arguments.length === 0) {
1482
+ return options.dayViewHeaderFormat;
1483
+ }
1484
+
1485
+ if (typeof newFormat !== 'string') {
1486
+ throw new TypeError('dayViewHeaderFormat() expects a string parameter');
1487
+ }
1488
+
1489
+ options.dayViewHeaderFormat = newFormat;
1490
+ return picker;
1491
+ };
1492
+
1493
+ picker.extraFormats = function (formats) {
1494
+ if (arguments.length === 0) {
1495
+ return options.extraFormats;
1496
+ }
1497
+
1498
+ if (formats !== false && !(formats instanceof Array)) {
1499
+ throw new TypeError('extraFormats() expects an array or false parameter');
1500
+ }
1501
+
1502
+ options.extraFormats = formats;
1503
+ if (parseFormats) {
1504
+ initFormatting(); // reinit formatting
1505
+ }
1506
+ return picker;
1507
+ };
1508
+
1509
+ picker.disabledDates = function (dates) {
1510
+ ///<signature helpKeyword="$.fn.datetimepicker.disabledDates">
1511
+ ///<summary>Returns an array with the currently set disabled dates on the component.</summary>
1512
+ ///<returns type="array">options.disabledDates</returns>
1513
+ ///</signature>
1514
+ ///<signature>
1515
+ ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
1516
+ ///options.enabledDates if such exist.</summary>
1517
+ ///<param name="dates" locid="$.fn.datetimepicker.disabledDates_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
1518
+ ///</signature>
1519
+ if (arguments.length === 0) {
1520
+ return (options.disabledDates ? $.extend({}, options.disabledDates) : options.disabledDates);
1521
+ }
1522
+
1523
+ if (!dates) {
1524
+ options.disabledDates = false;
1525
+ update();
1526
+ return picker;
1527
+ }
1528
+ if (!(dates instanceof Array)) {
1529
+ throw new TypeError('disabledDates() expects an array parameter');
1530
+ }
1531
+ options.disabledDates = indexGivenDates(dates);
1532
+ options.enabledDates = false;
1533
+ update();
1534
+ return picker;
1535
+ };
1536
+
1537
+ picker.enabledDates = function (dates) {
1538
+ ///<signature helpKeyword="$.fn.datetimepicker.enabledDates">
1539
+ ///<summary>Returns an array with the currently set enabled dates on the component.</summary>
1540
+ ///<returns type="array">options.enabledDates</returns>
1541
+ ///</signature>
1542
+ ///<signature>
1543
+ ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledDates if such exist.</summary>
1544
+ ///<param name="dates" locid="$.fn.datetimepicker.enabledDates_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
1545
+ ///</signature>
1546
+ if (arguments.length === 0) {
1547
+ return (options.enabledDates ? $.extend({}, options.enabledDates) : options.enabledDates);
1548
+ }
1549
+
1550
+ if (!dates) {
1551
+ options.enabledDates = false;
1552
+ update();
1553
+ return picker;
1554
+ }
1555
+ if (!(dates instanceof Array)) {
1556
+ throw new TypeError('enabledDates() expects an array parameter');
1557
+ }
1558
+ options.enabledDates = indexGivenDates(dates);
1559
+ options.disabledDates = false;
1560
+ update();
1561
+ return picker;
1562
+ };
1563
+
1564
+ picker.daysOfWeekDisabled = function (daysOfWeekDisabled) {
1565
+ if (arguments.length === 0) {
1566
+ return options.daysOfWeekDisabled.splice(0);
1567
+ }
1568
+
1569
+ if ((typeof daysOfWeekDisabled === 'boolean') && !daysOfWeekDisabled) {
1570
+ options.daysOfWeekDisabled = false;
1571
+ update();
1572
+ return picker;
1573
+ }
1574
+
1575
+ if (!(daysOfWeekDisabled instanceof Array)) {
1576
+ throw new TypeError('daysOfWeekDisabled() expects an array parameter');
1577
+ }
1578
+ options.daysOfWeekDisabled = daysOfWeekDisabled.reduce(function (previousValue, currentValue) {
1579
+ currentValue = parseInt(currentValue, 10);
1580
+ if (currentValue > 6 || currentValue < 0 || isNaN(currentValue)) {
1581
+ return previousValue;
1582
+ }
1583
+ if (previousValue.indexOf(currentValue) === -1) {
1584
+ previousValue.push(currentValue);
1585
+ }
1586
+ return previousValue;
1587
+ }, []).sort();
1588
+ if (options.useCurrent && !options.keepInvalid) {
1589
+ var tries = 0;
1590
+ while (!isValid(date, 'd')) {
1591
+ date.add(1, 'd');
1592
+ if (tries === 7) {
1593
+ throw 'Tried 7 times to find a valid date';
1594
+ }
1595
+ tries++;
1596
+ }
1597
+ setValue(date);
1598
+ }
1599
+ update();
1600
+ return picker;
1601
+ };
1602
+
1603
+ picker.maxDate = function (maxDate) {
1604
+ if (arguments.length === 0) {
1605
+ return options.maxDate ? options.maxDate.clone() : options.maxDate;
1606
+ }
1607
+
1608
+ if ((typeof maxDate === 'boolean') && maxDate === false) {
1609
+ options.maxDate = false;
1610
+ update();
1611
+ return picker;
1612
+ }
1613
+
1614
+ if (typeof maxDate === 'string') {
1615
+ if (maxDate === 'now' || maxDate === 'moment') {
1616
+ maxDate = moment();
1617
+ }
1618
+ }
1619
+
1620
+ var parsedDate = parseInputDate(maxDate);
1621
+
1622
+ if (!parsedDate.isValid()) {
1623
+ throw new TypeError('maxDate() Could not parse date parameter: ' + maxDate);
1624
+ }
1625
+ if (options.minDate && parsedDate.isBefore(options.minDate)) {
1626
+ throw new TypeError('maxDate() date parameter is before options.minDate: ' + parsedDate.format(actualFormat));
1627
+ }
1628
+ options.maxDate = parsedDate;
1629
+ if (options.useCurrent && !options.keepInvalid && date.isAfter(maxDate)) {
1630
+ setValue(options.maxDate);
1631
+ }
1632
+ if (viewDate.isAfter(parsedDate)) {
1633
+ viewDate = parsedDate.clone();
1634
+ }
1635
+ update();
1636
+ return picker;
1637
+ };
1638
+
1639
+ picker.minDate = function (minDate) {
1640
+ if (arguments.length === 0) {
1641
+ return options.minDate ? options.minDate.clone() : options.minDate;
1642
+ }
1643
+
1644
+ if ((typeof minDate === 'boolean') && minDate === false) {
1645
+ options.minDate = false;
1646
+ update();
1647
+ return picker;
1648
+ }
1649
+
1650
+ if (typeof minDate === 'string') {
1651
+ if (minDate === 'now' || minDate === 'moment') {
1652
+ minDate = moment();
1653
+ }
1654
+ }
1655
+
1656
+ var parsedDate = parseInputDate(minDate);
1657
+
1658
+ if (!parsedDate.isValid()) {
1659
+ throw new TypeError('minDate() Could not parse date parameter: ' + minDate);
1660
+ }
1661
+ if (options.maxDate && parsedDate.isAfter(options.maxDate)) {
1662
+ throw new TypeError('minDate() date parameter is after options.maxDate: ' + parsedDate.format(actualFormat));
1663
+ }
1664
+ options.minDate = parsedDate;
1665
+ if (options.useCurrent && !options.keepInvalid && date.isBefore(minDate)) {
1666
+ setValue(options.minDate);
1667
+ }
1668
+ if (viewDate.isBefore(parsedDate)) {
1669
+ viewDate = parsedDate.clone();
1670
+ }
1671
+ update();
1672
+ return picker;
1673
+ };
1674
+
1675
+ picker.defaultDate = function (defaultDate) {
1676
+ ///<signature helpKeyword="$.fn.datetimepicker.defaultDate">
1677
+ ///<summary>Returns a moment with the options.defaultDate option configuration or false if not set</summary>
1678
+ ///<returns type="Moment">date.clone()</returns>
1679
+ ///</signature>
1680
+ ///<signature>
1681
+ ///<summary>Will set the picker's inital date. If a boolean:false value is passed the options.defaultDate parameter is cleared.</summary>
1682
+ ///<param name="defaultDate" locid="$.fn.datetimepicker.defaultDate_p:defaultDate">Takes a string, Date, moment, boolean:false</param>
1683
+ ///</signature>
1684
+ if (arguments.length === 0) {
1685
+ return options.defaultDate ? options.defaultDate.clone() : options.defaultDate;
1686
+ }
1687
+ if (!defaultDate) {
1688
+ options.defaultDate = false;
1689
+ return picker;
1690
+ }
1691
+
1692
+ if (typeof defaultDate === 'string') {
1693
+ if (defaultDate === 'now' || defaultDate === 'moment') {
1694
+ defaultDate = moment();
1695
+ }
1696
+ }
1697
+
1698
+ var parsedDate = parseInputDate(defaultDate);
1699
+ if (!parsedDate.isValid()) {
1700
+ throw new TypeError('defaultDate() Could not parse date parameter: ' + defaultDate);
1701
+ }
1702
+ if (!isValid(parsedDate)) {
1703
+ throw new TypeError('defaultDate() date passed is invalid according to component setup validations');
1704
+ }
1705
+
1706
+ options.defaultDate = parsedDate;
1707
+
1708
+ if (options.defaultDate && options.inline || (input.val().trim() === '' && input.attr('placeholder') === undefined)) {
1709
+ setValue(options.defaultDate);
1710
+ }
1711
+ return picker;
1712
+ };
1713
+
1714
+ picker.locale = function (locale) {
1715
+ if (arguments.length === 0) {
1716
+ return options.locale;
1717
+ }
1718
+
1719
+ if (!moment.localeData(locale)) {
1720
+ throw new TypeError('locale() locale ' + locale + ' is not loaded from moment locales!');
1721
+ }
1722
+
1723
+ options.locale = locale;
1724
+ date.locale(options.locale);
1725
+ viewDate.locale(options.locale);
1726
+
1727
+ if (actualFormat) {
1728
+ initFormatting(); // reinit formatting
1729
+ }
1730
+ if (widget) {
1731
+ hide();
1732
+ show();
1733
+ }
1734
+ return picker;
1735
+ };
1736
+
1737
+ picker.stepping = function (stepping) {
1738
+ if (arguments.length === 0) {
1739
+ return options.stepping;
1740
+ }
1741
+
1742
+ stepping = parseInt(stepping, 10);
1743
+ if (isNaN(stepping) || stepping < 1) {
1744
+ stepping = 1;
1745
+ }
1746
+ options.stepping = stepping;
1747
+ return picker;
1748
+ };
1749
+
1750
+ picker.useCurrent = function (useCurrent) {
1751
+ var useCurrentOptions = ['year', 'month', 'day', 'hour', 'minute'];
1752
+ if (arguments.length === 0) {
1753
+ return options.useCurrent;
1754
+ }
1755
+
1756
+ if ((typeof useCurrent !== 'boolean') && (typeof useCurrent !== 'string')) {
1757
+ throw new TypeError('useCurrent() expects a boolean or string parameter');
1758
+ }
1759
+ if (typeof useCurrent === 'string' && useCurrentOptions.indexOf(useCurrent.toLowerCase()) === -1) {
1760
+ throw new TypeError('useCurrent() expects a string parameter of ' + useCurrentOptions.join(', '));
1761
+ }
1762
+ options.useCurrent = useCurrent;
1763
+ return picker;
1764
+ };
1765
+
1766
+ picker.collapse = function (collapse) {
1767
+ if (arguments.length === 0) {
1768
+ return options.collapse;
1769
+ }
1770
+
1771
+ if (typeof collapse !== 'boolean') {
1772
+ throw new TypeError('collapse() expects a boolean parameter');
1773
+ }
1774
+ if (options.collapse === collapse) {
1775
+ return picker;
1776
+ }
1777
+ options.collapse = collapse;
1778
+ if (widget) {
1779
+ hide();
1780
+ show();
1781
+ }
1782
+ return picker;
1783
+ };
1784
+
1785
+ picker.icons = function (icons) {
1786
+ if (arguments.length === 0) {
1787
+ return $.extend({}, options.icons);
1788
+ }
1789
+
1790
+ if (!(icons instanceof Object)) {
1791
+ throw new TypeError('icons() expects parameter to be an Object');
1792
+ }
1793
+ $.extend(options.icons, icons);
1794
+ if (widget) {
1795
+ hide();
1796
+ show();
1797
+ }
1798
+ return picker;
1799
+ };
1800
+
1801
+ picker.useStrict = function (useStrict) {
1802
+ if (arguments.length === 0) {
1803
+ return options.useStrict;
1804
+ }
1805
+
1806
+ if (typeof useStrict !== 'boolean') {
1807
+ throw new TypeError('useStrict() expects a boolean parameter');
1808
+ }
1809
+ options.useStrict = useStrict;
1810
+ return picker;
1811
+ };
1812
+
1813
+ picker.sideBySide = function (sideBySide) {
1814
+ if (arguments.length === 0) {
1815
+ return options.sideBySide;
1816
+ }
1817
+
1818
+ if (typeof sideBySide !== 'boolean') {
1819
+ throw new TypeError('sideBySide() expects a boolean parameter');
1820
+ }
1821
+ options.sideBySide = sideBySide;
1822
+ if (widget) {
1823
+ hide();
1824
+ show();
1825
+ }
1826
+ return picker;
1827
+ };
1828
+
1829
+ picker.viewMode = function (viewMode) {
1830
+ if (arguments.length === 0) {
1831
+ return options.viewMode;
1832
+ }
1833
+
1834
+ if (typeof viewMode !== 'string') {
1835
+ throw new TypeError('viewMode() expects a string parameter');
1836
+ }
1837
+
1838
+ if (viewModes.indexOf(viewMode) === -1) {
1839
+ throw new TypeError('viewMode() parameter must be one of (' + viewModes.join(', ') + ') value');
1840
+ }
1841
+
1842
+ options.viewMode = viewMode;
1843
+ currentViewMode = Math.max(viewModes.indexOf(viewMode), minViewModeNumber);
1844
+
1845
+ showMode();
1846
+ return picker;
1847
+ };
1848
+
1849
+ picker.toolbarPlacement = function (toolbarPlacement) {
1850
+ if (arguments.length === 0) {
1851
+ return options.toolbarPlacement;
1852
+ }
1853
+
1854
+ if (typeof toolbarPlacement !== 'string') {
1855
+ throw new TypeError('toolbarPlacement() expects a string parameter');
1856
+ }
1857
+ if (toolbarPlacements.indexOf(toolbarPlacement) === -1) {
1858
+ throw new TypeError('toolbarPlacement() parameter must be one of (' + toolbarPlacements.join(', ') + ') value');
1859
+ }
1860
+ options.toolbarPlacement = toolbarPlacement;
1861
+
1862
+ if (widget) {
1863
+ hide();
1864
+ show();
1865
+ }
1866
+ return picker;
1867
+ };
1868
+
1869
+ picker.widgetPositioning = function (widgetPositioning) {
1870
+ if (arguments.length === 0) {
1871
+ return $.extend({}, options.widgetPositioning);
1872
+ }
1873
+
1874
+ if (({}).toString.call(widgetPositioning) !== '[object Object]') {
1875
+ throw new TypeError('widgetPositioning() expects an object variable');
1876
+ }
1877
+ if (widgetPositioning.horizontal) {
1878
+ if (typeof widgetPositioning.horizontal !== 'string') {
1879
+ throw new TypeError('widgetPositioning() horizontal variable must be a string');
1880
+ }
1881
+ widgetPositioning.horizontal = widgetPositioning.horizontal.toLowerCase();
1882
+ if (horizontalModes.indexOf(widgetPositioning.horizontal) === -1) {
1883
+ throw new TypeError('widgetPositioning() expects horizontal parameter to be one of (' + horizontalModes.join(', ') + ')');
1884
+ }
1885
+ options.widgetPositioning.horizontal = widgetPositioning.horizontal;
1886
+ }
1887
+ if (widgetPositioning.vertical) {
1888
+ if (typeof widgetPositioning.vertical !== 'string') {
1889
+ throw new TypeError('widgetPositioning() vertical variable must be a string');
1890
+ }
1891
+ widgetPositioning.vertical = widgetPositioning.vertical.toLowerCase();
1892
+ if (verticalModes.indexOf(widgetPositioning.vertical) === -1) {
1893
+ throw new TypeError('widgetPositioning() expects vertical parameter to be one of (' + verticalModes.join(', ') + ')');
1894
+ }
1895
+ options.widgetPositioning.vertical = widgetPositioning.vertical;
1896
+ }
1897
+ update();
1898
+ return picker;
1899
+ };
1900
+
1901
+ picker.calendarWeeks = function (calendarWeeks) {
1902
+ if (arguments.length === 0) {
1903
+ return options.calendarWeeks;
1904
+ }
1905
+
1906
+ if (typeof calendarWeeks !== 'boolean') {
1907
+ throw new TypeError('calendarWeeks() expects parameter to be a boolean value');
1908
+ }
1909
+
1910
+ options.calendarWeeks = calendarWeeks;
1911
+ update();
1912
+ return picker;
1913
+ };
1914
+
1915
+ picker.showTodayButton = function (showTodayButton) {
1916
+ if (arguments.length === 0) {
1917
+ return options.showTodayButton;
1918
+ }
1919
+
1920
+ if (typeof showTodayButton !== 'boolean') {
1921
+ throw new TypeError('showTodayButton() expects a boolean parameter');
1922
+ }
1923
+
1924
+ options.showTodayButton = showTodayButton;
1925
+ if (widget) {
1926
+ hide();
1927
+ show();
1928
+ }
1929
+ return picker;
1930
+ };
1931
+
1932
+ picker.showClear = function (showClear) {
1933
+ if (arguments.length === 0) {
1934
+ return options.showClear;
1935
+ }
1936
+
1937
+ if (typeof showClear !== 'boolean') {
1938
+ throw new TypeError('showClear() expects a boolean parameter');
1939
+ }
1940
+
1941
+ options.showClear = showClear;
1942
+ if (widget) {
1943
+ hide();
1944
+ show();
1945
+ }
1946
+ return picker;
1947
+ };
1948
+
1949
+ picker.widgetParent = function (widgetParent) {
1950
+ if (arguments.length === 0) {
1951
+ return options.widgetParent;
1952
+ }
1953
+
1954
+ if (typeof widgetParent === 'string') {
1955
+ widgetParent = $(widgetParent);
1956
+ }
1957
+
1958
+ if (widgetParent !== null && (typeof widgetParent !== 'string' && !(widgetParent instanceof $))) {
1959
+ throw new TypeError('widgetParent() expects a string or a jQuery object parameter');
1960
+ }
1961
+
1962
+ options.widgetParent = widgetParent;
1963
+ if (widget) {
1964
+ hide();
1965
+ show();
1966
+ }
1967
+ return picker;
1968
+ };
1969
+
1970
+ picker.keepOpen = function (keepOpen) {
1971
+ if (arguments.length === 0) {
1972
+ return options.keepOpen;
1973
+ }
1974
+
1975
+ if (typeof keepOpen !== 'boolean') {
1976
+ throw new TypeError('keepOpen() expects a boolean parameter');
1977
+ }
1978
+
1979
+ options.keepOpen = keepOpen;
1980
+ return picker;
1981
+ };
1982
+
1983
+ picker.focusOnShow = function (focusOnShow) {
1984
+ if (arguments.length === 0) {
1985
+ return options.focusOnShow;
1986
+ }
1987
+
1988
+ if (typeof focusOnShow !== 'boolean') {
1989
+ throw new TypeError('focusOnShow() expects a boolean parameter');
1990
+ }
1991
+
1992
+ options.focusOnShow = focusOnShow;
1993
+ return picker;
1994
+ };
1995
+
1996
+ picker.inline = function (inline) {
1997
+ if (arguments.length === 0) {
1998
+ return options.inline;
1999
+ }
2000
+
2001
+ if (typeof inline !== 'boolean') {
2002
+ throw new TypeError('inline() expects a boolean parameter');
2003
+ }
2004
+
2005
+ options.inline = inline;
2006
+ return picker;
2007
+ };
2008
+
2009
+ picker.clear = function () {
2010
+ clear();
2011
+ return picker;
2012
+ };
2013
+
2014
+ picker.keyBinds = function (keyBinds) {
2015
+ options.keyBinds = keyBinds;
2016
+ return picker;
2017
+ };
2018
+
2019
+ picker.debug = function (debug) {
2020
+ if (typeof debug !== 'boolean') {
2021
+ throw new TypeError('debug() expects a boolean parameter');
2022
+ }
2023
+
2024
+ options.debug = debug;
2025
+ return picker;
2026
+ };
2027
+
2028
+ picker.allowInputToggle = function (allowInputToggle) {
2029
+ if (arguments.length === 0) {
2030
+ return options.allowInputToggle;
2031
+ }
2032
+
2033
+ if (typeof allowInputToggle !== 'boolean') {
2034
+ throw new TypeError('allowInputToggle() expects a boolean parameter');
2035
+ }
2036
+
2037
+ options.allowInputToggle = allowInputToggle;
2038
+ return picker;
2039
+ };
2040
+
2041
+ picker.showClose = function (showClose) {
2042
+ if (arguments.length === 0) {
2043
+ return options.showClose;
2044
+ }
2045
+
2046
+ if (typeof showClose !== 'boolean') {
2047
+ throw new TypeError('showClose() expects a boolean parameter');
2048
+ }
2049
+
2050
+ options.showClose = showClose;
2051
+ return picker;
2052
+ };
2053
+
2054
+ picker.keepInvalid = function (keepInvalid) {
2055
+ if (arguments.length === 0) {
2056
+ return options.keepInvalid;
2057
+ }
2058
+
2059
+ if (typeof keepInvalid !== 'boolean') {
2060
+ throw new TypeError('keepInvalid() expects a boolean parameter');
2061
+ }
2062
+ options.keepInvalid = keepInvalid;
2063
+ return picker;
2064
+ };
2065
+
2066
+ picker.datepickerInput = function (datepickerInput) {
2067
+ if (arguments.length === 0) {
2068
+ return options.datepickerInput;
2069
+ }
2070
+
2071
+ if (typeof datepickerInput !== 'string') {
2072
+ throw new TypeError('datepickerInput() expects a string parameter');
2073
+ }
2074
+
2075
+ options.datepickerInput = datepickerInput;
2076
+ return picker;
2077
+ };
2078
+
2079
+ picker.parseInputDate = function (parseInputDate) {
2080
+ if (arguments.length === 0) {
2081
+ return options.parseInputDate;
2082
+ }
2083
+
2084
+ if (typeof parseInputDate !== 'function') {
2085
+ throw new TypeError('parseInputDate() sholud be as function');
2086
+ }
2087
+
2088
+ options.parseInputDate = parseInputDate;
2089
+
2090
+ return picker;
2091
+ };
2092
+
2093
+ picker.disabledTimeIntervals = function (disabledTimeIntervals) {
2094
+ ///<signature helpKeyword="$.fn.datetimepicker.disabledTimeIntervals">
2095
+ ///<summary>Returns an array with the currently set disabled dates on the component.</summary>
2096
+ ///<returns type="array">options.disabledTimeIntervals</returns>
2097
+ ///</signature>
2098
+ ///<signature>
2099
+ ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
2100
+ ///options.enabledDates if such exist.</summary>
2101
+ ///<param name="dates" locid="$.fn.datetimepicker.disabledTimeIntervals_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
2102
+ ///</signature>
2103
+ if (arguments.length === 0) {
2104
+ return (options.disabledTimeIntervals ? $.extend({}, options.disabledTimeIntervals) : options.disabledTimeIntervals);
2105
+ }
2106
+
2107
+ if (!disabledTimeIntervals) {
2108
+ options.disabledTimeIntervals = false;
2109
+ update();
2110
+ return picker;
2111
+ }
2112
+ if (!(disabledTimeIntervals instanceof Array)) {
2113
+ throw new TypeError('disabledTimeIntervals() expects an array parameter');
2114
+ }
2115
+ options.disabledTimeIntervals = disabledTimeIntervals;
2116
+ update();
2117
+ return picker;
2118
+ };
2119
+
2120
+ picker.disabledHours = function (hours) {
2121
+ ///<signature helpKeyword="$.fn.datetimepicker.disabledHours">
2122
+ ///<summary>Returns an array with the currently set disabled hours on the component.</summary>
2123
+ ///<returns type="array">options.disabledHours</returns>
2124
+ ///</signature>
2125
+ ///<signature>
2126
+ ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
2127
+ ///options.enabledHours if such exist.</summary>
2128
+ ///<param name="hours" locid="$.fn.datetimepicker.disabledHours_p:hours">Takes an [ int ] of values and disallows the user to select only from those hours.</param>
2129
+ ///</signature>
2130
+ if (arguments.length === 0) {
2131
+ return (options.disabledHours ? $.extend({}, options.disabledHours) : options.disabledHours);
2132
+ }
2133
+
2134
+ if (!hours) {
2135
+ options.disabledHours = false;
2136
+ update();
2137
+ return picker;
2138
+ }
2139
+ if (!(hours instanceof Array)) {
2140
+ throw new TypeError('disabledHours() expects an array parameter');
2141
+ }
2142
+ options.disabledHours = indexGivenHours(hours);
2143
+ options.enabledHours = false;
2144
+ if (options.useCurrent && !options.keepInvalid) {
2145
+ var tries = 0;
2146
+ while (!isValid(date, 'h')) {
2147
+ date.add(1, 'h');
2148
+ if (tries === 24) {
2149
+ throw 'Tried 24 times to find a valid date';
2150
+ }
2151
+ tries++;
2152
+ }
2153
+ setValue(date);
2154
+ }
2155
+ update();
2156
+ return picker;
2157
+ };
2158
+
2159
+ picker.enabledHours = function (hours) {
2160
+ ///<signature helpKeyword="$.fn.datetimepicker.enabledHours">
2161
+ ///<summary>Returns an array with the currently set enabled hours on the component.</summary>
2162
+ ///<returns type="array">options.enabledHours</returns>
2163
+ ///</signature>
2164
+ ///<signature>
2165
+ ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledHours if such exist.</summary>
2166
+ ///<param name="hours" locid="$.fn.datetimepicker.enabledHours_p:hours">Takes an [ int ] of values and allows the user to select only from those hours.</param>
2167
+ ///</signature>
2168
+ if (arguments.length === 0) {
2169
+ return (options.enabledHours ? $.extend({}, options.enabledHours) : options.enabledHours);
2170
+ }
2171
+
2172
+ if (!hours) {
2173
+ options.enabledHours = false;
2174
+ update();
2175
+ return picker;
2176
+ }
2177
+ if (!(hours instanceof Array)) {
2178
+ throw new TypeError('enabledHours() expects an array parameter');
2179
+ }
2180
+ options.enabledHours = indexGivenHours(hours);
2181
+ options.disabledHours = false;
2182
+ if (options.useCurrent && !options.keepInvalid) {
2183
+ var tries = 0;
2184
+ while (!isValid(date, 'h')) {
2185
+ date.add(1, 'h');
2186
+ if (tries === 24) {
2187
+ throw 'Tried 24 times to find a valid date';
2188
+ }
2189
+ tries++;
2190
+ }
2191
+ setValue(date);
2192
+ }
2193
+ update();
2194
+ return picker;
2195
+ };
2196
+
2197
+ 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
+ if (arguments.length === 0) {
2207
+ return viewDate.clone();
2208
+ }
2209
+
2210
+ if (!newDate) {
2211
+ viewDate = date.clone();
2212
+ return picker;
2213
+ }
2214
+
2215
+ if (typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) {
2216
+ throw new TypeError('viewDate() parameter must be one of [string, moment or Date]');
2217
+ }
2218
+
2219
+ viewDate = parseInputDate(newDate);
2220
+ viewUpdate();
2221
+ return picker;
2222
+ };
2223
+
2224
+ // initializing element and component attributes
2225
+ if (element.is('input')) {
2226
+ input = element;
2227
+ } else {
2228
+ input = element.find(options.datepickerInput);
2229
+ if (input.size() === 0) {
2230
+ input = element.find('input');
2231
+ } else if (!input.is('input')) {
2232
+ throw new Error('CSS class "' + options.datepickerInput + '" cannot be applied to non input element');
2233
+ }
2234
+ }
2235
+
2236
+ if (element.hasClass('input-group')) {
2237
+ // 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-"]');
2240
+ } else {
2241
+ component = element.find('.datepickerbutton');
2242
+ }
2243
+ }
2244
+
2245
+ if (!options.inline && !input.is('input')) {
2246
+ throw new Error('Could not initialize DateTimePicker without an input element');
2247
+ }
2248
+
2249
+ $.extend(true, options, dataToOptions());
2250
+
2251
+ picker.options(options);
2252
+
2253
+ initFormatting();
2254
+
2255
+ attachDatePickerElementEvents();
2256
+
2257
+ if (input.prop('disabled')) {
2258
+ picker.disable();
2259
+ }
2260
+ if (input.is('input') && input.val().trim().length !== 0) {
2261
+ setValue(parseInputDate(input.val().trim()));
2262
+ }
2263
+ else if (options.defaultDate && input.attr('placeholder') === undefined) {
2264
+ setValue(options.defaultDate);
2265
+ }
2266
+ if (options.inline) {
2267
+ show();
2268
+ }
2269
+ return picker;
2270
+ };
2271
+
2272
+ /********************************************************************************
2273
+ *
2274
+ * jQuery plugin constructor and defaults object
2275
+ *
2276
+ ********************************************************************************/
2277
+
2278
+ $.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
+ });
2287
+ };
2288
+
2289
+ $.fn.datetimepicker.defaults = {
2290
+ format: false,
2291
+ dayViewHeaderFormat: 'MMMM YYYY',
2292
+ extraFormats: false,
2293
+ stepping: 1,
2294
+ minDate: false,
2295
+ maxDate: false,
2296
+ useCurrent: true,
2297
+ collapse: true,
2298
+ locale: moment.locale(),
2299
+ defaultDate: false,
2300
+ disabledDates: false,
2301
+ enabledDates: false,
2302
+ icons: {
2303
+ time: 'glyphicon glyphicon-time',
2304
+ date: 'glyphicon glyphicon-calendar',
2305
+ up: 'glyphicon glyphicon-chevron-up',
2306
+ down: 'glyphicon glyphicon-chevron-down',
2307
+ previous: 'glyphicon glyphicon-chevron-left',
2308
+ next: 'glyphicon glyphicon-chevron-right',
2309
+ today: 'glyphicon glyphicon-screenshot',
2310
+ clear: 'glyphicon glyphicon-trash',
2311
+ close: 'glyphicon glyphicon-remove'
2312
+ },
2313
+ useStrict: false,
2314
+ sideBySide: false,
2315
+ daysOfWeekDisabled: false,
2316
+ calendarWeeks: false,
2317
+ viewMode: 'days',
2318
+ toolbarPlacement: 'default',
2319
+ showTodayButton: false,
2320
+ showClear: false,
2321
+ showClose: false,
2322
+ widgetPositioning: {
2323
+ horizontal: 'auto',
2324
+ vertical: 'auto'
2325
+ },
2326
+ widgetParent: null,
2327
+ ignoreReadonly: false,
2328
+ keepOpen: false,
2329
+ focusOnShow: true,
2330
+ inline: false,
2331
+ keepInvalid: false,
2332
+ datepickerInput: '.datepickerinput',
2333
+ keyBinds: {
2334
+ up: function (widget) {
2335
+ if (!widget) {
2336
+ return;
2337
+ }
2338
+ var d = this.date() || moment();
2339
+ if (widget.find('.datepicker').is(':visible')) {
2340
+ this.date(d.clone().subtract(7, 'd'));
2341
+ } else {
2342
+ this.date(d.clone().add(1, 'm'));
2343
+ }
2344
+ },
2345
+ down: function (widget) {
2346
+ if (!widget) {
2347
+ this.show();
2348
+ return;
2349
+ }
2350
+ var d = this.date() || moment();
2351
+ if (widget.find('.datepicker').is(':visible')) {
2352
+ this.date(d.clone().add(7, 'd'));
2353
+ } else {
2354
+ this.date(d.clone().subtract(1, 'm'));
2355
+ }
2356
+ },
2357
+ 'control up': function (widget) {
2358
+ if (!widget) {
2359
+ return;
2360
+ }
2361
+ var d = this.date() || moment();
2362
+ if (widget.find('.datepicker').is(':visible')) {
2363
+ this.date(d.clone().subtract(1, 'y'));
2364
+ } else {
2365
+ this.date(d.clone().add(1, 'h'));
2366
+ }
2367
+ },
2368
+ 'control down': function (widget) {
2369
+ if (!widget) {
2370
+ return;
2371
+ }
2372
+ var d = this.date() || moment();
2373
+ if (widget.find('.datepicker').is(':visible')) {
2374
+ this.date(d.clone().add(1, 'y'));
2375
+ } else {
2376
+ this.date(d.clone().subtract(1, 'h'));
2377
+ }
2378
+ },
2379
+ left: function (widget) {
2380
+ if (!widget) {
2381
+ return;
2382
+ }
2383
+ var d = this.date() || moment();
2384
+ if (widget.find('.datepicker').is(':visible')) {
2385
+ this.date(d.clone().subtract(1, 'd'));
2386
+ }
2387
+ },
2388
+ right: function (widget) {
2389
+ if (!widget) {
2390
+ return;
2391
+ }
2392
+ var d = this.date() || moment();
2393
+ if (widget.find('.datepicker').is(':visible')) {
2394
+ this.date(d.clone().add(1, 'd'));
2395
+ }
2396
+ },
2397
+ pageUp: function (widget) {
2398
+ if (!widget) {
2399
+ return;
2400
+ }
2401
+ var d = this.date() || moment();
2402
+ if (widget.find('.datepicker').is(':visible')) {
2403
+ this.date(d.clone().subtract(1, 'M'));
2404
+ }
2405
+ },
2406
+ pageDown: function (widget) {
2407
+ if (!widget) {
2408
+ return;
2409
+ }
2410
+ var d = this.date() || moment();
2411
+ if (widget.find('.datepicker').is(':visible')) {
2412
+ this.date(d.clone().add(1, 'M'));
2413
+ }
2414
+ },
2415
+ enter: function () {
2416
+ this.hide();
2417
+ },
2418
+ escape: function () {
2419
+ this.hide();
2420
+ },
2421
+ //tab: function (widget) { //this break the flow of the form. disabling for now
2422
+ // var toggle = widget.find('.picker-switch a[data-action="togglePicker"]');
2423
+ // if(toggle.length > 0) toggle.click();
2424
+ //},
2425
+ 'control space': function (widget) {
2426
+ if (widget.find('.timepicker').is(':visible')) {
2427
+ widget.find('.btn[data-action="togglePeriod"]').click();
2428
+ }
2429
+ },
2430
+ t: function () {
2431
+ this.date(moment());
2432
+ },
2433
+ 'delete': function () {
2434
+ this.clear();
2435
+ }
2436
+ },
2437
+ debug: false,
2438
+ allowInputToggle: false,
2439
+ disabledTimeIntervals: false,
2440
+ disabledHours: false,
2441
+ enabledHours: false,
2442
+ viewDate: false
2443
+ };
2444
+ }));