Projet

Général

Profil

0001-add-bootstrap-based-date-and-datetime-fields-10606.patch

Benjamin Dauvergne, 24 mars 2017 14:07

Télécharger (78,2 ko)

Voir les différences:

Subject: [PATCH 1/2] add bootstrap based date and datetime fields (#10606)

 setup.py                                           |    2 +
 src/authentic2/settings.py                         |    2 +
 src/authentic2/static/css/datetimepicker.css       |  372 +++++
 .../static/js/bootstrap-datetimepicker.js          | 1752 ++++++++++++++++++++
 .../js/locales/bootstrap-datetimepicker.fr.js      |   18 +
 src/authentic2/widgets.py                          |  186 +++
 tests/test_widget_datetimepicker.py                |    6 +
 7 files changed, 2338 insertions(+)
 create mode 100644 src/authentic2/static/css/datetimepicker.css
 create mode 100644 src/authentic2/static/js/bootstrap-datetimepicker.js
 create mode 100644 src/authentic2/static/js/locales/bootstrap-datetimepicker.fr.js
 create mode 100644 src/authentic2/widgets.py
 create mode 100644 tests/test_widget_datetimepicker.py
setup.py
129 129
          'django-jsonfield < 1.0.0',
130 130
          'jwcrypto>=0.3.1,<1',
131 131
          'cryptography',
132
          'XStatic-jQuery',
133
          'XStatic-jquery-ui',
132 134
      ],
133 135
      extras_require = {
134 136
          'idp-openid': ['python-openid'],
src/authentic2/settings.py
119 119
    'authentic2.a2_rbac',
120 120
    'gadjo',
121 121
    'rest_framework',
122
    'xstatic.pkg.jquery',
123
    'xstatic.pkg.jquery_ui',
122 124
)
123 125

  
124 126
INSTALLED_APPS = tuple(plugins.register_plugins_installed_apps(INSTALLED_APPS))
src/authentic2/static/css/datetimepicker.css
1
/*!
2
 * Datetimepicker for Bootstrap
3
 *
4
 * Copyright 2012 Stefan Petre
5
 * Improvements by Andrew Rowls
6
 * Licensed under the Apache License v2.0
7
 * http://www.apache.org/licenses/LICENSE-2.0
8
 *
9
 */
10
.datetimepicker {
11
  padding: 4px;
12
  margin-top: 1px;
13
  -webkit-border-radius: 4px;
14
  -moz-border-radius: 4px;
15
  border-radius: 4px;
16
  direction: ltr;
17
  /*.dow {
18
		border-top: 1px solid #ddd !important;
19
	}*/
20

  
21
}
22
.datetimepicker-inline {
23
  width: 220px;
24
}
25
.datetimepicker.datetimepicker-rtl {
26
  direction: rtl;
27
}
28
.datetimepicker.datetimepicker-rtl table tr td span {
29
  float: right;
30
}
31
.datetimepicker-dropdown, .datetimepicker-dropdown-left {
32
  top: 0;
33
  left: 0;
34
}
35
.datetimepicker-dropdown:before {
36
  content: '';
37
  display: inline-block;
38
  border-left: 7px solid transparent;
39
  border-right: 7px solid transparent;
40
  border-bottom: 7px solid #ccc;
41
  border-bottom-color: rgba(0, 0, 0, 0.2);
42
  position: absolute;
43
  top: -7px;
44
  left: 6px;
45
}
46
.datetimepicker-dropdown:after {
47
  content: '';
48
  display: inline-block;
49
  border-left: 6px solid transparent;
50
  border-right: 6px solid transparent;
51
  border-bottom: 6px solid #ffffff;
52
  position: absolute;
53
  top: -6px;
54
  left: 7px;
55
}
56
.datetimepicker-dropdown-left:before {
57
  content: '';
58
  display: inline-block;
59
  border-left: 7px solid transparent;
60
  border-right: 7px solid transparent;
61
  border-bottom: 7px solid #ccc;
62
  border-bottom-color: rgba(0, 0, 0, 0.2);
63
  position: absolute;
64
  top: -7px;
65
  right: 6px;
66
}
67
.datetimepicker-dropdown-left:after {
68
  content: '';
69
  display: inline-block;
70
  border-left: 6px solid transparent;
71
  border-right: 6px solid transparent;
72
  border-bottom: 6px solid #ffffff;
73
  position: absolute;
74
  top: -6px;
75
  right: 7px;
76
}
77
.datetimepicker > div {
78
  display: none;
79
}
80
.datetimepicker.minutes div.datetimepicker-minutes {
81
    display: block;
82
}
83
.datetimepicker.hours div.datetimepicker-hours {
84
    display: block;
85
}
86
.datetimepicker.days div.datetimepicker-days {
87
    display: block;
88
}
89
.datetimepicker.months div.datetimepicker-months {
90
  display: block;
91
}
92
.datetimepicker.years div.datetimepicker-years {
93
  display: block;
94
}
95
.datetimepicker table {
96
  margin: 0;
97
}
98
.datetimepicker  td,
99
.datetimepicker th {
100
  text-align: center;
101
  width: 20px;
102
  height: 20px;
103
  -webkit-border-radius: 4px;
104
  -moz-border-radius: 4px;
105
  border-radius: 4px;
106
  border: none;
107
}
108
.table-striped .datetimepicker table tr td,
109
.table-striped .datetimepicker table tr th {
110
  background-color: transparent;
111
}
112
.datetimepicker table tr td.minute:hover {
113
    background: #eeeeee;
114
    cursor: pointer;
115
}
116
.datetimepicker table tr td.hour:hover {
117
    background: #eeeeee;
118
    cursor: pointer;
119
}
120
.datetimepicker table tr td.day:hover {
121
    background: #eeeeee;
122
    cursor: pointer;
123
}
124
.datetimepicker table tr td.old,
125
.datetimepicker table tr td.new {
126
  color: #999999;
127
}
128
.datetimepicker table tr td.disabled,
129
.datetimepicker table tr td.disabled:hover {
130
  background: none;
131
  color: #999999;
132
  cursor: default;
133
}
134
.datetimepicker table tr td.today,
135
.datetimepicker table tr td.today:hover,
136
.datetimepicker table tr td.today.disabled,
137
.datetimepicker table tr td.today.disabled:hover {
138
  background-color: #fde19a;
139
  background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a);
140
  background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a);
141
  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a));
142
  background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a);
143
  background-image: -o-linear-gradient(top, #fdd49a, #fdf59a);
144
  background-image: linear-gradient(top, #fdd49a, #fdf59a);
145
  background-repeat: repeat-x;
146
  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);
147
  border-color: #fdf59a #fdf59a #fbed50;
148
  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
149
  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
150
}
151
.datetimepicker table tr td.today:hover,
152
.datetimepicker table tr td.today:hover:hover,
153
.datetimepicker table tr td.today.disabled:hover,
154
.datetimepicker table tr td.today.disabled:hover:hover,
155
.datetimepicker table tr td.today:active,
156
.datetimepicker table tr td.today:hover:active,
157
.datetimepicker table tr td.today.disabled:active,
158
.datetimepicker table tr td.today.disabled:hover:active,
159
.datetimepicker table tr td.today.active,
160
.datetimepicker table tr td.today:hover.active,
161
.datetimepicker table tr td.today.disabled.active,
162
.datetimepicker table tr td.today.disabled:hover.active,
163
.datetimepicker table tr td.today.disabled,
164
.datetimepicker table tr td.today:hover.disabled,
165
.datetimepicker table tr td.today.disabled.disabled,
166
.datetimepicker table tr td.today.disabled:hover.disabled,
167
.datetimepicker table tr td.today[disabled],
168
.datetimepicker table tr td.today:hover[disabled],
169
.datetimepicker table tr td.today.disabled[disabled],
170
.datetimepicker table tr td.today.disabled:hover[disabled] {
171
  background-color: #fdf59a;
172
}
173
.datetimepicker table tr td.today:active,
174
.datetimepicker table tr td.today:hover:active,
175
.datetimepicker table tr td.today.disabled:active,
176
.datetimepicker table tr td.today.disabled:hover:active,
177
.datetimepicker table tr td.today.active,
178
.datetimepicker table tr td.today:hover.active,
179
.datetimepicker table tr td.today.disabled.active,
180
.datetimepicker table tr td.today.disabled:hover.active {
181
  background-color: #fbf069 \9;
182
}
183
.datetimepicker table tr td.active,
184
.datetimepicker table tr td.active:hover,
185
.datetimepicker table tr td.active.disabled,
186
.datetimepicker table tr td.active.disabled:hover {
187
  background-color: #006dcc;
188
  background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
189
  background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
190
  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
191
  background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
192
  background-image: -o-linear-gradient(top, #0088cc, #0044cc);
193
  background-image: linear-gradient(top, #0088cc, #0044cc);
194
  background-repeat: repeat-x;
195
  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
196
  border-color: #0044cc #0044cc #002a80;
197
  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
198
  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
199
  color: #fff;
200
  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
201
}
202
.datetimepicker table tr td.active:hover,
203
.datetimepicker table tr td.active:hover:hover,
204
.datetimepicker table tr td.active.disabled:hover,
205
.datetimepicker table tr td.active.disabled:hover:hover,
206
.datetimepicker table tr td.active:active,
207
.datetimepicker table tr td.active:hover:active,
208
.datetimepicker table tr td.active.disabled:active,
209
.datetimepicker table tr td.active.disabled:hover:active,
210
.datetimepicker table tr td.active.active,
211
.datetimepicker table tr td.active:hover.active,
212
.datetimepicker table tr td.active.disabled.active,
213
.datetimepicker table tr td.active.disabled:hover.active,
214
.datetimepicker table tr td.active.disabled,
215
.datetimepicker table tr td.active:hover.disabled,
216
.datetimepicker table tr td.active.disabled.disabled,
217
.datetimepicker table tr td.active.disabled:hover.disabled,
218
.datetimepicker table tr td.active[disabled],
219
.datetimepicker table tr td.active:hover[disabled],
220
.datetimepicker table tr td.active.disabled[disabled],
221
.datetimepicker table tr td.active.disabled:hover[disabled] {
222
  background-color: #0044cc;
223
}
224
.datetimepicker table tr td.active:active,
225
.datetimepicker table tr td.active:hover:active,
226
.datetimepicker table tr td.active.disabled:active,
227
.datetimepicker table tr td.active.disabled:hover:active,
228
.datetimepicker table tr td.active.active,
229
.datetimepicker table tr td.active:hover.active,
230
.datetimepicker table tr td.active.disabled.active,
231
.datetimepicker table tr td.active.disabled:hover.active {
232
  background-color: #003399 \9;
233
}
234
.datetimepicker table tr td span {
235
  display: block;
236
  width: 23%;
237
  height: 54px;
238
  line-height: 54px;
239
  float: left;
240
  margin: 1%;
241
  cursor: pointer;
242
  -webkit-border-radius: 4px;
243
  -moz-border-radius: 4px;
244
  border-radius: 4px;
245
}
246
.datetimepicker .datetimepicker-hours span {
247
  height: 26px;
248
  line-height: 26px;
249
}
250
.datetimepicker .datetimepicker-hours table tr td span.hour_am,
251
.datetimepicker .datetimepicker-hours table tr td span.hour_pm {
252
  width: 14.6%;
253
}
254
.datetimepicker .datetimepicker-hours fieldset legend,
255
.datetimepicker .datetimepicker-minutes fieldset legend {
256
  margin-bottom: inherit;
257
  line-height: 30px;
258
}
259
.datetimepicker .datetimepicker-minutes span {
260
  height: 26px;
261
  line-height: 26px;
262
}
263
.datetimepicker table tr td span:hover {
264
  background: #eeeeee;
265
}
266
.datetimepicker table tr td span.disabled,
267
.datetimepicker table tr td span.disabled:hover {
268
  background: none;
269
  color: #999999;
270
  cursor: default;
271
}
272
.datetimepicker table tr td span.active,
273
.datetimepicker table tr td span.active:hover,
274
.datetimepicker table tr td span.active.disabled,
275
.datetimepicker table tr td span.active.disabled:hover {
276
  background-color: #006dcc;
277
  background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
278
  background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
279
  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
280
  background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
281
  background-image: -o-linear-gradient(top, #0088cc, #0044cc);
282
  background-image: linear-gradient(top, #0088cc, #0044cc);
283
  background-repeat: repeat-x;
284
  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
285
  border-color: #0044cc #0044cc #002a80;
286
  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
287
  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
288
  color: #fff;
289
  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
290
}
291
.datetimepicker table tr td span.active:hover,
292
.datetimepicker table tr td span.active:hover:hover,
293
.datetimepicker table tr td span.active.disabled:hover,
294
.datetimepicker table tr td span.active.disabled:hover:hover,
295
.datetimepicker table tr td span.active:active,
296
.datetimepicker table tr td span.active:hover:active,
297
.datetimepicker table tr td span.active.disabled:active,
298
.datetimepicker table tr td span.active.disabled:hover:active,
299
.datetimepicker table tr td span.active.active,
300
.datetimepicker table tr td span.active:hover.active,
301
.datetimepicker table tr td span.active.disabled.active,
302
.datetimepicker table tr td span.active.disabled:hover.active,
303
.datetimepicker table tr td span.active.disabled,
304
.datetimepicker table tr td span.active:hover.disabled,
305
.datetimepicker table tr td span.active.disabled.disabled,
306
.datetimepicker table tr td span.active.disabled:hover.disabled,
307
.datetimepicker table tr td span.active[disabled],
308
.datetimepicker table tr td span.active:hover[disabled],
309
.datetimepicker table tr td span.active.disabled[disabled],
310
.datetimepicker table tr td span.active.disabled:hover[disabled] {
311
  background-color: #0044cc;
312
}
313
.datetimepicker table tr td span.active:active,
314
.datetimepicker table tr td span.active:hover:active,
315
.datetimepicker table tr td span.active.disabled:active,
316
.datetimepicker table tr td span.active.disabled:hover:active,
317
.datetimepicker table tr td span.active.active,
318
.datetimepicker table tr td span.active:hover.active,
319
.datetimepicker table tr td span.active.disabled.active,
320
.datetimepicker table tr td span.active.disabled:hover.active {
321
  background-color: #003399 \9;
322
}
323
.datetimepicker table tr td span.old {
324
  color: #999999;
325
}
326
.datetimepicker th.switch {
327
  width: 145px;
328
}
329
.datetimepicker thead tr:first-child th,
330
.datetimepicker tfoot tr:first-child th {
331
  cursor: pointer;
332
}
333
.datetimepicker thead tr:first-child th:hover,
334
.datetimepicker tfoot tr:first-child th:hover {
335
  background: #eeeeee;
336
}
337
.input-append.date .add-on i,
338
.input-prepend.date .add-on i {
339
  cursor: pointer;
340
  width: 14px;
341
  height: 14px;
342
}
343

  
344

  
345

  
346
.dropdown-menu {
347
  position: absolute;
348
  top: 100%;
349
  left: 0;
350
  z-index: 1000;
351
  display: none;
352
  float: left;
353
  min-width: 160px;
354
  padding: 5px 0;
355
  margin: 2px 0 0;
356
  list-style: none;
357
  background-color: #ffffff;
358
  border: 1px solid #cccccc;
359
  border: 1px solid rgba(0, 0, 0, 0.15);
360
  border-radius: 4px;
361
  -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
362
          box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
363
  background-clip: padding-box;
364
}
365

  
366
.icon-arrow-left:before {
367
	content: "←";
368
}
369

  
370
.icon-arrow-right:before {
371
	content: "→";
372
}
src/authentic2/static/js/bootstrap-datetimepicker.js
1
/* =========================================================
2
 * bootstrap-datetimepicker.js
3
 * =========================================================
4
 * Copyright 2012 Stefan Petre
5
 * Improvements by Andrew Rowls
6
 * Improvements by Sébastien Malot
7
 * Improvements by Yun Lai
8
 * Project URL : http://www.malot.fr/bootstrap-datetimepicker
9
 *
10
 * Licensed under the Apache License, Version 2.0 (the "License");
11
 * you may not use this file except in compliance with the License.
12
 * You may obtain a copy of the License at
13
 *
14
 * http://www.apache.org/licenses/LICENSE-2.0
15
 *
16
 * Unless required by applicable law or agreed to in writing, software
17
 * distributed under the License is distributed on an "AS IS" BASIS,
18
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
 * See the License for the specific language governing permissions and
20
 * limitations under the License.
21
 * ========================================================= */
22

  
23
/*
24
 * Improvement by CuGBabyBeaR @ 2013-09-12
25
 * 
26
 * Make it work in bootstrap v3
27
 */
28

  
29
!function ($) {
30

  
31
	function UTCDate() {
32
		return new Date(Date.UTC.apply(Date, arguments));
33
	}
34

  
35
	function UTCToday() {
36
		var today = new Date();
37
		return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate(), today.getUTCHours(), today.getUTCMinutes(), today.getUTCSeconds(), 0);
38
	}
39

  
40
	// Picker object
41

  
42
	var Datetimepicker = function (element, options) {
43
		var that = this;
44

  
45
		this.element = $(element);
46

  
47
		// add container for single page application
48
		// when page switch the datetimepicker div will be removed also.
49
		this.container = options.container || 'body';
50

  
51
		this.language = options.language || this.element.data('date-language') || "en";
52
		this.language = this.language in dates ? this.language : "en";
53
		this.isRTL = dates[this.language].rtl || false;
54
		this.formatType = options.formatType || this.element.data('format-type') || 'standard';
55
		this.format = DPGlobal.parseFormat(options.format || this.element.data('date-format') || dates[this.language].format || DPGlobal.getDefaultFormat(this.formatType, 'input'), this.formatType);
56
		this.isInline = false;
57
		this.isVisible = false;
58
		this.isInput = this.element.is('input');
59

  
60

  
61
		this.bootcssVer = this.isInput ? (this.element.is('.form-control') ? 3 : 2) : ( this.bootcssVer = this.element.is('.input-group') ? 3 : 2 );
62

  
63
		this.component = this.element.is('.date') ? ( this.bootcssVer == 3 ? this.element.find('.input-group-addon .glyphicon-th, .input-group-addon .glyphicon-time, .input-group-addon .glyphicon-calendar').parent() : this.element.find('.add-on .icon-th, .add-on .icon-time, .add-on .icon-calendar').parent()) : false;
64
		this.componentReset = this.element.is('.date') ? ( this.bootcssVer == 3 ? this.element.find('.input-group-addon .glyphicon-remove').parent() : this.element.find('.add-on .icon-remove').parent()) : false;
65
		this.hasInput = this.component && this.element.find('input').length;
66
		if (this.component && this.component.length === 0) {
67
			this.component = false;
68
		}
69
		this.linkField = options.linkField || this.element.data('link-field') || false;
70
		this.linkFormat = DPGlobal.parseFormat(options.linkFormat || this.element.data('link-format') || DPGlobal.getDefaultFormat(this.formatType, 'link'), this.formatType);
71
		this.minuteStep = options.minuteStep || this.element.data('minute-step') || 5;
72
		this.pickerPosition = options.pickerPosition || this.element.data('picker-position') || 'bottom-right';
73
		this.showMeridian = options.showMeridian || this.element.data('show-meridian') || false;
74
		this.initialDate = options.initialDate || new Date();
75

  
76
		this._attachEvents();
77

  
78
		this.formatViewType = "datetime";
79
		if ('formatViewType' in options) {
80
			this.formatViewType = options.formatViewType;
81
		} else if ('formatViewType' in this.element.data()) {
82
			this.formatViewType = this.element.data('formatViewType');
83
		}
84

  
85
		this.minView = 0;
86
		if ('minView' in options) {
87
			this.minView = options.minView;
88
		} else if ('minView' in this.element.data()) {
89
			this.minView = this.element.data('min-view');
90
		}
91
		this.minView = DPGlobal.convertViewMode(this.minView);
92

  
93
		this.maxView = DPGlobal.modes.length - 1;
94
		if ('maxView' in options) {
95
			this.maxView = options.maxView;
96
		} else if ('maxView' in this.element.data()) {
97
			this.maxView = this.element.data('max-view');
98
		}
99
		this.maxView = DPGlobal.convertViewMode(this.maxView);
100

  
101
		this.wheelViewModeNavigation = false;
102
		if ('wheelViewModeNavigation' in options) {
103
			this.wheelViewModeNavigation = options.wheelViewModeNavigation;
104
		} else if ('wheelViewModeNavigation' in this.element.data()) {
105
			this.wheelViewModeNavigation = this.element.data('view-mode-wheel-navigation');
106
		}
107

  
108
		this.wheelViewModeNavigationInverseDirection = false;
109

  
110
		if ('wheelViewModeNavigationInverseDirection' in options) {
111
			this.wheelViewModeNavigationInverseDirection = options.wheelViewModeNavigationInverseDirection;
112
		} else if ('wheelViewModeNavigationInverseDirection' in this.element.data()) {
113
			this.wheelViewModeNavigationInverseDirection = this.element.data('view-mode-wheel-navigation-inverse-dir');
114
		}
115

  
116
		this.wheelViewModeNavigationDelay = 100;
117
		if ('wheelViewModeNavigationDelay' in options) {
118
			this.wheelViewModeNavigationDelay = options.wheelViewModeNavigationDelay;
119
		} else if ('wheelViewModeNavigationDelay' in this.element.data()) {
120
			this.wheelViewModeNavigationDelay = this.element.data('view-mode-wheel-navigation-delay');
121
		}
122

  
123
		this.startViewMode = 2;
124
		if ('startView' in options) {
125
			this.startViewMode = options.startView;
126
		} else if ('startView' in this.element.data()) {
127
			this.startViewMode = this.element.data('start-view');
128
		}
129
		this.startViewMode = DPGlobal.convertViewMode(this.startViewMode);
130
		this.viewMode = this.startViewMode;
131

  
132
		this.viewSelect = this.minView;
133
		if ('viewSelect' in options) {
134
			this.viewSelect = options.viewSelect;
135
		} else if ('viewSelect' in this.element.data()) {
136
			this.viewSelect = this.element.data('view-select');
137
		}
138
		this.viewSelect = DPGlobal.convertViewMode(this.viewSelect);
139

  
140
		this.forceParse = true;
141
		if ('forceParse' in options) {
142
			this.forceParse = options.forceParse;
143
		} else if ('dateForceParse' in this.element.data()) {
144
			this.forceParse = this.element.data('date-force-parse');
145
		}
146

  
147
		this.picker = $((this.bootcssVer == 3) ? DPGlobal.templateV3 : DPGlobal.template)
148
			.appendTo(this.isInline ? this.element : this.container) // 'body')
149
			.on({
150
				click:     $.proxy(this.click, this),
151
				mousedown: $.proxy(this.mousedown, this)
152
			});
153

  
154
		if (this.wheelViewModeNavigation) {
155
			if ($.fn.mousewheel) {
156
				this.picker.on({mousewheel: $.proxy(this.mousewheel, this)});
157
			} else {
158
				console.log("Mouse Wheel event is not supported. Please include the jQuery Mouse Wheel plugin before enabling this option");
159
			}
160
		}
161

  
162
		if (this.isInline) {
163
			this.picker.addClass('datetimepicker-inline');
164
		} else {
165
			this.picker.addClass('datetimepicker-dropdown-' + this.pickerPosition + ' dropdown-menu');
166
		}
167
		if (this.isRTL) {
168
			this.picker.addClass('datetimepicker-rtl');
169
			if (this.bootcssVer == 3) {
170
				this.picker.find('.prev span, .next span')
171
					.toggleClass('glyphicon-arrow-left glyphicon-arrow-right');
172
			} else {
173
				this.picker.find('.prev i, .next i')
174
					.toggleClass('icon-arrow-left icon-arrow-right');
175
			}
176
			;
177

  
178
		}
179
		$(document).on('mousedown', function (e) {
180
			// Clicked outside the datetimepicker, hide it
181
			if ($(e.target).closest('.datetimepicker').length === 0) {
182
				that.hide();
183
			}
184
		});
185

  
186
		this.autoclose = false;
187
		if ('autoclose' in options) {
188
			this.autoclose = options.autoclose;
189
		} else if ('dateAutoclose' in this.element.data()) {
190
			this.autoclose = this.element.data('date-autoclose');
191
		}
192

  
193
		this.keyboardNavigation = true;
194
		if ('keyboardNavigation' in options) {
195
			this.keyboardNavigation = options.keyboardNavigation;
196
		} else if ('dateKeyboardNavigation' in this.element.data()) {
197
			this.keyboardNavigation = this.element.data('date-keyboard-navigation');
198
		}
199

  
200
		this.todayBtn = (options.todayBtn || this.element.data('date-today-btn') || false);
201
		this.todayHighlight = (options.todayHighlight || this.element.data('date-today-highlight') || false);
202

  
203
		this.weekStart = ((options.weekStart || this.element.data('date-weekstart') || dates[this.language].weekStart || 0) % 7);
204
		this.weekEnd = ((this.weekStart + 6) % 7);
205
		this.startDate = -Infinity;
206
		this.endDate = Infinity;
207
		this.daysOfWeekDisabled = [];
208
		this.setStartDate(options.startDate || this.element.data('date-startdate'));
209
		this.setEndDate(options.endDate || this.element.data('date-enddate'));
210
		this.setDaysOfWeekDisabled(options.daysOfWeekDisabled || this.element.data('date-days-of-week-disabled'));
211
		this.fillDow();
212
		this.fillMonths();
213
		this.update();
214
		this.showMode();
215

  
216
		if (this.isInline) {
217
			this.show();
218
		}
219
	};
220

  
221
	Datetimepicker.prototype = {
222
		constructor: Datetimepicker,
223

  
224
		_events:       [],
225
		_attachEvents: function () {
226
			this._detachEvents();
227
			if (this.isInput) { // single input
228
				this._events = [
229
					[this.element, {
230
						focus:   $.proxy(this.show, this),
231
						keyup:   $.proxy(this.update, this),
232
						keydown: $.proxy(this.keydown, this)
233
					}]
234
				];
235
			}
236
			else if (this.component && this.hasInput) { // component: input + button
237
				this._events = [
238
					// For components that are not readonly, allow keyboard nav
239
					[this.element.find('input'), {
240
						focus:   $.proxy(this.show, this),
241
						keyup:   $.proxy(this.update, this),
242
						keydown: $.proxy(this.keydown, this)
243
					}],
244
					[this.component, {
245
						click: $.proxy(this.show, this)
246
					}]
247
				];
248
				if (this.componentReset) {
249
					this._events.push([
250
						this.componentReset,
251
						{click: $.proxy(this.reset, this)}
252
					]);
253
				}
254
			}
255
			else if (this.element.is('div')) {  // inline datetimepicker
256
				this.isInline = true;
257
			}
258
			else {
259
				this._events = [
260
					[this.element, {
261
						click: $.proxy(this.show, this)
262
					}]
263
				];
264
			}
265
			for (var i = 0, el, ev; i < this._events.length; i++) {
266
				el = this._events[i][0];
267
				ev = this._events[i][1];
268
				el.on(ev);
269
			}
270
		},
271

  
272
		_detachEvents: function () {
273
			for (var i = 0, el, ev; i < this._events.length; i++) {
274
				el = this._events[i][0];
275
				ev = this._events[i][1];
276
				el.off(ev);
277
			}
278
			this._events = [];
279
		},
280

  
281
		show: function (e) {
282
			this.picker.show();
283
			this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
284
			if (this.forceParse) {
285
				this.update();
286
			}
287
			this.place();
288
			$(window).on('resize', $.proxy(this.place, this));
289
			if (e) {
290
				e.stopPropagation();
291
				e.preventDefault();
292
			}
293
			this.isVisible = true;
294
			this.element.trigger({
295
				type: 'show',
296
				date: this.date
297
			});
298
		},
299

  
300
		hide: function (e) {
301
			if (!this.isVisible) return;
302
			if (this.isInline) return;
303
			this.picker.hide();
304
			$(window).off('resize', this.place);
305
			this.viewMode = this.startViewMode;
306
			this.showMode();
307
			if (!this.isInput) {
308
				$(document).off('mousedown', this.hide);
309
			}
310

  
311
			if (
312
				this.forceParse &&
313
					(
314
						this.isInput && this.element.val() ||
315
							this.hasInput && this.element.find('input').val()
316
						)
317
				)
318
				this.setValue();
319
			this.isVisible = false;
320
			this.element.trigger({
321
				type: 'hide',
322
				date: this.date
323
			});
324
		},
325

  
326
		remove: function () {
327
			this._detachEvents();
328
			this.picker.remove();
329
			delete this.picker;
330
			delete this.element.data().datetimepicker;
331
		},
332

  
333
		getDate: function () {
334
			var d = this.getUTCDate();
335
			return new Date(d.getTime() + (d.getTimezoneOffset() * 60000));
336
		},
337

  
338
		getUTCDate: function () {
339
			return this.date;
340
		},
341

  
342
		setDate: function (d) {
343
			this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset() * 60000)));
344
		},
345

  
346
		setUTCDate: function (d) {
347
			if (d >= this.startDate && d <= this.endDate) {
348
				this.date = d;
349
				this.setValue();
350
				this.viewDate = this.date;
351
				this.fill();
352
			} else {
353
				this.element.trigger({
354
					type:      'outOfRange',
355
					date:      d,
356
					startDate: this.startDate,
357
					endDate:   this.endDate
358
				});
359
			}
360
		},
361

  
362
		setFormat: function (format) {
363
			this.format = DPGlobal.parseFormat(format, this.formatType);
364
			var element;
365
			if (this.isInput) {
366
				element = this.element;
367
			} else if (this.component) {
368
				element = this.element.find('input');
369
			}
370
			if (element && element.val()) {
371
				this.setValue();
372
			}
373
		},
374

  
375
		setValue: function () {
376
			var formatted = this.getFormattedDate();
377
			if (!this.isInput) {
378
				if (this.component) {
379
					this.element.find('input').val(formatted);
380
				}
381
				this.element.data('date', formatted);
382
			} else {
383
				this.element.val(formatted);
384
			}
385
			if (this.linkField) {
386
				$('#' + this.linkField).val(this.getFormattedDate(this.linkFormat));
387
			}
388
		},
389

  
390
		getFormattedDate: function (format) {
391
			if (format == undefined) format = this.format;
392
			return DPGlobal.formatDate(this.date, format, this.language, this.formatType);
393
		},
394

  
395
		setStartDate: function (startDate) {
396
			this.startDate = startDate || -Infinity;
397
			if (this.startDate !== -Infinity) {
398
				this.startDate = DPGlobal.parseDate(this.startDate, this.format, this.language, this.formatType);
399
			}
400
			this.update();
401
			this.updateNavArrows();
402
		},
403

  
404
		setEndDate: function (endDate) {
405
			this.endDate = endDate || Infinity;
406
			if (this.endDate !== Infinity) {
407
				this.endDate = DPGlobal.parseDate(this.endDate, this.format, this.language, this.formatType);
408
			}
409
			this.update();
410
			this.updateNavArrows();
411
		},
412

  
413
		setDaysOfWeekDisabled: function (daysOfWeekDisabled) {
414
			this.daysOfWeekDisabled = daysOfWeekDisabled || [];
415
			if (!$.isArray(this.daysOfWeekDisabled)) {
416
				this.daysOfWeekDisabled = this.daysOfWeekDisabled.split(/,\s*/);
417
			}
418
			this.daysOfWeekDisabled = $.map(this.daysOfWeekDisabled, function (d) {
419
				return parseInt(d, 10);
420
			});
421
			this.update();
422
			this.updateNavArrows();
423
		},
424

  
425
		place: function () {
426
			if (this.isInline) return;
427

  
428
			var index_highest = 0;
429
			$('div').each(function () {
430
				var index_current = parseInt($(this).css("zIndex"), 10);
431
				if (index_current > index_highest) {
432
					index_highest = index_current;
433
				}
434
			});
435
			var zIndex = index_highest + 10;
436

  
437
			var offset, top, left, containerOffset;
438
			if (this.container instanceof $) {
439
				containerOffset = this.container.offset();
440
			} else {
441
				containerOffset = $(this.container).offset();
442
			}
443

  
444
			if (this.component) {
445
				offset = this.component.offset();
446
				left = offset.left;
447
				if (this.pickerPosition == 'bottom-left' || this.pickerPosition == 'top-left') {
448
					left += this.component.outerWidth() - this.picker.outerWidth();
449
				}
450
			} else {
451
				offset = this.element.offset();
452
				left = offset.left;
453
			}
454
			
455
			if(left+220 > document.body.clientWidth){
456
            			left = document.body.clientWidth-220;
457
          		}
458
			
459
			if (this.pickerPosition == 'top-left' || this.pickerPosition == 'top-right') {
460
				top = offset.top - this.picker.outerHeight();
461
			} else {
462
				top = offset.top + this.height;
463
			}
464

  
465
			top = top - containerOffset.top;
466
			left = left - containerOffset.left;
467

  
468
			this.picker.css({
469
				top:    top,
470
				left:   left,
471
				zIndex: zIndex
472
			});
473
		},
474

  
475
		update: function () {
476
			var date, fromArgs = false;
477
			if (arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {
478
				date = arguments[0];
479
				fromArgs = true;
480
			} else {
481
				date = (this.isInput ? this.element.val() : this.element.find('input').val()) || this.element.data('date') || this.initialDate;
482
				if (typeof date == 'string' || date instanceof String) {
483
				  date = date.replace(/^\s+|\s+$/g,'');
484
				}
485
			}
486

  
487
			if (!date) {
488
				date = new Date();
489
				fromArgs = false;
490
			}
491

  
492
			this.date = DPGlobal.parseDate(date, this.format, this.language, this.formatType);
493

  
494
			if (fromArgs) this.setValue();
495

  
496
			if (this.date < this.startDate) {
497
				this.viewDate = new Date(this.startDate);
498
			} else if (this.date > this.endDate) {
499
				this.viewDate = new Date(this.endDate);
500
			} else {
501
				this.viewDate = new Date(this.date);
502
			}
503
			this.fill();
504
		},
505

  
506
		fillDow: function () {
507
			var dowCnt = this.weekStart,
508
				html = '<tr>';
509
			while (dowCnt < this.weekStart + 7) {
510
				html += '<th class="dow">' + dates[this.language].daysMin[(dowCnt++) % 7] + '</th>';
511
			}
512
			html += '</tr>';
513
			this.picker.find('.datetimepicker-days thead').append(html);
514
		},
515

  
516
		fillMonths: function () {
517
			var html = '',
518
				i = 0;
519
			while (i < 12) {
520
				html += '<span class="month">' + dates[this.language].monthsShort[i++] + '</span>';
521
			}
522
			this.picker.find('.datetimepicker-months td').html(html);
523
		},
524

  
525
		fill: function () {
526
			if (this.date == null || this.viewDate == null) {
527
				return;
528
			}
529
			var d = new Date(this.viewDate),
530
				year = d.getUTCFullYear(),
531
				month = d.getUTCMonth(),
532
				dayMonth = d.getUTCDate(),
533
				hours = d.getUTCHours(),
534
				minutes = d.getUTCMinutes(),
535
				startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity,
536
				startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity,
537
				endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity,
538
				endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity,
539
				currentDate = (new UTCDate(this.date.getUTCFullYear(), this.date.getUTCMonth(), this.date.getUTCDate())).valueOf(),
540
				today = new Date();
541
			this.picker.find('.datetimepicker-days thead th:eq(1)')
542
				.text(dates[this.language].months[month] + ' ' + year);
543
			if (this.formatViewType == "time") {
544
				var hourConverted = hours % 12 ? hours % 12 : 12;
545
				var hoursDisplay = (hourConverted < 10 ? '0' : '') + hourConverted;
546
				var minutesDisplay = (minutes < 10 ? '0' : '') + minutes;
547
				var meridianDisplay = dates[this.language].meridiem[hours < 12 ? 0 : 1];
548
				this.picker.find('.datetimepicker-hours thead th:eq(1)')
549
					.text(hoursDisplay + ':' + minutesDisplay + ' ' + (meridianDisplay ? meridianDisplay.toUpperCase() : ''));
550
				this.picker.find('.datetimepicker-minutes thead th:eq(1)')
551
					.text(hoursDisplay + ':' + minutesDisplay + ' ' + (meridianDisplay ? meridianDisplay.toUpperCase() : ''));
552
			} else {
553
				this.picker.find('.datetimepicker-hours thead th:eq(1)')
554
					.text(dayMonth + ' ' + dates[this.language].months[month] + ' ' + year);
555
				this.picker.find('.datetimepicker-minutes thead th:eq(1)')
556
					.text(dayMonth + ' ' + dates[this.language].months[month] + ' ' + year);
557
			}
558
			this.picker.find('tfoot th.today')
559
				.text(dates[this.language].today)
560
				.toggle(this.todayBtn !== false);
561
			this.updateNavArrows();
562
			this.fillMonths();
563
			/*var prevMonth = UTCDate(year, month, 0,0,0,0,0);
564
			 prevMonth.setUTCDate(prevMonth.getDate() - (prevMonth.getUTCDay() - this.weekStart + 7)%7);*/
565
			var prevMonth = UTCDate(year, month - 1, 28, 0, 0, 0, 0),
566
				day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
567
			prevMonth.setUTCDate(day);
568
			prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7) % 7);
569
			var nextMonth = new Date(prevMonth);
570
			nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
571
			nextMonth = nextMonth.valueOf();
572
			var html = [];
573
			var clsName;
574
			while (prevMonth.valueOf() < nextMonth) {
575
				if (prevMonth.getUTCDay() == this.weekStart) {
576
					html.push('<tr>');
577
				}
578
				clsName = '';
579
				if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) {
580
					clsName += ' old';
581
				} else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) {
582
					clsName += ' new';
583
				}
584
				// Compare internal UTC date with local today, not UTC today
585
				if (this.todayHighlight &&
586
					prevMonth.getUTCFullYear() == today.getFullYear() &&
587
					prevMonth.getUTCMonth() == today.getMonth() &&
588
					prevMonth.getUTCDate() == today.getDate()) {
589
					clsName += ' today';
590
				}
591
				if (prevMonth.valueOf() == currentDate) {
592
					clsName += ' active';
593
				}
594
				if ((prevMonth.valueOf() + 86400000) <= this.startDate || prevMonth.valueOf() > this.endDate ||
595
					$.inArray(prevMonth.getUTCDay(), this.daysOfWeekDisabled) !== -1) {
596
					clsName += ' disabled';
597
				}
598
				html.push('<td class="day' + clsName + '">' + prevMonth.getUTCDate() + '</td>');
599
				if (prevMonth.getUTCDay() == this.weekEnd) {
600
					html.push('</tr>');
601
				}
602
				prevMonth.setUTCDate(prevMonth.getUTCDate() + 1);
603
			}
604
			this.picker.find('.datetimepicker-days tbody').empty().append(html.join(''));
605

  
606
			html = [];
607
			var txt = '', meridian = '', meridianOld = '';
608
			for (var i = 0; i < 24; i++) {
609
				var actual = UTCDate(year, month, dayMonth, i);
610
				clsName = '';
611
				// We want the previous hour for the startDate
612
				if ((actual.valueOf() + 3600000) <= this.startDate || actual.valueOf() > this.endDate) {
613
					clsName += ' disabled';
614
				} else if (hours == i) {
615
					clsName += ' active';
616
				}
617
				if (this.showMeridian && dates[this.language].meridiem.length == 2) {
618
					meridian = (i < 12 ? dates[this.language].meridiem[0] : dates[this.language].meridiem[1]);
619
					if (meridian != meridianOld) {
620
						if (meridianOld != '') {
621
							html.push('</fieldset>');
622
						}
623
						html.push('<fieldset class="hour"><legend>' + meridian.toUpperCase() + '</legend>');
624
					}
625
					meridianOld = meridian;
626
					txt = (i % 12 ? i % 12 : 12);
627
					html.push('<span class="hour' + clsName + ' hour_' + (i < 12 ? 'am' : 'pm') + '">' + txt + '</span>');
628
					if (i == 23) {
629
						html.push('</fieldset>');
630
					}
631
				} else {
632
					txt = i + ':00';
633
					html.push('<span class="hour' + clsName + '">' + txt + '</span>');
634
				}
635
			}
636
			this.picker.find('.datetimepicker-hours td').html(html.join(''));
637

  
638
			html = [];
639
			txt = '', meridian = '', meridianOld = '';
640
			for (var i = 0; i < 60; i += this.minuteStep) {
641
				var actual = UTCDate(year, month, dayMonth, hours, i, 0);
642
				clsName = '';
643
				if (actual.valueOf() < this.startDate || actual.valueOf() > this.endDate) {
644
					clsName += ' disabled';
645
				} else if (Math.floor(minutes / this.minuteStep) == Math.floor(i / this.minuteStep)) {
646
					clsName += ' active';
647
				}
648
				if (this.showMeridian && dates[this.language].meridiem.length == 2) {
649
					meridian = (hours < 12 ? dates[this.language].meridiem[0] : dates[this.language].meridiem[1]);
650
					if (meridian != meridianOld) {
651
						if (meridianOld != '') {
652
							html.push('</fieldset>');
653
						}
654
						html.push('<fieldset class="minute"><legend>' + meridian.toUpperCase() + '</legend>');
655
					}
656
					meridianOld = meridian;
657
					txt = (hours % 12 ? hours % 12 : 12);
658
					//html.push('<span class="minute'+clsName+' minute_'+(hours<12?'am':'pm')+'">'+txt+'</span>');
659
					html.push('<span class="minute' + clsName + '">' + txt + ':' + (i < 10 ? '0' + i : i) + '</span>');
660
					if (i == 59) {
661
						html.push('</fieldset>');
662
					}
663
				} else {
664
					txt = i + ':00';
665
					//html.push('<span class="hour'+clsName+'">'+txt+'</span>');
666
					html.push('<span class="minute' + clsName + '">' + hours + ':' + (i < 10 ? '0' + i : i) + '</span>');
667
				}
668
			}
669
			this.picker.find('.datetimepicker-minutes td').html(html.join(''));
670

  
671
			var currentYear = this.date.getUTCFullYear();
672
			var months = this.picker.find('.datetimepicker-months')
673
				.find('th:eq(1)')
674
				.text(year)
675
				.end()
676
				.find('span').removeClass('active');
677
			if (currentYear == year) {
678
				months.eq(this.date.getUTCMonth()).addClass('active');
679
			}
680
			if (year < startYear || year > endYear) {
681
				months.addClass('disabled');
682
			}
683
			if (year == startYear) {
684
				months.slice(0, startMonth).addClass('disabled');
685
			}
686
			if (year == endYear) {
687
				months.slice(endMonth + 1).addClass('disabled');
688
			}
689

  
690
			html = '';
691
			year = parseInt(year / 10, 10) * 10;
692
			var yearCont = this.picker.find('.datetimepicker-years')
693
				.find('th:eq(1)')
694
				.text(year + '-' + (year + 9))
695
				.end()
696
				.find('td');
697
			year -= 1;
698
			for (var i = -1; i < 11; i++) {
699
				html += '<span class="year' + (i == -1 || i == 10 ? ' old' : '') + (currentYear == year ? ' active' : '') + (year < startYear || year > endYear ? ' disabled' : '') + '">' + year + '</span>';
700
				year += 1;
701
			}
702
			yearCont.html(html);
703
			this.place();
704
		},
705

  
706
		updateNavArrows: function () {
707
			var d = new Date(this.viewDate),
708
				year = d.getUTCFullYear(),
709
				month = d.getUTCMonth(),
710
				day = d.getUTCDate(),
711
				hour = d.getUTCHours();
712
			switch (this.viewMode) {
713
				case 0:
714
					if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()
715
						&& month <= this.startDate.getUTCMonth()
716
						&& day <= this.startDate.getUTCDate()
717
						&& hour <= this.startDate.getUTCHours()) {
718
						this.picker.find('.prev').css({visibility: 'hidden'});
719
					} else {
720
						this.picker.find('.prev').css({visibility: 'visible'});
721
					}
722
					if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()
723
						&& month >= this.endDate.getUTCMonth()
724
						&& day >= this.endDate.getUTCDate()
725
						&& hour >= this.endDate.getUTCHours()) {
726
						this.picker.find('.next').css({visibility: 'hidden'});
727
					} else {
728
						this.picker.find('.next').css({visibility: 'visible'});
729
					}
730
					break;
731
				case 1:
732
					if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()
733
						&& month <= this.startDate.getUTCMonth()
734
						&& day <= this.startDate.getUTCDate()) {
735
						this.picker.find('.prev').css({visibility: 'hidden'});
736
					} else {
737
						this.picker.find('.prev').css({visibility: 'visible'});
738
					}
739
					if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()
740
						&& month >= this.endDate.getUTCMonth()
741
						&& day >= this.endDate.getUTCDate()) {
742
						this.picker.find('.next').css({visibility: 'hidden'});
743
					} else {
744
						this.picker.find('.next').css({visibility: 'visible'});
745
					}
746
					break;
747
				case 2:
748
					if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()
749
						&& month <= this.startDate.getUTCMonth()) {
750
						this.picker.find('.prev').css({visibility: 'hidden'});
751
					} else {
752
						this.picker.find('.prev').css({visibility: 'visible'});
753
					}
754
					if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()
755
						&& month >= this.endDate.getUTCMonth()) {
756
						this.picker.find('.next').css({visibility: 'hidden'});
757
					} else {
758
						this.picker.find('.next').css({visibility: 'visible'});
759
					}
760
					break;
761
				case 3:
762
				case 4:
763
					if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) {
764
						this.picker.find('.prev').css({visibility: 'hidden'});
765
					} else {
766
						this.picker.find('.prev').css({visibility: 'visible'});
767
					}
768
					if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) {
769
						this.picker.find('.next').css({visibility: 'hidden'});
770
					} else {
771
						this.picker.find('.next').css({visibility: 'visible'});
772
					}
773
					break;
774
			}
775
		},
776

  
777
		mousewheel: function (e) {
778

  
779
			e.preventDefault();
780
			e.stopPropagation();
781

  
782
			if (this.wheelPause) {
783
				return;
784
			}
785

  
786
			this.wheelPause = true;
787

  
788
			var originalEvent = e.originalEvent;
789

  
790
			var delta = originalEvent.wheelDelta;
791

  
792
			var mode = delta > 0 ? 1 : (delta === 0) ? 0 : -1;
793

  
794
			if (this.wheelViewModeNavigationInverseDirection) {
795
				mode = -mode;
796
			}
797

  
798
			this.showMode(mode);
799

  
800
			setTimeout($.proxy(function () {
801

  
802
				this.wheelPause = false
803

  
804
			}, this), this.wheelViewModeNavigationDelay);
805

  
806
		},
807

  
808
		click: function (e) {
809
			e.stopPropagation();
810
			e.preventDefault();
811
			var target = $(e.target).closest('span, td, th, legend');
812
			if (target.is('.glyphicon')) {
813
				target = $(target).parent().closest('span, td, th, legend');
814
			}
815
			if (target.length == 1) {
816
				if (target.is('.disabled')) {
817
					this.element.trigger({
818
						type:      'outOfRange',
819
						date:      this.viewDate,
820
						startDate: this.startDate,
821
						endDate:   this.endDate
822
					});
823
					return;
824
				}
825
				switch (target[0].nodeName.toLowerCase()) {
826
					case 'th':
827
						switch (target[0].className) {
828
							case 'switch':
829
								this.showMode(1);
830
								break;
831
							case 'prev':
832
							case 'next':
833
								var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);
834
								switch (this.viewMode) {
835
									case 0:
836
										this.viewDate = this.moveHour(this.viewDate, dir);
837
										break;
838
									case 1:
839
										this.viewDate = this.moveDate(this.viewDate, dir);
840
										break;
841
									case 2:
842
										this.viewDate = this.moveMonth(this.viewDate, dir);
843
										break;
844
									case 3:
845
									case 4:
846
										this.viewDate = this.moveYear(this.viewDate, dir);
847
										break;
848
								}
849
								this.fill();
850
								this.element.trigger({
851
									type:      target[0].className + ':' + this.convertViewModeText(this.viewMode),
852
									date:      this.viewDate,
853
									startDate: this.startDate,
854
									endDate:   this.endDate
855
								});
856
								break;
857
							case 'today':
858
								var date = new Date();
859
								date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), 0);
860

  
861
								// Respect startDate and endDate.
862
								if (date < this.startDate) date = this.startDate;
863
								else if (date > this.endDate) date = this.endDate;
864

  
865
								this.viewMode = this.startViewMode;
866
								this.showMode(0);
867
								this._setDate(date);
868
								this.fill();
869
								if (this.autoclose) {
870
									this.hide();
871
								}
872
								break;
873
						}
874
						break;
875
					case 'span':
876
						if (!target.is('.disabled')) {
877
							var year = this.viewDate.getUTCFullYear(),
878
								month = this.viewDate.getUTCMonth(),
879
								day = this.viewDate.getUTCDate(),
880
								hours = this.viewDate.getUTCHours(),
881
								minutes = this.viewDate.getUTCMinutes(),
882
								seconds = this.viewDate.getUTCSeconds();
883

  
884
							if (target.is('.month')) {
885
								this.viewDate.setUTCDate(1);
886
								month = target.parent().find('span').index(target);
887
								day = this.viewDate.getUTCDate();
888
								this.viewDate.setUTCMonth(month);
889
								this.element.trigger({
890
									type: 'changeMonth',
891
									date: this.viewDate
892
								});
893
								if (this.viewSelect >= 3) {
894
									this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0));
895
								}
896
							} else if (target.is('.year')) {
897
								this.viewDate.setUTCDate(1);
898
								year = parseInt(target.text(), 10) || 0;
899
								this.viewDate.setUTCFullYear(year);
900
								this.element.trigger({
901
									type: 'changeYear',
902
									date: this.viewDate
903
								});
904
								if (this.viewSelect >= 4) {
905
									this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0));
906
								}
907
							} else if (target.is('.hour')) {
908
								hours = parseInt(target.text(), 10) || 0;
909
								if (target.hasClass('hour_am') || target.hasClass('hour_pm')) {
910
									if (hours == 12 && target.hasClass('hour_am')) {
911
										hours = 0;
912
									} else if (hours != 12 && target.hasClass('hour_pm')) {
913
										hours += 12;
914
									}
915
								}
916
								this.viewDate.setUTCHours(hours);
917
								this.element.trigger({
918
									type: 'changeHour',
919
									date: this.viewDate
920
								});
921
								if (this.viewSelect >= 1) {
922
									this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0));
923
								}
924
							} else if (target.is('.minute')) {
925
								minutes = parseInt(target.text().substr(target.text().indexOf(':') + 1), 10) || 0;
926
								this.viewDate.setUTCMinutes(minutes);
927
								this.element.trigger({
928
									type: 'changeMinute',
929
									date: this.viewDate
930
								});
931
								if (this.viewSelect >= 0) {
932
									this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0));
933
								}
934
							}
935
							if (this.viewMode != 0) {
936
								var oldViewMode = this.viewMode;
937
								this.showMode(-1);
938
								this.fill();
939
								if (oldViewMode == this.viewMode && this.autoclose) {
940
									this.hide();
941
								}
942
							} else {
943
								this.fill();
944
								if (this.autoclose) {
945
									this.hide();
946
								}
947
							}
948
						}
949
						break;
950
					case 'td':
951
						if (target.is('.day') && !target.is('.disabled')) {
952
							var day = parseInt(target.text(), 10) || 1;
953
							var year = this.viewDate.getUTCFullYear(),
954
								month = this.viewDate.getUTCMonth(),
955
								hours = this.viewDate.getUTCHours(),
956
								minutes = this.viewDate.getUTCMinutes(),
957
								seconds = this.viewDate.getUTCSeconds();
958
							if (target.is('.old')) {
959
								if (month === 0) {
960
									month = 11;
961
									year -= 1;
962
								} else {
963
									month -= 1;
964
								}
965
							} else if (target.is('.new')) {
966
								if (month == 11) {
967
									month = 0;
968
									year += 1;
969
								} else {
970
									month += 1;
971
								}
972
							}
973
							this.viewDate.setUTCFullYear(year);
974
							this.viewDate.setUTCMonth(month, day);
975
							this.element.trigger({
976
								type: 'changeDay',
977
								date: this.viewDate
978
							});
979
							if (this.viewSelect >= 2) {
980
								this._setDate(UTCDate(year, month, day, hours, minutes, seconds, 0));
981
							}
982
						}
983
						var oldViewMode = this.viewMode;
984
						this.showMode(-1);
985
						this.fill();
986
						if (oldViewMode == this.viewMode && this.autoclose) {
987
							this.hide();
988
						}
989
						break;
990
				}
991
			}
992
		},
993

  
994
		_setDate: function (date, which) {
995
			if (!which || which == 'date')
996
				this.date = date;
997
			if (!which || which == 'view')
998
				this.viewDate = date;
999
			this.fill();
1000
			this.setValue();
1001
			var element;
1002
			if (this.isInput) {
1003
				element = this.element;
1004
			} else if (this.component) {
1005
				element = this.element.find('input');
1006
			}
1007
			if (element) {
1008
				element.change();
1009
				if (this.autoclose && (!which || which == 'date')) {
1010
					//this.hide();
1011
				}
1012
			}
1013
			this.element.trigger({
1014
				type: 'changeDate',
1015
				date: this.date
1016
			});
1017
		},
1018

  
1019
		moveMinute: function (date, dir) {
1020
			if (!dir) return date;
1021
			var new_date = new Date(date.valueOf());
1022
			//dir = dir > 0 ? 1 : -1;
1023
			new_date.setUTCMinutes(new_date.getUTCMinutes() + (dir * this.minuteStep));
1024
			return new_date;
1025
		},
1026

  
1027
		moveHour: function (date, dir) {
1028
			if (!dir) return date;
1029
			var new_date = new Date(date.valueOf());
1030
			//dir = dir > 0 ? 1 : -1;
1031
			new_date.setUTCHours(new_date.getUTCHours() + dir);
1032
			return new_date;
1033
		},
1034

  
1035
		moveDate: function (date, dir) {
1036
			if (!dir) return date;
1037
			var new_date = new Date(date.valueOf());
1038
			//dir = dir > 0 ? 1 : -1;
1039
			new_date.setUTCDate(new_date.getUTCDate() + dir);
1040
			return new_date;
1041
		},
1042

  
1043
		moveMonth: function (date, dir) {
1044
			if (!dir) return date;
1045
			var new_date = new Date(date.valueOf()),
1046
				day = new_date.getUTCDate(),
1047
				month = new_date.getUTCMonth(),
1048
				mag = Math.abs(dir),
1049
				new_month, test;
1050
			dir = dir > 0 ? 1 : -1;
1051
			if (mag == 1) {
1052
				test = dir == -1
1053
					// If going back one month, make sure month is not current month
1054
					// (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
1055
					? function () {
1056
					return new_date.getUTCMonth() == month;
1057
				}
1058
					// If going forward one month, make sure month is as expected
1059
					// (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
1060
					: function () {
1061
					return new_date.getUTCMonth() != new_month;
1062
				};
1063
				new_month = month + dir;
1064
				new_date.setUTCMonth(new_month);
1065
				// Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
1066
				if (new_month < 0 || new_month > 11)
1067
					new_month = (new_month + 12) % 12;
1068
			} else {
1069
				// For magnitudes >1, move one month at a time...
1070
				for (var i = 0; i < mag; i++)
1071
					// ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
1072
					new_date = this.moveMonth(new_date, dir);
1073
				// ...then reset the day, keeping it in the new month
1074
				new_month = new_date.getUTCMonth();
1075
				new_date.setUTCDate(day);
1076
				test = function () {
1077
					return new_month != new_date.getUTCMonth();
1078
				};
1079
			}
1080
			// Common date-resetting loop -- if date is beyond end of month, make it
1081
			// end of month
1082
			while (test()) {
1083
				new_date.setUTCDate(--day);
1084
				new_date.setUTCMonth(new_month);
1085
			}
1086
			return new_date;
1087
		},
1088

  
1089
		moveYear: function (date, dir) {
1090
			return this.moveMonth(date, dir * 12);
1091
		},
1092

  
1093
		dateWithinRange: function (date) {
1094
			return date >= this.startDate && date <= this.endDate;
1095
		},
1096

  
1097
		keydown: function (e) {
1098
			if (this.picker.is(':not(:visible)')) {
1099
				if (e.keyCode == 27) // allow escape to hide and re-show picker
1100
					this.show();
1101
				return;
1102
			}
1103
			var dateChanged = false,
1104
				dir, day, month,
1105
				newDate, newViewDate;
1106
			switch (e.keyCode) {
1107
				case 27: // escape
1108
					this.hide();
1109
					e.preventDefault();
1110
					break;
1111
				case 37: // left
1112
				case 39: // right
1113
					if (!this.keyboardNavigation) break;
1114
					dir = e.keyCode == 37 ? -1 : 1;
1115
					viewMode = this.viewMode;
1116
					if (e.ctrlKey) {
1117
						viewMode += 2;
1118
					} else if (e.shiftKey) {
1119
						viewMode += 1;
1120
					}
1121
					if (viewMode == 4) {
1122
						newDate = this.moveYear(this.date, dir);
1123
						newViewDate = this.moveYear(this.viewDate, dir);
1124
					} else if (viewMode == 3) {
1125
						newDate = this.moveMonth(this.date, dir);
1126
						newViewDate = this.moveMonth(this.viewDate, dir);
1127
					} else if (viewMode == 2) {
1128
						newDate = this.moveDate(this.date, dir);
1129
						newViewDate = this.moveDate(this.viewDate, dir);
1130
					} else if (viewMode == 1) {
1131
						newDate = this.moveHour(this.date, dir);
1132
						newViewDate = this.moveHour(this.viewDate, dir);
1133
					} else if (viewMode == 0) {
1134
						newDate = this.moveMinute(this.date, dir);
1135
						newViewDate = this.moveMinute(this.viewDate, dir);
1136
					}
1137
					if (this.dateWithinRange(newDate)) {
1138
						this.date = newDate;
1139
						this.viewDate = newViewDate;
1140
						this.setValue();
1141
						this.update();
1142
						e.preventDefault();
1143
						dateChanged = true;
1144
					}
1145
					break;
1146
				case 38: // up
1147
				case 40: // down
1148
					if (!this.keyboardNavigation) break;
1149
					dir = e.keyCode == 38 ? -1 : 1;
1150
					viewMode = this.viewMode;
1151
					if (e.ctrlKey) {
1152
						viewMode += 2;
1153
					} else if (e.shiftKey) {
1154
						viewMode += 1;
1155
					}
1156
					if (viewMode == 4) {
1157
						newDate = this.moveYear(this.date, dir);
1158
						newViewDate = this.moveYear(this.viewDate, dir);
1159
					} else if (viewMode == 3) {
1160
						newDate = this.moveMonth(this.date, dir);
1161
						newViewDate = this.moveMonth(this.viewDate, dir);
1162
					} else if (viewMode == 2) {
1163
						newDate = this.moveDate(this.date, dir * 7);
1164
						newViewDate = this.moveDate(this.viewDate, dir * 7);
1165
					} else if (viewMode == 1) {
1166
						if (this.showMeridian) {
1167
							newDate = this.moveHour(this.date, dir * 6);
1168
							newViewDate = this.moveHour(this.viewDate, dir * 6);
1169
						} else {
1170
							newDate = this.moveHour(this.date, dir * 4);
1171
							newViewDate = this.moveHour(this.viewDate, dir * 4);
1172
						}
1173
					} else if (viewMode == 0) {
1174
						newDate = this.moveMinute(this.date, dir * 4);
1175
						newViewDate = this.moveMinute(this.viewDate, dir * 4);
1176
					}
1177
					if (this.dateWithinRange(newDate)) {
1178
						this.date = newDate;
1179
						this.viewDate = newViewDate;
1180
						this.setValue();
1181
						this.update();
1182
						e.preventDefault();
1183
						dateChanged = true;
1184
					}
1185
					break;
1186
				case 13: // enter
1187
					if (this.viewMode != 0) {
1188
						var oldViewMode = this.viewMode;
1189
						this.showMode(-1);
1190
						this.fill();
1191
						if (oldViewMode == this.viewMode && this.autoclose) {
1192
							this.hide();
1193
						}
1194
					} else {
1195
						this.fill();
1196
						if (this.autoclose) {
1197
							this.hide();
1198
						}
1199
					}
1200
					e.preventDefault();
1201
					break;
1202
				case 9: // tab
1203
					this.hide();
1204
					break;
1205
			}
1206
			if (dateChanged) {
1207
				var element;
1208
				if (this.isInput) {
1209
					element = this.element;
1210
				} else if (this.component) {
1211
					element = this.element.find('input');
1212
				}
1213
				if (element) {
1214
					element.change();
1215
				}
1216
				this.element.trigger({
1217
					type: 'changeDate',
1218
					date: this.date
1219
				});
1220
			}
1221
		},
1222

  
1223
		showMode: function (dir) {
1224
			if (dir) {
1225
				var newViewMode = Math.max(0, Math.min(DPGlobal.modes.length - 1, this.viewMode + dir));
1226
				if (newViewMode >= this.minView && newViewMode <= this.maxView) {
1227
					this.element.trigger({
1228
						type:        'changeMode',
1229
						date:        this.viewDate,
1230
						oldViewMode: this.viewMode,
1231
						newViewMode: newViewMode
1232
					});
1233

  
1234
					this.viewMode = newViewMode;
1235
				}
1236
			}
1237
			/*
1238
			 vitalets: fixing bug of very special conditions:
1239
			 jquery 1.7.1 + webkit + show inline datetimepicker in bootstrap popover.
1240
			 Method show() does not set display css correctly and datetimepicker is not shown.
1241
			 Changed to .css('display', 'block') solve the problem.
1242
			 See https://github.com/vitalets/x-editable/issues/37
1243

  
1244
			 In jquery 1.7.2+ everything works fine.
1245
			 */
1246
			//this.picker.find('>div').hide().filter('.datetimepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
1247
			this.picker.find('>div').hide().filter('.datetimepicker-' + DPGlobal.modes[this.viewMode].clsName).css('display', 'block');
1248
			this.updateNavArrows();
1249
		},
1250

  
1251
		reset: function (e) {
1252
			this._setDate(null, 'date');
1253
		},
1254

  
1255
		convertViewModeText:  function (viewMode) {
1256
			switch (viewMode) {
1257
				case 4:
1258
					return 'decade';
1259
				case 3:
1260
					return 'year';
1261
				case 2:
1262
					return 'month';
1263
				case 1:
1264
					return 'day';
1265
				case 0:
1266
					return 'hour';
1267
			}
1268
		}
1269
	};
1270

  
1271
	$.fn.datetimepicker = function (option) {
1272
		var args = Array.apply(null, arguments);
1273
		args.shift();
1274
		var internal_return;
1275
		this.each(function () {
1276
			var $this = $(this),
1277
				data = $this.data('datetimepicker'),
1278
				options = typeof option == 'object' && option;
1279
			if (!data) {
1280
				$this.data('datetimepicker', (data = new Datetimepicker(this, $.extend({}, $.fn.datetimepicker.defaults, options))));
1281
			}
1282
			if (typeof option == 'string' && typeof data[option] == 'function') {
1283
				internal_return = data[option].apply(data, args);
1284
				if (internal_return !== undefined) {
1285
					return false;
1286
				}
1287
			}
1288
		});
1289
		if (internal_return !== undefined)
1290
			return internal_return;
1291
		else
1292
			return this;
1293
	};
1294

  
1295
	$.fn.datetimepicker.defaults = {
1296
	};
1297
	$.fn.datetimepicker.Constructor = Datetimepicker;
1298
	var dates = $.fn.datetimepicker.dates = {
1299
		en: {
1300
			days:        ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
1301
			daysShort:   ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
1302
			daysMin:     ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
1303
			months:      ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
1304
			monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
1305
			meridiem:    ["am", "pm"],
1306
			suffix:      ["st", "nd", "rd", "th"],
1307
			today:       "Today"
1308
		}
1309
	};
1310

  
1311
	var DPGlobal = {
1312
		modes:            [
1313
			{
1314
				clsName: 'minutes',
1315
				navFnc:  'Hours',
1316
				navStep: 1
1317
			},
1318
			{
1319
				clsName: 'hours',
1320
				navFnc:  'Date',
1321
				navStep: 1
1322
			},
1323
			{
1324
				clsName: 'days',
1325
				navFnc:  'Month',
1326
				navStep: 1
1327
			},
1328
			{
1329
				clsName: 'months',
1330
				navFnc:  'FullYear',
1331
				navStep: 1
1332
			},
1333
			{
1334
				clsName: 'years',
1335
				navFnc:  'FullYear',
1336
				navStep: 10
1337
			}
1338
		],
1339
		isLeapYear:       function (year) {
1340
			return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
1341
		},
1342
		getDaysInMonth:   function (year, month) {
1343
			return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
1344
		},
1345
		getDefaultFormat: function (type, field) {
1346
			if (type == "standard") {
1347
				if (field == 'input')
1348
					return 'yyyy-mm-dd hh:ii';
1349
				else
1350
					return 'yyyy-mm-dd hh:ii:ss';
1351
			} else if (type == "php") {
1352
				if (field == 'input')
1353
					return 'Y-m-d H:i';
1354
				else
1355
					return 'Y-m-d H:i:s';
1356
			} else {
1357
				throw new Error("Invalid format type.");
1358
			}
1359
		},
1360
		validParts:       function (type) {
1361
			if (type == "standard") {
1362
				return /hh?|HH?|p|P|ii?|ss?|dd?|DD?|mm?|MM?|yy(?:yy)?/g;
1363
			} else if (type == "php") {
1364
				return /[dDjlNwzFmMnStyYaABgGhHis]/g;
1365
			} else {
1366
				throw new Error("Invalid format type.");
1367
			}
1368
		},
1369
		nonpunctuation:   /[^ -\/:-@\[-`{-~\t\n\rTZ]+/g,
1370
		parseFormat:      function (format, type) {
1371
			// IE treats \0 as a string end in inputs (truncating the value),
1372
			// so it's a bad format delimiter, anyway
1373
			var separators = format.replace(this.validParts(type), '\0').split('\0'),
1374
				parts = format.match(this.validParts(type));
1375
			if (!separators || !separators.length || !parts || parts.length == 0) {
1376
				throw new Error("Invalid date format.");
1377
			}
1378
			return {separators: separators, parts: parts};
1379
		},
1380
		parseDate:        function (date, format, language, type) {
1381
			if (date instanceof Date) {
1382
				var dateUTC = new Date(date.valueOf() - date.getTimezoneOffset() * 60000);
1383
				dateUTC.setMilliseconds(0);
1384
				return dateUTC;
1385
			}
1386
			if (/^\d{4}\-\d{1,2}\-\d{1,2}$/.test(date)) {
1387
				format = this.parseFormat('yyyy-mm-dd', type);
1388
			}
1389
			if (/^\d{4}\-\d{1,2}\-\d{1,2}[T ]\d{1,2}\:\d{1,2}$/.test(date)) {
1390
				format = this.parseFormat('yyyy-mm-dd hh:ii', type);
1391
			}
1392
			if (/^\d{4}\-\d{1,2}\-\d{1,2}[T ]\d{1,2}\:\d{1,2}\:\d{1,2}[Z]{0,1}$/.test(date)) {
1393
				format = this.parseFormat('yyyy-mm-dd hh:ii:ss', type);
1394
			}
1395
			if (/^[-+]\d+[dmwy]([\s,]+[-+]\d+[dmwy])*$/.test(date)) {
1396
				var part_re = /([-+]\d+)([dmwy])/,
1397
					parts = date.match(/([-+]\d+)([dmwy])/g),
1398
					part, dir;
1399
				date = new Date();
1400
				for (var i = 0; i < parts.length; i++) {
1401
					part = part_re.exec(parts[i]);
1402
					dir = parseInt(part[1]);
1403
					switch (part[2]) {
1404
						case 'd':
1405
							date.setUTCDate(date.getUTCDate() + dir);
1406
							break;
1407
						case 'm':
1408
							date = Datetimepicker.prototype.moveMonth.call(Datetimepicker.prototype, date, dir);
1409
							break;
1410
						case 'w':
1411
							date.setUTCDate(date.getUTCDate() + dir * 7);
1412
							break;
1413
						case 'y':
1414
							date = Datetimepicker.prototype.moveYear.call(Datetimepicker.prototype, date, dir);
1415
							break;
1416
					}
1417
				}
1418
				return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), 0);
1419
			}
1420
			var parts = date && date.match(this.nonpunctuation) || [],
1421
				date = new Date(0, 0, 0, 0, 0, 0, 0),
1422
				parsed = {},
1423
				setters_order = ['hh', 'h', 'ii', 'i', 'ss', 's', 'yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'D', 'DD', 'd', 'dd', 'H', 'HH', 'p', 'P'],
1424
				setters_map = {
1425
					hh:   function (d, v) {
1426
						return d.setUTCHours(v);
1427
					},
1428
					h:    function (d, v) {
1429
						return d.setUTCHours(v);
1430
					},
1431
					HH:   function (d, v) {
1432
						return d.setUTCHours(v == 12 ? 0 : v);
1433
					},
1434
					H:    function (d, v) {
1435
						return d.setUTCHours(v == 12 ? 0 : v);
1436
					},
1437
					ii:   function (d, v) {
1438
						return d.setUTCMinutes(v);
1439
					},
1440
					i:    function (d, v) {
1441
						return d.setUTCMinutes(v);
1442
					},
1443
					ss:   function (d, v) {
1444
						return d.setUTCSeconds(v);
1445
					},
1446
					s:    function (d, v) {
1447
						return d.setUTCSeconds(v);
1448
					},
1449
					yyyy: function (d, v) {
1450
						return d.setUTCFullYear(v);
1451
					},
1452
					yy:   function (d, v) {
1453
						return d.setUTCFullYear(2000 + v);
1454
					},
1455
					m:    function (d, v) {
1456
						v -= 1;
1457
						while (v < 0) v += 12;
1458
						v %= 12;
1459
						d.setUTCMonth(v);
1460
						while (d.getUTCMonth() != v)
1461
							if (isNaN(d.getUTCMonth()))
1462
								return d;
1463
							else
1464
								d.setUTCDate(d.getUTCDate() - 1);
1465
						return d;
1466
					},
1467
					d:    function (d, v) {
1468
						return d.setUTCDate(v);
1469
					},
1470
					p:    function (d, v) {
1471
						return d.setUTCHours(v == 1 ? d.getUTCHours() + 12 : d.getUTCHours());
1472
					}
1473
				},
1474
				val, filtered, part;
1475
			setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
1476
			setters_map['dd'] = setters_map['d'];
1477
			setters_map['P'] = setters_map['p'];
1478
			date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds());
1479
			if (parts.length == format.parts.length) {
1480
				for (var i = 0, cnt = format.parts.length; i < cnt; i++) {
1481
					val = parseInt(parts[i], 10);
1482
					part = format.parts[i];
1483
					if (isNaN(val)) {
1484
						switch (part) {
1485
							case 'MM':
1486
								filtered = $(dates[language].months).filter(function () {
1487
									var m = this.slice(0, parts[i].length),
1488
										p = parts[i].slice(0, m.length);
1489
									return m == p;
1490
								});
1491
								val = $.inArray(filtered[0], dates[language].months) + 1;
1492
								break;
1493
							case 'M':
1494
								filtered = $(dates[language].monthsShort).filter(function () {
1495
									var m = this.slice(0, parts[i].length),
1496
										p = parts[i].slice(0, m.length);
1497
									return m.toLowerCase() == p.toLowerCase();
1498
								});
1499
								val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
1500
								break;
1501
							case 'p':
1502
							case 'P':
1503
								val = $.inArray(parts[i].toLowerCase(), dates[language].meridiem);
1504
								break;
1505
						}
1506
					}
1507
					parsed[part] = val;
1508
				}
1509
				for (var i = 0, s; i < setters_order.length; i++) {
1510
					s = setters_order[i];
1511
					if (s in parsed && !isNaN(parsed[s]))
1512
						setters_map[s](date, parsed[s])
1513
				}
1514
			}
1515
			return date;
1516
		},
1517
		formatDate:       function (date, format, language, type) {
1518
			if (date == null) {
1519
				return '';
1520
			}
1521
			var val;
1522
			if (type == 'standard') {
1523
				val = {
1524
					// year
1525
					yy:   date.getUTCFullYear().toString().substring(2),
1526
					yyyy: date.getUTCFullYear(),
1527
					// month
1528
					m:    date.getUTCMonth() + 1,
1529
					M:    dates[language].monthsShort[date.getUTCMonth()],
1530
					MM:   dates[language].months[date.getUTCMonth()],
1531
					// day
1532
					d:    date.getUTCDate(),
1533
					D:    dates[language].daysShort[date.getUTCDay()],
1534
					DD:   dates[language].days[date.getUTCDay()],
1535
					p:    (dates[language].meridiem.length == 2 ? dates[language].meridiem[date.getUTCHours() < 12 ? 0 : 1] : ''),
1536
					// hour
1537
					h:    date.getUTCHours(),
1538
					// minute
1539
					i:    date.getUTCMinutes(),
1540
					// second
1541
					s:    date.getUTCSeconds()
1542
				};
1543

  
1544
				if (dates[language].meridiem.length == 2) {
1545
					val.H = (val.h % 12 == 0 ? 12 : val.h % 12);
1546
				}
1547
				else {
1548
					val.H = val.h;
1549
				}
1550
				val.HH = (val.H < 10 ? '0' : '') + val.H;
1551
				val.P = val.p.toUpperCase();
1552
				val.hh = (val.h < 10 ? '0' : '') + val.h;
1553
				val.ii = (val.i < 10 ? '0' : '') + val.i;
1554
				val.ss = (val.s < 10 ? '0' : '') + val.s;
1555
				val.dd = (val.d < 10 ? '0' : '') + val.d;
1556
				val.mm = (val.m < 10 ? '0' : '') + val.m;
1557
			} else if (type == 'php') {
1558
				// php format
1559
				val = {
1560
					// year
1561
					y: date.getUTCFullYear().toString().substring(2),
1562
					Y: date.getUTCFullYear(),
1563
					// month
1564
					F: dates[language].months[date.getUTCMonth()],
1565
					M: dates[language].monthsShort[date.getUTCMonth()],
1566
					n: date.getUTCMonth() + 1,
1567
					t: DPGlobal.getDaysInMonth(date.getUTCFullYear(), date.getUTCMonth()),
1568
					// day
1569
					j: date.getUTCDate(),
1570
					l: dates[language].days[date.getUTCDay()],
1571
					D: dates[language].daysShort[date.getUTCDay()],
1572
					w: date.getUTCDay(), // 0 -> 6
1573
					N: (date.getUTCDay() == 0 ? 7 : date.getUTCDay()),       // 1 -> 7
1574
					S: (date.getUTCDate() % 10 <= dates[language].suffix.length ? dates[language].suffix[date.getUTCDate() % 10 - 1] : ''),
1575
					// hour
1576
					a: (dates[language].meridiem.length == 2 ? dates[language].meridiem[date.getUTCHours() < 12 ? 0 : 1] : ''),
1577
					g: (date.getUTCHours() % 12 == 0 ? 12 : date.getUTCHours() % 12),
1578
					G: date.getUTCHours(),
1579
					// minute
1580
					i: date.getUTCMinutes(),
1581
					// second
1582
					s: date.getUTCSeconds()
1583
				};
1584
				val.m = (val.n < 10 ? '0' : '') + val.n;
1585
				val.d = (val.j < 10 ? '0' : '') + val.j;
1586
				val.A = val.a.toString().toUpperCase();
1587
				val.h = (val.g < 10 ? '0' : '') + val.g;
1588
				val.H = (val.G < 10 ? '0' : '') + val.G;
1589
				val.i = (val.i < 10 ? '0' : '') + val.i;
1590
				val.s = (val.s < 10 ? '0' : '') + val.s;
1591
			} else {
1592
				throw new Error("Invalid format type.");
1593
			}
1594
			var date = [],
1595
				seps = $.extend([], format.separators);
1596
			for (var i = 0, cnt = format.parts.length; i < cnt; i++) {
1597
				if (seps.length) {
1598
					date.push(seps.shift());
1599
				}
1600
				date.push(val[format.parts[i]]);
1601
			}
1602
			if (seps.length) {
1603
				date.push(seps.shift());
1604
			}
1605
			return date.join('');
1606
		},
1607
		convertViewMode:  function (viewMode) {
1608
			switch (viewMode) {
1609
				case 4:
1610
				case 'decade':
1611
					viewMode = 4;
1612
					break;
1613
				case 3:
1614
				case 'year':
1615
					viewMode = 3;
1616
					break;
1617
				case 2:
1618
				case 'month':
1619
					viewMode = 2;
1620
					break;
1621
				case 1:
1622
				case 'day':
1623
					viewMode = 1;
1624
					break;
1625
				case 0:
1626
				case 'hour':
1627
					viewMode = 0;
1628
					break;
1629
			}
1630

  
1631
			return viewMode;
1632
		},
1633
		headTemplate:     '<thead>' +
1634
							  '<tr>' +
1635
							  '<th class="prev"><i class="icon-arrow-left"/></th>' +
1636
							  '<th colspan="5" class="switch"></th>' +
1637
							  '<th class="next"><i class="icon-arrow-right"/></th>' +
1638
							  '</tr>' +
1639
			'</thead>',
1640
		headTemplateV3:   '<thead>' +
1641
							  '<tr>' +
1642
							  '<th class="prev"><span class="glyphicon glyphicon-arrow-left"></span> </th>' +
1643
							  '<th colspan="5" class="switch"></th>' +
1644
							  '<th class="next"><span class="glyphicon glyphicon-arrow-right"></span> </th>' +
1645
							  '</tr>' +
1646
			'</thead>',
1647
		contTemplate:     '<tbody><tr><td colspan="7"></td></tr></tbody>',
1648
		footTemplate:     '<tfoot><tr><th colspan="7" class="today"></th></tr></tfoot>'
1649
	};
1650
	DPGlobal.template = '<div class="datetimepicker">' +
1651
		'<div class="datetimepicker-minutes">' +
1652
		'<table class=" table-condensed">' +
1653
		DPGlobal.headTemplate +
1654
		DPGlobal.contTemplate +
1655
		DPGlobal.footTemplate +
1656
		'</table>' +
1657
		'</div>' +
1658
		'<div class="datetimepicker-hours">' +
1659
		'<table class=" table-condensed">' +
1660
		DPGlobal.headTemplate +
1661
		DPGlobal.contTemplate +
1662
		DPGlobal.footTemplate +
1663
		'</table>' +
1664
		'</div>' +
1665
		'<div class="datetimepicker-days">' +
1666
		'<table class=" table-condensed">' +
1667
		DPGlobal.headTemplate +
1668
		'<tbody></tbody>' +
1669
		DPGlobal.footTemplate +
1670
		'</table>' +
1671
		'</div>' +
1672
		'<div class="datetimepicker-months">' +
1673
		'<table class="table-condensed">' +
1674
		DPGlobal.headTemplate +
1675
		DPGlobal.contTemplate +
1676
		DPGlobal.footTemplate +
1677
		'</table>' +
1678
		'</div>' +
1679
		'<div class="datetimepicker-years">' +
1680
		'<table class="table-condensed">' +
1681
		DPGlobal.headTemplate +
1682
		DPGlobal.contTemplate +
1683
		DPGlobal.footTemplate +
1684
		'</table>' +
1685
		'</div>' +
1686
		'</div>';
1687
	DPGlobal.templateV3 = '<div class="datetimepicker">' +
1688
		'<div class="datetimepicker-minutes">' +
1689
		'<table class=" table-condensed">' +
1690
		DPGlobal.headTemplateV3 +
1691
		DPGlobal.contTemplate +
1692
		DPGlobal.footTemplate +
1693
		'</table>' +
1694
		'</div>' +
1695
		'<div class="datetimepicker-hours">' +
1696
		'<table class=" table-condensed">' +
1697
		DPGlobal.headTemplateV3 +
1698
		DPGlobal.contTemplate +
1699
		DPGlobal.footTemplate +
1700
		'</table>' +
1701
		'</div>' +
1702
		'<div class="datetimepicker-days">' +
1703
		'<table class=" table-condensed">' +
1704
		DPGlobal.headTemplateV3 +
1705
		'<tbody></tbody>' +
1706
		DPGlobal.footTemplate +
1707
		'</table>' +
1708
		'</div>' +
1709
		'<div class="datetimepicker-months">' +
1710
		'<table class="table-condensed">' +
1711
		DPGlobal.headTemplateV3 +
1712
		DPGlobal.contTemplate +
1713
		DPGlobal.footTemplate +
1714
		'</table>' +
1715
		'</div>' +
1716
		'<div class="datetimepicker-years">' +
1717
		'<table class="table-condensed">' +
1718
		DPGlobal.headTemplateV3 +
1719
		DPGlobal.contTemplate +
1720
		DPGlobal.footTemplate +
1721
		'</table>' +
1722
		'</div>' +
1723
		'</div>';
1724
	$.fn.datetimepicker.DPGlobal = DPGlobal;
1725

  
1726
	/* DATETIMEPICKER NO CONFLICT
1727
	 * =================== */
1728

  
1729
	$.fn.datetimepicker.noConflict = function () {
1730
		$.fn.datetimepicker = old;
1731
		return this;
1732
	};
1733

  
1734
	/* DATETIMEPICKER DATA-API
1735
	 * ================== */
1736

  
1737
	$(document).on(
1738
		'focus.datetimepicker.data-api click.datetimepicker.data-api',
1739
		'[data-provide="datetimepicker"]',
1740
		function (e) {
1741
			var $this = $(this);
1742
			if ($this.data('datetimepicker')) return;
1743
			e.preventDefault();
1744
			// component click requires us to explicitly show it
1745
			$this.datetimepicker('show');
1746
		}
1747
	);
1748
	$(function () {
1749
		$('[data-provide="datetimepicker-inline"]').datetimepicker();
1750
	});
1751

  
1752
}(window.jQuery);
src/authentic2/static/js/locales/bootstrap-datetimepicker.fr.js
1
/**
2
 * French translation for bootstrap-datetimepicker
3
 * Nico Mollet <nico.mollet@gmail.com>
4
 */
5
;(function($){
6
	$.fn.datetimepicker.dates['fr'] = {
7
		days: ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"],
8
		daysShort: ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"],
9
		daysMin: ["D", "L", "Ma", "Me", "J", "V", "S", "D"],
10
		months: ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
11
		monthsShort: ["Jan", "Fev", "Mar", "Avr", "Mai", "Jui", "Jul", "Aou", "Sep", "Oct", "Nov", "Dec"],
12
		today: "Aujourd'hui",
13
		suffix: [],
14
		meridiem: ["am", "pm"],
15
		weekStart: 1,
16
		format: "dd/mm/yyyy"
17
	};
18
}(jQuery));
src/authentic2/widgets.py
1
# Bootstrap django-datetime-widget is a simple and clean widget for DateField,
2
# Timefiled and DateTimeField in Django framework. It is based on Bootstrap
3
# datetime picker, supports Bootstrap 2
4
#
5
# https://github.com/asaglimbeni/django-datetime-widget
6
#
7
# License: BSD
8
# Initial Author: Alfredo Saglimbeni
9

  
10
import json
11
import re
12
import uuid
13

  
14
from django.forms.widgets import DateTimeInput, DateInput, TimeInput
15
from django.utils.formats import get_language
16
from django.utils.safestring import mark_safe
17

  
18
from gadjo.templatetags.gadjo import xstatic
19

  
20
DATE_FORMAT_JS_PY_MAPPING = {
21
    'P': '%p',
22
    'ss': '%S',
23
    'ii': '%M',
24
    'hh': '%H',
25
    'HH': '%I',
26
    'dd': '%d',
27
    'mm': '%m',
28
    'yy': '%y',
29
    'yyyy': '%Y',
30
}
31

  
32
DATE_FORMAT_TO_PYTHON_REGEX = re.compile(r'\b(' + '|'.join(DATE_FORMAT_JS_PY_MAPPING.keys()) + r')\b')
33

  
34

  
35
DATE_FORMAT_PY_JS_MAPPING = {
36
    '%M': 'ii',
37
    '%m': 'mm',
38
    '%I': 'HH',
39
    '%H': 'hh',
40
    '%d': 'dd',
41
    '%Y': 'yyyy',
42
    '%y': 'yy',
43
    '%p': 'P',
44
    '%S': 'ss'
45
}
46

  
47
DATE_FORMAT_TO_JS_REGEX = re.compile(r'(?<!\w)(' + '|'.join(DATE_FORMAT_PY_JS_MAPPING.keys()) + r')\b')
48

  
49

  
50
BOOTSTRAP_INPUT_TEMPLATE = """
51
       <div id="%(id)s"  class="controls input-append date">
52
           %(rendered_widget)s
53
           %(clear_button)s
54
           <span class="add-on"><i class="icon-th"></i></span>
55
       </div>
56
       <script type="text/javascript">
57
           $("#%(id)s").datetimepicker({%(options)s});
58
       </script>
59
       """
60

  
61
CLEAR_BTN_TEMPLATE = """<span class="add-on"><i class="icon-remove"></i></span>"""
62

  
63

  
64
class PickerWidgetMixin(object):
65
    class Media:
66
        css = {
67
            'all': ('css/datetimepicker.css',),
68
        }
69
        js = (
70
            xstatic('jquery', 'jquery.min.js'),
71
            xstatic('jquery_ui', 'jquery-ui.min.js'),
72
            'js/bootstrap-datetimepicker.js',
73
            'js/locales/bootstrap-datetimepicker.fr.js',
74
        )
75

  
76
    format_name = None
77
    glyphicon = None
78

  
79
    def __init__(self, attrs=None, options=None, usel10n=None):
80

  
81
        if attrs is None:
82
            attrs = {'readonly': ''}
83

  
84
        self.options = options
85
        self.options['language'] = get_language().split('-')[0]
86

  
87
        # We're not doing localisation, get the Javascript date format provided by the user,
88
        # with a default, and convert it to a Python data format for later string parsing
89
        date_format = self.options['format']
90
        self.format = DATE_FORMAT_TO_PYTHON_REGEX.sub(
91
            lambda x: DATE_FORMAT_JS_PY_MAPPING[x.group()],
92
            date_format
93
            )
94

  
95
        super(PickerWidgetMixin, self).__init__(attrs, format=self.format)
96

  
97
    def render(self, name, value, attrs=None):
98
        final_attrs = self.build_attrs(attrs)
99
        rendered_widget = super(PickerWidgetMixin, self).render(name, value, final_attrs)
100

  
101
        #if not set, autoclose have to be true.
102
        self.options.setdefault('autoclose', True)
103

  
104
        # Build javascript options out of python dictionary
105
        options_list = []
106
        for key, value in iter(self.options.items()):
107
            options_list.append("%s: %s" % (key, json.dumps(value)))
108

  
109
        js_options = ",\n".join(options_list)
110

  
111
        # Use provided id or generate hex to avoid collisions in document
112
        id = final_attrs.get('id', uuid.uuid4().hex)
113

  
114
        return mark_safe(BOOTSTRAP_INPUT_TEMPLATE % dict(
115
                    id=id,
116
                    rendered_widget=rendered_widget,
117
                    clear_button=CLEAR_BTN_TEMPLATE if self.options.get('clearBtn') else '',
118
                    glyphicon=self.glyphicon,
119
                    options=js_options
120
                    )
121
        )
122

  
123

  
124
class DateTimeWidget(PickerWidgetMixin, DateTimeInput):
125
    """
126
    DateTimeWidget is the corresponding widget for Datetime field, it renders both the date and time
127
    sections of the datetime picker.
128
    """
129

  
130
    format_name = 'DATETIME_INPUT_FORMATS'
131
    glyphicon = 'glyphicon-th'
132

  
133
    def __init__(self, attrs=None, options=None, usel10n=None):
134

  
135
        if options is None:
136
            options = {}
137

  
138
        # Set the default options to show only the datepicker object
139
        options['format'] = options.get('format', 'dd/mm/yyyy hh:ii')
140

  
141
        super(DateTimeWidget, self).__init__(attrs, options, usel10n)
142

  
143

  
144
class DateWidget(PickerWidgetMixin, DateInput):
145
    """
146
    DateWidget is the corresponding widget for Date field, it renders only the date section of
147
    datetime picker.
148
    """
149

  
150
    format_name = 'DATE_INPUT_FORMATS'
151
    glyphicon = 'glyphicon-calendar'
152

  
153
    def __init__(self, attrs=None, options=None, usel10n=None):
154

  
155
        if options is None:
156
            options = {}
157

  
158
        # Set the default options to show only the datepicker object
159
        options['startView'] = options.get('startView', 2)
160
        options['minView'] = options.get('minView', 2)
161
        options['format'] = options.get('format', 'dd/mm/yyyy')
162

  
163
        super(DateWidget, self).__init__(attrs, options, usel10n)
164

  
165

  
166
class TimeWidget(PickerWidgetMixin, TimeInput):
167
    """
168
    TimeWidget is the corresponding widget for Time field, it renders only the time section of
169
    datetime picker.
170
    """
171

  
172
    format_name = 'TIME_INPUT_FORMATS'
173
    glyphicon = 'glyphicon-time'
174

  
175
    def __init__(self, attrs=None, options=None, usel10n=None):
176

  
177
        if options is None:
178
            options = {}
179

  
180
        # Set the default options to show only the timepicker object
181
        options['startView'] = options.get('startView', 1)
182
        options['minView'] = options.get('minView', 0)
183
        options['maxView'] = options.get('maxView', 1)
184
        options['format'] = options.get('format', 'hh:ii')
185

  
186
        super(TimeWidget, self).__init__(attrs, options, usel10n)
tests/test_widget_datetimepicker.py
1
from authentic2.widgets import DateTimeWidget, DateWidget, TimeWidget
2

  
3
def test_widgets_init():
4
    DateTimeWidget()
5
    DateWidget()
6
    TimeWidget()
0
-