effective_form_inputs 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 98b06e8541683a63c88431d726aef55d80f22f42
4
+ data.tar.gz: beed34183584c0e589488afd005436e5d37df9ab
5
+ SHA512:
6
+ metadata.gz: adae80480b8caf15d1ca19548eedbb4587ef6d850deb1085ead6ca7fd373eea4bd64ef25e99e43ba41cbc06dca906faebe3917347abd75aae919a7c7e6d06f40
7
+ data.tar.gz: 1038b9a5f82d18b58fef6ef255e95ef7d6f4d13a7c68b78c736f66933a74f29cf439e97eaa9b66d3f3486b1f688bdb645b0e09670941e9f6a9ef5919e70e6352
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Code and Effect Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # Effective Form Inputs
2
+
3
+ This gem is in active development and should be considered BETA
4
+
5
+ The purpose of this gem is to house a whole bunch of Javascript form inputs that can then be all at once brought into a Rails app
6
+
7
+ Each form input will be a Rails FormBuilder and a simple_form input
8
+
9
+ Right now there's just one form input -- the bootstrap3 datepicker
10
+
11
+ ## Installation
12
+
13
+ bundle install the gem
14
+
15
+ No rails generater to run
16
+
17
+ If you want to use all inputs:
18
+
19
+ //= require effective_form_inputs
20
+ @import 'effective_form_inputs';
21
+
22
+ Or just one:
23
+
24
+ //= require effective_date_time_picker/input
25
+ @import 'effective_date_time_picker/input';
26
+
27
+ ## Bootstrap3 Datepicker
28
+
29
+ https://github.com/Eonasdan/bootstrap-datetimepicker
30
+
31
+ As a rails FormBuilder
32
+
33
+ = f.effective_date_time_picker :updated_at
34
+
35
+ As a SimpleForm input
36
+
37
+ = f.input :updated_at, :as => :effective_date_time_picker
38
+
39
+ ## TODO
40
+
41
+ Write a proper README.
42
+
43
+ ## License
44
+
45
+ MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/)
46
+
47
+ Code and Effect is the product arm of [AgileStyle](http://www.agilestyle.com/), an Edmonton-based shop that specializes in building custom web applications with Ruby on Rails.
48
+
49
+
50
+ ## Credits
51
+
52
+
53
+ ## Testing
54
+
55
+ The test suite for this gem is unfortunately not yet complete.
56
+
57
+ Run tests by:
58
+
59
+ ```ruby
60
+ rake spec
61
+ ```
62
+
63
+
64
+ ## Contributing
65
+
66
+ 1. Fork it
67
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
68
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
69
+ 4. Push to the branch (`git push origin my-new-feature`)
70
+ 5. Bonus points for test coverage
71
+ 6. Create new Pull Request
72
+
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ # Testing tasks
9
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
10
+ load 'rails/tasks/engine.rake'
11
+
12
+ Bundler::GemHelper.install_tasks
13
+
14
+ require 'rspec/core'
15
+ require 'rspec/core/rake_task'
16
+
17
+ desc "Run all specs in spec directory (excluding plugin specs)"
18
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
19
+
20
+ task :default => :spec
@@ -0,0 +1,1700 @@
1
+ /*
2
+ //! version : 4.0.0
3
+ =========================================================
4
+ bootstrap-datetimejs
5
+ https://github.com/Eonasdan/bootstrap-datetimepicker
6
+ =========================================================
7
+ The MIT License (MIT)
8
+
9
+ Copyright (c) 2015 Jonathan Peterson
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in
19
+ all copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
+ THE SOFTWARE.
28
+ */
29
+ (function (factory) {
30
+ 'use strict';
31
+ if (typeof define === 'function' && define.amd) {
32
+ // AMD is used - Register as an anonymous module.
33
+ define(['jquery', 'moment'], factory);
34
+ } else if (typeof exports === 'object') {
35
+ factory(require('jquery'), require('moment'));
36
+ } else {
37
+ // Neither AMD nor CommonJS used. Use global variables.
38
+ if (!jQuery) {
39
+ throw 'bootstrap-datetimepicker requires jQuery to be loaded first';
40
+ }
41
+ if (!moment) {
42
+ throw 'bootstrap-datetimepicker requires Moment.js to be loaded first';
43
+ }
44
+ factory(jQuery, moment);
45
+ }
46
+ }(function ($, moment) {
47
+ 'use strict';
48
+ if (!moment) {
49
+ throw new Error('bootstrap-datetimepicker requires Moment.js to be loaded first');
50
+ }
51
+
52
+ var dateTimePicker = function (element, options) {
53
+ var picker = {},
54
+ date = moment(),
55
+ viewDate = date.clone(),
56
+ unset = true,
57
+ input,
58
+ component = false,
59
+ widget = false,
60
+ use24Hours,
61
+ minViewModeNumber = 0,
62
+ actualFormat,
63
+ parseFormats,
64
+ currentViewMode,
65
+ datePickerModes = [
66
+ {
67
+ clsName: 'days',
68
+ navFnc: 'M',
69
+ navStep: 1
70
+ },
71
+ {
72
+ clsName: 'months',
73
+ navFnc: 'y',
74
+ navStep: 1
75
+ },
76
+ {
77
+ clsName: 'years',
78
+ navFnc: 'y',
79
+ navStep: 10
80
+ }
81
+ ],
82
+ viewModes = ['days', 'months', 'years'],
83
+ verticalModes = ['top', 'bottom', 'auto'],
84
+ horizontalModes = ['left', 'right', 'auto'],
85
+ toolbarPlacements = ['default', 'top', 'bottom'],
86
+
87
+ /********************************************************************************
88
+ *
89
+ * Private functions
90
+ *
91
+ ********************************************************************************/
92
+ isEnabled = function (granularity) {
93
+ if (typeof granularity !== 'string' || granularity.length > 1) {
94
+ throw new TypeError('isEnabled expects a single character string parameter');
95
+ }
96
+ switch (granularity) {
97
+ case 'y':
98
+ return actualFormat.indexOf('Y') !== -1;
99
+ case 'M':
100
+ return actualFormat.indexOf('M') !== -1;
101
+ case 'd':
102
+ return actualFormat.toLowerCase().indexOf('d') !== -1;
103
+ case 'h':
104
+ case 'H':
105
+ return actualFormat.toLowerCase().indexOf('h') !== -1;
106
+ case 'm':
107
+ return actualFormat.indexOf('m') !== -1;
108
+ case 's':
109
+ return actualFormat.indexOf('s') !== -1;
110
+ default:
111
+ return false;
112
+ }
113
+ },
114
+
115
+ hasTime = function () {
116
+ return (isEnabled('h') || isEnabled('m') || isEnabled('s'));
117
+ },
118
+
119
+ hasDate = function () {
120
+ return (isEnabled('y') || isEnabled('M') || isEnabled('d'));
121
+ },
122
+
123
+ getDatePickerTemplate = function () {
124
+ var headTemplate = $('<thead>')
125
+ .append($('<tr>')
126
+ .append($('<th>').addClass('prev').attr('data-action', 'previous')
127
+ .append($('<span>').addClass(options.icons.previous))
128
+ )
129
+ .append($('<th>').addClass('picker-switch').attr('data-action', 'pickerSwitch').attr('colspan', (options.calendarWeeks ? '6' : '5')))
130
+ .append($('<th>').addClass('next').attr('data-action', 'next')
131
+ .append($('<span>').addClass(options.icons.next))
132
+ )
133
+ ),
134
+ contTemplate = $('<tbody>')
135
+ .append($('<tr>')
136
+ .append($('<td>').attr('colspan', (options.calendarWeeks ? '8' : '7')))
137
+ );
138
+
139
+ return [
140
+ $('<div>').addClass('datepicker-days')
141
+ .append($('<table>').addClass('table-condensed')
142
+ .append(headTemplate)
143
+ .append($('<tbody>'))
144
+ ),
145
+ $('<div>').addClass('datepicker-months')
146
+ .append($('<table>').addClass('table-condensed')
147
+ .append(headTemplate.clone())
148
+ .append(contTemplate.clone())
149
+ ),
150
+ $('<div>').addClass('datepicker-years')
151
+ .append($('<table>').addClass('table-condensed')
152
+ .append(headTemplate.clone())
153
+ .append(contTemplate.clone())
154
+ )
155
+ ];
156
+ },
157
+
158
+ getTimePickerMainTemplate = function () {
159
+ var topRow = $('<tr>'),
160
+ middleRow = $('<tr>'),
161
+ bottomRow = $('<tr>');
162
+
163
+ if (isEnabled('h')) {
164
+ topRow.append($('<td>')
165
+ .append($('<a>').attr('href', '#').addClass('btn').attr('data-action', 'incrementHours')
166
+ .append($('<span>').addClass(options.icons.up))));
167
+ middleRow.append($('<td>')
168
+ .append($('<span>').addClass('timepicker-hour').attr('data-time-component', 'hours').attr('data-action', 'showHours')));
169
+ bottomRow.append($('<td>')
170
+ .append($('<a>').attr('href', '#').addClass('btn').attr('data-action', 'decrementHours')
171
+ .append($('<span>').addClass(options.icons.down))));
172
+ }
173
+ if (isEnabled('m')) {
174
+ if (isEnabled('h')) {
175
+ topRow.append($('<td>').addClass('separator'));
176
+ middleRow.append($('<td>').addClass('separator').html(':'));
177
+ bottomRow.append($('<td>').addClass('separator'));
178
+ }
179
+ topRow.append($('<td>')
180
+ .append($('<a>').attr('href', '#').addClass('btn').attr('data-action', 'incrementMinutes')
181
+ .append($('<span>').addClass(options.icons.up))));
182
+ middleRow.append($('<td>')
183
+ .append($('<span>').addClass('timepicker-minute').attr('data-time-component', 'minutes').attr('data-action', 'showMinutes')));
184
+ bottomRow.append($('<td>')
185
+ .append($('<a>').attr('href', '#').addClass('btn').attr('data-action', 'decrementMinutes')
186
+ .append($('<span>').addClass(options.icons.down))));
187
+ }
188
+ if (isEnabled('s')) {
189
+ if (isEnabled('m')) {
190
+ topRow.append($('<td>').addClass('separator'));
191
+ middleRow.append($('<td>').addClass('separator').html(':'));
192
+ bottomRow.append($('<td>').addClass('separator'));
193
+ }
194
+ topRow.append($('<td>')
195
+ .append($('<a>').attr('href', '#').addClass('btn').attr('data-action', 'incrementSeconds')
196
+ .append($('<span>').addClass(options.icons.up))));
197
+ middleRow.append($('<td>')
198
+ .append($('<span>').addClass('timepicker-second').attr('data-time-component', 'seconds').attr('data-action', 'showSeconds')));
199
+ bottomRow.append($('<td>')
200
+ .append($('<a>').attr('href', '#').addClass('btn').attr('data-action', 'decrementSeconds')
201
+ .append($('<span>').addClass(options.icons.down))));
202
+ }
203
+
204
+ if (!use24Hours) {
205
+ topRow.append($('<td>').addClass('separator'));
206
+ middleRow.append($('<td>')
207
+ .append($('<button>').addClass('btn btn-primary').attr('data-action', 'togglePeriod')));
208
+ bottomRow.append($('<td>').addClass('separator'));
209
+ }
210
+
211
+ return $('<div>').addClass('timepicker-picker')
212
+ .append($('<table>').addClass('table-condensed')
213
+ .append([topRow, middleRow, bottomRow]));
214
+ },
215
+
216
+ getTimePickerTemplate = function () {
217
+ var hoursView = $('<div>').addClass('timepicker-hours')
218
+ .append($('<table>').addClass('table-condensed')),
219
+ minutesView = $('<div>').addClass('timepicker-minutes')
220
+ .append($('<table>').addClass('table-condensed')),
221
+ secondsView = $('<div>').addClass('timepicker-seconds')
222
+ .append($('<table>').addClass('table-condensed')),
223
+ ret = [getTimePickerMainTemplate()];
224
+
225
+ if (isEnabled('h')) {
226
+ ret.push(hoursView);
227
+ }
228
+ if (isEnabled('m')) {
229
+ ret.push(minutesView);
230
+ }
231
+ if (isEnabled('s')) {
232
+ ret.push(secondsView);
233
+ }
234
+
235
+ return ret;
236
+ },
237
+
238
+ getToolbar = function () {
239
+ var row = [];
240
+ if (options.showTodayButton) {
241
+ row.push($('<td>').append($('<a>').attr('data-action', 'today').append($('<span>').addClass(options.icons.today))));
242
+ }
243
+ if (!options.sideBySide && hasDate() && hasTime()) {
244
+ row.push($('<td>').append($('<a>').attr('data-action', 'togglePicker').append($('<span>').addClass(options.icons.time))));
245
+ }
246
+ if (options.showClear) {
247
+ row.push($('<td>').append($('<a>').attr('data-action', 'clear').append($('<span>').addClass(options.icons.clear))));
248
+ }
249
+ return $('<table>').addClass('table-condensed').append($('<tbody>').append($('<tr>').append(row)));
250
+ },
251
+
252
+ getTemplate = function () {
253
+ var template = $('<div>').addClass('bootstrap-datetimepicker-widget dropdown-menu'),
254
+ dateView = $('<div>').addClass('datepicker').append(getDatePickerTemplate()),
255
+ timeView = $('<div>').addClass('timepicker').append(getTimePickerTemplate()),
256
+ content = $('<ul>').addClass('list-unstyled'),
257
+ toolbar = $('<li>').addClass('picker-switch' + (options.collapse ? ' accordion-toggle' : '')).append(getToolbar());
258
+
259
+ if (use24Hours) {
260
+ template.addClass('usetwentyfour');
261
+ }
262
+ if (options.sideBySide && hasDate() && hasTime()) {
263
+ template.addClass('timepicker-sbs');
264
+ template.append(
265
+ $('<div>').addClass('row')
266
+ .append(dateView.addClass('col-sm-6'))
267
+ .append(timeView.addClass('col-sm-6'))
268
+ );
269
+ template.append(toolbar);
270
+ return template;
271
+ }
272
+
273
+ if (options.toolbarPlacement === 'top') {
274
+ content.append(toolbar);
275
+ }
276
+ if (hasDate()) {
277
+ content.append($('<li>').addClass((options.collapse && hasTime() ? 'collapse in' : '')).append(dateView));
278
+ }
279
+ if (options.toolbarPlacement === 'default') {
280
+ content.append(toolbar);
281
+ }
282
+ if (hasTime()) {
283
+ content.append($('<li>').addClass((options.collapse && hasDate() ? 'collapse' : '')).append(timeView));
284
+ }
285
+ if (options.toolbarPlacement === 'bottom') {
286
+ content.append(toolbar);
287
+ }
288
+ return template.append(content);
289
+ },
290
+
291
+ dataToOptions = function () {
292
+ var eData = element.data(),
293
+ dataOptions = {};
294
+
295
+ if (eData.dateOptions && eData.dateOptions instanceof Object) {
296
+ dataOptions = $.extend(true, dataOptions, eData.dateOptions);
297
+ }
298
+
299
+ $.each(options, function (key) {
300
+ var attributeName = 'date' + key.charAt(0).toUpperCase() + key.slice(1);
301
+ if (eData[attributeName] !== undefined) {
302
+ dataOptions[key] = eData[attributeName];
303
+ }
304
+ });
305
+ return dataOptions;
306
+ },
307
+
308
+ place = function () {
309
+ var offset = (component || element).position(),
310
+ vertical = options.widgetPositioning.vertical,
311
+ horizontal = options.widgetPositioning.horizontal,
312
+ parent;
313
+
314
+ if (options.widgetParent) {
315
+ parent = options.widgetParent.append(widget);
316
+ } else if (element.is('input')) {
317
+ parent = element.parent().append(widget);
318
+ } else {
319
+ parent = element;
320
+ element.children().first().after(widget);
321
+ }
322
+
323
+ // Top and bottom logic
324
+ if (vertical === 'auto') {
325
+ if ((component || element).offset().top + widget.height() > $(window).height() + $(window).scrollTop() &&
326
+ widget.height() + element.outerHeight() < (component || element).offset().top) {
327
+ vertical = 'top';
328
+ } else {
329
+ vertical = 'bottom';
330
+ }
331
+ }
332
+
333
+ // Left and right logic
334
+ if (horizontal === 'auto') {
335
+ if (parent.width() < offset.left + widget.outerWidth()) {
336
+ horizontal = 'right';
337
+ } else {
338
+ horizontal = 'left';
339
+ }
340
+ }
341
+
342
+ if (vertical === 'top') {
343
+ widget.addClass('top').removeClass('bottom');
344
+ } else {
345
+ widget.addClass('bottom').removeClass('top');
346
+ }
347
+
348
+ if (horizontal === 'right') {
349
+ widget.addClass('pull-right');
350
+ } else {
351
+ widget.removeClass('pull-right');
352
+ }
353
+
354
+ // find the first parent element that has a relative css positioning
355
+ if (parent.css('position') !== 'relative') {
356
+ parent = parent.parents().filter(function () {
357
+ return $(this).css('position') === 'relative';
358
+ }).first();
359
+ }
360
+
361
+ if (parent.length === 0) {
362
+ throw new Error('datetimepicker component should be placed within a relative positioned container');
363
+ }
364
+
365
+ widget.css({
366
+ top: vertical === 'top' ? 'auto' : offset.top + element.outerHeight(),
367
+ bottom: vertical === 'top' ? offset.top + element.outerHeight() : 'auto',
368
+ left: horizontal === 'left' ? parent.css('padding-left') : 'auto',
369
+ right: horizontal === 'left' ? 'auto' : parent.css('padding-right')
370
+ });
371
+ },
372
+
373
+ notifyEvent = function (e) {
374
+ if (e.type === 'dp.change' && ((e.date && e.date.isSame(e.oldDate)) || (!e.date && !e.oldDate))) {
375
+ return;
376
+ }
377
+ element.trigger(e);
378
+ },
379
+
380
+ showMode = function (dir) {
381
+ if (!widget) {
382
+ return;
383
+ }
384
+ if (dir) {
385
+ currentViewMode = Math.max(minViewModeNumber, Math.min(2, currentViewMode + dir));
386
+ }
387
+ widget.find('.datepicker > div').hide().filter('.datepicker-' + datePickerModes[currentViewMode].clsName).show();
388
+ },
389
+
390
+ fillDow = function () {
391
+ var row = $('<tr>'),
392
+ currentDate = viewDate.clone().startOf('w');
393
+
394
+ if (options.calendarWeeks === true) {
395
+ row.append($('<th>').addClass('cw').text('#'));
396
+ }
397
+
398
+ while (currentDate.isBefore(viewDate.clone().endOf('w'))) {
399
+ row.append($('<th>').addClass('dow').text(currentDate.format('dd')));
400
+ currentDate.add(1, 'd');
401
+ }
402
+ widget.find('.datepicker-days thead').append(row);
403
+ },
404
+
405
+ isInDisabledDates = function (date) {
406
+ if (!options.disabledDates) {
407
+ return false;
408
+ }
409
+ return options.disabledDates[date.format('YYYY-MM-DD')] === true;
410
+ },
411
+
412
+ isInEnabledDates = function (date) {
413
+ if (!options.enabledDates) {
414
+ return false;
415
+ }
416
+ return options.enabledDates[date.format('YYYY-MM-DD')] === true;
417
+ },
418
+
419
+ isValid = function (targetMoment, granularity) {
420
+ if (!targetMoment.isValid()) {
421
+ return false;
422
+ }
423
+ if (options.disabledDates && isInDisabledDates(targetMoment)) {
424
+ return false;
425
+ }
426
+ if (options.enabledDates && isInEnabledDates(targetMoment)) {
427
+ return true;
428
+ }
429
+ if (options.minDate && targetMoment.isBefore(options.minDate, granularity)) {
430
+ return false;
431
+ }
432
+ if (options.maxDate && targetMoment.isAfter(options.maxDate, granularity)) {
433
+ return false;
434
+ }
435
+ if (granularity === 'd' && options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) {
436
+ return false;
437
+ }
438
+ return true;
439
+ },
440
+
441
+ fillMonths = function () {
442
+ var spans = [],
443
+ monthsShort = viewDate.clone().startOf('y').hour(12); // hour is changed to avoid DST issues in some browsers
444
+ while (monthsShort.isSame(viewDate, 'y')) {
445
+ spans.push($('<span>').attr('data-action', 'selectMonth').addClass('month').text(monthsShort.format('MMM')));
446
+ monthsShort.add(1, 'M');
447
+ }
448
+ widget.find('.datepicker-months td').empty().append(spans);
449
+ },
450
+
451
+ updateMonths = function () {
452
+ var monthsView = widget.find('.datepicker-months'),
453
+ monthsViewHeader = monthsView.find('th'),
454
+ months = monthsView.find('tbody').find('span');
455
+
456
+ monthsView.find('.disabled').removeClass('disabled');
457
+
458
+ if (!isValid(viewDate.clone().subtract(1, 'y'), 'y')) {
459
+ monthsViewHeader.eq(0).addClass('disabled');
460
+ }
461
+
462
+ monthsViewHeader.eq(1).text(viewDate.year());
463
+
464
+ if (!isValid(viewDate.clone().add(1, 'y'), 'y')) {
465
+ monthsViewHeader.eq(2).addClass('disabled');
466
+ }
467
+
468
+ months.removeClass('active');
469
+ if (date.isSame(viewDate, 'y')) {
470
+ months.eq(date.month()).addClass('active');
471
+ }
472
+
473
+ months.each(function (index) {
474
+ if (!isValid(viewDate.clone().month(index), 'M')) {
475
+ $(this).addClass('disabled');
476
+ }
477
+ });
478
+ },
479
+
480
+ updateYears = function () {
481
+ var yearsView = widget.find('.datepicker-years'),
482
+ yearsViewHeader = yearsView.find('th'),
483
+ startYear = viewDate.clone().subtract(5, 'y'),
484
+ endYear = viewDate.clone().add(6, 'y'),
485
+ html = '';
486
+
487
+ yearsView.find('.disabled').removeClass('disabled');
488
+
489
+ if (options.minDate && options.minDate.isAfter(startYear, 'y')) {
490
+ yearsViewHeader.eq(0).addClass('disabled');
491
+ }
492
+
493
+ yearsViewHeader.eq(1).text(startYear.year() + '-' + endYear.year());
494
+
495
+ if (options.maxDate && options.maxDate.isBefore(endYear, 'y')) {
496
+ yearsViewHeader.eq(2).addClass('disabled');
497
+ }
498
+
499
+ while (!startYear.isAfter(endYear, 'y')) {
500
+ html += '<span data-action="selectYear" class="year' + (startYear.isSame(date, 'y') ? ' active' : '') + (!isValid(startYear, 'y') ? ' disabled' : '') + '">' + startYear.year() + '</span>';
501
+ startYear.add(1, 'y');
502
+ }
503
+
504
+ yearsView.find('td').html(html);
505
+ },
506
+
507
+ fillDate = function () {
508
+ var daysView = widget.find('.datepicker-days'),
509
+ daysViewHeader = daysView.find('th'),
510
+ currentDate,
511
+ html = [],
512
+ row,
513
+ clsName;
514
+
515
+ if (!hasDate()) {
516
+ return;
517
+ }
518
+
519
+ daysView.find('.disabled').removeClass('disabled');
520
+ daysViewHeader.eq(1).text(viewDate.format(options.dayViewHeaderFormat));
521
+
522
+ if (!isValid(viewDate.clone().subtract(1, 'M'), 'M')) {
523
+ daysViewHeader.eq(0).addClass('disabled');
524
+ }
525
+ if (!isValid(viewDate.clone().add(1, 'M'), 'M')) {
526
+ daysViewHeader.eq(2).addClass('disabled');
527
+ }
528
+
529
+ currentDate = viewDate.clone().startOf('M').startOf('week');
530
+
531
+ while (!viewDate.clone().endOf('M').endOf('w').isBefore(currentDate, 'd')) {
532
+ if (currentDate.weekday() === 0) {
533
+ row = $('<tr>');
534
+ if (options.calendarWeeks) {
535
+ row.append('<td class="cw">' + currentDate.week() + '</td>');
536
+ }
537
+ html.push(row);
538
+ }
539
+ clsName = '';
540
+ if (currentDate.isBefore(viewDate, 'M')) {
541
+ clsName += ' old';
542
+ }
543
+ if (currentDate.isAfter(viewDate, 'M')) {
544
+ clsName += ' new';
545
+ }
546
+ if (currentDate.isSame(date, 'd') && !unset) {
547
+ clsName += ' active';
548
+ }
549
+ if (!isValid(currentDate, 'd')) {
550
+ clsName += ' disabled';
551
+ }
552
+ if (currentDate.isSame(moment(), 'd')) {
553
+ clsName += ' today';
554
+ }
555
+ if (currentDate.day() === 0 || currentDate.day() === 6) {
556
+ clsName += ' weekend';
557
+ }
558
+ row.append('<td data-action="selectDay" class="day' + clsName + '">' + currentDate.date() + '</td>');
559
+ currentDate.add(1, 'd');
560
+ }
561
+
562
+ daysView.find('tbody').empty().append(html);
563
+
564
+ updateMonths();
565
+
566
+ updateYears();
567
+ },
568
+
569
+ fillHours = function () {
570
+ var table = widget.find('.timepicker-hours table'),
571
+ currentHour = viewDate.clone().startOf('d'),
572
+ html = [],
573
+ row = $('<tr>');
574
+
575
+ if (viewDate.hour() > 11 && !use24Hours) {
576
+ currentHour.hour(12);
577
+ }
578
+ while (currentHour.isSame(viewDate, 'd') && (use24Hours || (viewDate.hour() < 12 && currentHour.hour() < 12) || viewDate.hour() > 11)) {
579
+ if (currentHour.hour() % 4 === 0) {
580
+ row = $('<tr>');
581
+ html.push(row);
582
+ }
583
+ row.append('<td data-action="selectHour" class="hour' + (!isValid(currentHour, 'h') ? ' disabled' : '') + '">' + currentHour.format(use24Hours ? 'HH' : 'hh') + '</td>');
584
+ currentHour.add(1, 'h');
585
+ }
586
+ table.empty().append(html);
587
+ },
588
+
589
+ fillMinutes = function () {
590
+ var table = widget.find('.timepicker-minutes table'),
591
+ currentMinute = viewDate.clone().startOf('h'),
592
+ html = [],
593
+ row = $('<tr>'),
594
+ step = options.stepping === 1 ? 5 : options.stepping;
595
+
596
+ while (viewDate.isSame(currentMinute, 'h')) {
597
+ if (currentMinute.minute() % (step * 4) === 0) {
598
+ row = $('<tr>');
599
+ html.push(row);
600
+ }
601
+ row.append('<td data-action="selectMinute" class="minute' + (!isValid(currentMinute, 'm') ? ' disabled' : '') + '">' + currentMinute.format('mm') + '</td>');
602
+ currentMinute.add(step, 'm');
603
+ }
604
+ table.empty().append(html);
605
+ },
606
+
607
+ fillSeconds = function () {
608
+ var table = widget.find('.timepicker-seconds table'),
609
+ currentSecond = viewDate.clone().startOf('m'),
610
+ html = [],
611
+ row = $('<tr>');
612
+
613
+ while (viewDate.isSame(currentSecond, 'm')) {
614
+ if (currentSecond.second() % 20 === 0) {
615
+ row = $('<tr>');
616
+ html.push(row);
617
+ }
618
+ row.append('<td data-action="selectSecond" class="second' + (!isValid(currentSecond, 's') ? ' disabled' : '') + '">' + currentSecond.format('ss') + '</td>');
619
+ currentSecond.add(5, 's');
620
+ }
621
+
622
+ table.empty().append(html);
623
+ },
624
+
625
+ fillTime = function () {
626
+ var timeComponents = widget.find('.timepicker span[data-time-component]');
627
+ if (!use24Hours) {
628
+ widget.find('.timepicker [data-action=togglePeriod]').text(date.format('A'));
629
+ }
630
+ timeComponents.filter('[data-time-component=hours]').text(date.format(use24Hours ? 'HH' : 'hh'));
631
+ timeComponents.filter('[data-time-component=minutes]').text(date.format('mm'));
632
+ timeComponents.filter('[data-time-component=seconds]').text(date.format('ss'));
633
+
634
+ fillHours();
635
+ fillMinutes();
636
+ fillSeconds();
637
+ },
638
+
639
+ update = function () {
640
+ if (!widget) {
641
+ return;
642
+ }
643
+ fillDate();
644
+ fillTime();
645
+ },
646
+
647
+ setValue = function (targetMoment) {
648
+ var oldDate = unset ? null : date;
649
+
650
+ // case of calling setValue(null or false)
651
+ if (!targetMoment) {
652
+ unset = true;
653
+ input.val('');
654
+ element.data('date', '');
655
+ notifyEvent({
656
+ type: 'dp.change',
657
+ date: null,
658
+ oldDate: oldDate
659
+ });
660
+ update();
661
+ return;
662
+ }
663
+
664
+ targetMoment = targetMoment.clone().locale(options.locale);
665
+
666
+ if (options.stepping !== 1) {
667
+ targetMoment.minutes((Math.round(targetMoment.minutes() / options.stepping) * options.stepping) % 60).seconds(0);
668
+ }
669
+
670
+ if (isValid(targetMoment)) {
671
+ date = targetMoment;
672
+ viewDate = date.clone();
673
+ input.val(date.format(actualFormat));
674
+ element.data('date', date.format(actualFormat));
675
+ update();
676
+ unset = false;
677
+ notifyEvent({
678
+ type: 'dp.change',
679
+ date: date.clone(),
680
+ oldDate: oldDate
681
+ });
682
+ } else {
683
+ input.val(unset ? '' : date.format(actualFormat));
684
+ notifyEvent({
685
+ type: 'dp.error',
686
+ date: targetMoment
687
+ });
688
+ }
689
+ },
690
+
691
+ hide = function () {
692
+ var transitioning = false;
693
+ if (!widget) {
694
+ return picker;
695
+ }
696
+ // Ignore event if in the middle of a picker transition
697
+ widget.find('.collapse').each(function () {
698
+ var collapseData = $(this).data('collapse');
699
+ if (collapseData && collapseData.transitioning) {
700
+ transitioning = true;
701
+ return false;
702
+ }
703
+ });
704
+ if (transitioning) {
705
+ return picker;
706
+ }
707
+ if (component && component.hasClass('btn')) {
708
+ component.toggleClass('active');
709
+ }
710
+ widget.hide();
711
+
712
+ $(window).off('resize', place);
713
+ widget.off('click', '[data-action]');
714
+ widget.off('mousedown', false);
715
+
716
+ widget.remove();
717
+ widget = false;
718
+
719
+ notifyEvent({
720
+ type: 'dp.hide',
721
+ date: date.clone()
722
+ });
723
+ return picker;
724
+ },
725
+
726
+ /********************************************************************************
727
+ *
728
+ * Widget UI interaction functions
729
+ *
730
+ ********************************************************************************/
731
+ actions = {
732
+ next: function () {
733
+ viewDate.add(datePickerModes[currentViewMode].navStep, datePickerModes[currentViewMode].navFnc);
734
+ fillDate();
735
+ },
736
+
737
+ previous: function () {
738
+ viewDate.subtract(datePickerModes[currentViewMode].navStep, datePickerModes[currentViewMode].navFnc);
739
+ fillDate();
740
+ },
741
+
742
+ pickerSwitch: function () {
743
+ showMode(1);
744
+ },
745
+
746
+ selectMonth: function (e) {
747
+ var month = $(e.target).closest('tbody').find('span').index($(e.target));
748
+ viewDate.month(month);
749
+ if (currentViewMode === minViewModeNumber) {
750
+ setValue(date.clone().year(viewDate.year()).month(viewDate.month()));
751
+ hide();
752
+ }
753
+ showMode(-1);
754
+ fillDate();
755
+ },
756
+
757
+ selectYear: function (e) {
758
+ var year = parseInt($(e.target).text(), 10) || 0;
759
+ viewDate.year(year);
760
+ if (currentViewMode === minViewModeNumber) {
761
+ setValue(date.clone().year(viewDate.year()));
762
+ hide();
763
+ }
764
+ showMode(-1);
765
+ fillDate();
766
+ },
767
+
768
+ selectDay: function (e) {
769
+ var day = viewDate.clone();
770
+ if ($(e.target).is('.old')) {
771
+ day.subtract(1, 'M');
772
+ }
773
+ if ($(e.target).is('.new')) {
774
+ day.add(1, 'M');
775
+ }
776
+ setValue(day.date(parseInt($(e.target).text(), 10)));
777
+ if (!hasTime() && !options.keepOpen) {
778
+ hide();
779
+ }
780
+ },
781
+
782
+ incrementHours: function () {
783
+ setValue(date.clone().add(1, 'h'));
784
+ },
785
+
786
+ incrementMinutes: function () {
787
+ setValue(date.clone().add(options.stepping, 'm'));
788
+ },
789
+
790
+ incrementSeconds: function () {
791
+ setValue(date.clone().add(1, 's'));
792
+ },
793
+
794
+ decrementHours: function () {
795
+ setValue(date.clone().subtract(1, 'h'));
796
+ },
797
+
798
+ decrementMinutes: function () {
799
+ setValue(date.clone().subtract(options.stepping, 'm'));
800
+ },
801
+
802
+ decrementSeconds: function () {
803
+ setValue(date.clone().subtract(1, 's'));
804
+ },
805
+
806
+ togglePeriod: function () {
807
+ setValue(date.clone().add((date.hours() >= 12) ? -12 : 12, 'h'));
808
+ },
809
+
810
+ togglePicker: function (e) {
811
+ var $this = $(e.target),
812
+ $parent = $this.closest('ul'),
813
+ expanded = $parent.find('.in'),
814
+ closed = $parent.find('.collapse:not(.in)'),
815
+ collapseData;
816
+
817
+ if (expanded && expanded.length) {
818
+ collapseData = expanded.data('collapse');
819
+ if (collapseData && collapseData.transitioning) {
820
+ return;
821
+ }
822
+ expanded.collapse('hide');
823
+ closed.collapse('show');
824
+ if ($this.is('span')) {
825
+ $this.toggleClass(options.icons.time + ' ' + options.icons.date);
826
+ } else {
827
+ $this.find('span').toggleClass(options.icons.time + ' ' + options.icons.date);
828
+ }
829
+
830
+ // NOTE: uncomment if toggled state will be restored in show()
831
+ //if (component) {
832
+ // component.find('span').toggleClass(options.icons.time + ' ' + options.icons.date);
833
+ //}
834
+ }
835
+ },
836
+
837
+ showPicker: function () {
838
+ widget.find('.timepicker > div:not(.timepicker-picker)').hide();
839
+ widget.find('.timepicker .timepicker-picker').show();
840
+ },
841
+
842
+ showHours: function () {
843
+ widget.find('.timepicker .timepicker-picker').hide();
844
+ widget.find('.timepicker .timepicker-hours').show();
845
+ },
846
+
847
+ showMinutes: function () {
848
+ widget.find('.timepicker .timepicker-picker').hide();
849
+ widget.find('.timepicker .timepicker-minutes').show();
850
+ },
851
+
852
+ showSeconds: function () {
853
+ widget.find('.timepicker .timepicker-picker').hide();
854
+ widget.find('.timepicker .timepicker-seconds').show();
855
+ },
856
+
857
+ selectHour: function (e) {
858
+ var hour = parseInt($(e.target).text(), 10);
859
+
860
+ if (!use24Hours) {
861
+ if (date.hours() >= 12) {
862
+ if (hour !== 12) {
863
+ hour += 12;
864
+ }
865
+ } else {
866
+ if (hour === 12) {
867
+ hour = 0;
868
+ }
869
+ }
870
+ }
871
+ setValue(date.clone().hours(hour));
872
+ actions.showPicker.call(picker);
873
+ },
874
+
875
+ selectMinute: function (e) {
876
+ setValue(date.clone().minutes(parseInt($(e.target).text(), 10)));
877
+ actions.showPicker.call(picker);
878
+ },
879
+
880
+ selectSecond: function (e) {
881
+ setValue(date.clone().seconds(parseInt($(e.target).text(), 10)));
882
+ actions.showPicker.call(picker);
883
+ },
884
+
885
+ clear: function () {
886
+ setValue(null);
887
+ },
888
+
889
+ today: function () {
890
+ setValue(moment());
891
+ }
892
+ },
893
+
894
+ doAction = function (e) {
895
+ if ($(e.currentTarget).is('.disabled')) {
896
+ return false;
897
+ }
898
+ actions[$(e.currentTarget).data('action')].apply(picker, arguments);
899
+ return false;
900
+ },
901
+
902
+ show = function () {
903
+ var currentMoment,
904
+ useCurrentGranularity = {
905
+ 'year': function (m) {
906
+ return m.month(0).date(1).hours(0).seconds(0).minutes(0);
907
+ },
908
+ 'month': function (m) {
909
+ return m.date(1).hours(0).seconds(0).minutes(0);
910
+ },
911
+ 'day': function (m) {
912
+ return m.hours(0).seconds(0).minutes(0);
913
+ },
914
+ 'hour': function (m) {
915
+ return m.seconds(0).minutes(0);
916
+ },
917
+ 'minute': function (m) {
918
+ return m.seconds(0);
919
+ }
920
+ };
921
+
922
+ if (input.prop('disabled') || input.prop('readonly') || widget) {
923
+ return picker;
924
+ }
925
+ if (options.useCurrent && unset) { // && input.val().trim().length !== 0) { this broke the jasmine test
926
+ currentMoment = moment();
927
+ if (typeof options.useCurrent === 'string') {
928
+ currentMoment = useCurrentGranularity[options.useCurrent](currentMoment);
929
+ }
930
+ setValue(currentMoment);
931
+ }
932
+
933
+ widget = getTemplate();
934
+
935
+ fillDow();
936
+ fillMonths();
937
+
938
+ widget.find('.timepicker-hours').hide();
939
+ widget.find('.timepicker-minutes').hide();
940
+ widget.find('.timepicker-seconds').hide();
941
+
942
+ update();
943
+ showMode();
944
+
945
+ $(window).on('resize', place);
946
+ widget.on('click', '[data-action]', doAction); // this handles clicks on the widget
947
+ widget.on('mousedown', false);
948
+
949
+ if (component && component.hasClass('btn')) {
950
+ component.toggleClass('active');
951
+ }
952
+ widget.show();
953
+ place();
954
+
955
+ if (!input.is(':focus')) {
956
+ input.focus();
957
+ }
958
+
959
+ notifyEvent({
960
+ type: 'dp.show'
961
+ });
962
+ return picker;
963
+ },
964
+
965
+ toggle = function () {
966
+ return (widget ? hide() : show());
967
+ },
968
+
969
+ parseInputDate = function (date) {
970
+ if (moment.isMoment(date) || date instanceof Date) {
971
+ date = moment(date);
972
+ } else {
973
+ date = moment(date, parseFormats, options.useStrict);
974
+ }
975
+ date.locale(options.locale);
976
+ return date;
977
+ },
978
+
979
+ keydown = function (e) {
980
+ if (e.keyCode === 27) { // allow escape to hide picker
981
+ hide();
982
+ }
983
+ },
984
+
985
+ change = function (e) {
986
+ var val = $(e.target).val().trim(),
987
+ parsedDate = val ? parseInputDate(val) : null;
988
+ setValue(parsedDate);
989
+ e.stopImmediatePropagation();
990
+ return false;
991
+ },
992
+
993
+ attachDatePickerElementEvents = function () {
994
+ input.on({
995
+ 'change': change,
996
+ 'blur': hide,
997
+ 'keydown': keydown
998
+ });
999
+
1000
+ if (element.is('input')) {
1001
+ input.on({
1002
+ 'focus': show
1003
+ });
1004
+ } else if (component) {
1005
+ component.on('click', toggle);
1006
+ component.on('mousedown', false);
1007
+ }
1008
+ },
1009
+
1010
+ detachDatePickerElementEvents = function () {
1011
+ input.off({
1012
+ 'change': change,
1013
+ 'blur': hide,
1014
+ 'keydown': keydown
1015
+ });
1016
+
1017
+ if (element.is('input')) {
1018
+ input.off({
1019
+ 'focus': show
1020
+ });
1021
+ } else if (component) {
1022
+ component.off('click', toggle);
1023
+ component.off('mousedown', false);
1024
+ }
1025
+ },
1026
+
1027
+ indexGivenDates = function (givenDatesArray) {
1028
+ // Store given enabledDates and disabledDates as keys.
1029
+ // This way we can check their existence in O(1) time instead of looping through whole array.
1030
+ // (for example: options.enabledDates['2014-02-27'] === true)
1031
+ var givenDatesIndexed = {};
1032
+ $.each(givenDatesArray, function () {
1033
+ var dDate = parseInputDate(this);
1034
+ if (dDate.isValid()) {
1035
+ givenDatesIndexed[dDate.format('YYYY-MM-DD')] = true;
1036
+ }
1037
+ });
1038
+ return (Object.keys(givenDatesIndexed).length) ? givenDatesIndexed : false;
1039
+ },
1040
+
1041
+ initFormatting = function () {
1042
+ var format = options.format || 'L LT';
1043
+
1044
+ actualFormat = format.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (input) {
1045
+ return date.localeData().longDateFormat(input) || input;
1046
+ });
1047
+
1048
+ parseFormats = options.extraFormats ? options.extraFormats.slice() : [];
1049
+ if (parseFormats.indexOf(format) < 0 && parseFormats.indexOf(actualFormat) < 0) {
1050
+ parseFormats.push(actualFormat);
1051
+ }
1052
+
1053
+ use24Hours = (actualFormat.toLowerCase().indexOf('a') < 1 && actualFormat.indexOf('h') < 1);
1054
+
1055
+ if (isEnabled('y')) {
1056
+ minViewModeNumber = 2;
1057
+ }
1058
+ if (isEnabled('M')) {
1059
+ minViewModeNumber = 1;
1060
+ }
1061
+ if (isEnabled('d')) {
1062
+ minViewModeNumber = 0;
1063
+ }
1064
+
1065
+ currentViewMode = Math.max(minViewModeNumber, currentViewMode);
1066
+
1067
+ if (!unset) {
1068
+ setValue(date);
1069
+ }
1070
+ };
1071
+
1072
+ /********************************************************************************
1073
+ *
1074
+ * Public API functions
1075
+ * =====================
1076
+ *
1077
+ * Important: Do not expose direct references to private objects or the options
1078
+ * object to the outer world. Always return a clone when returning values or make
1079
+ * a clone when setting a private variable.
1080
+ *
1081
+ ********************************************************************************/
1082
+ picker.destroy = function () {
1083
+ hide();
1084
+ detachDatePickerElementEvents();
1085
+ element.removeData('DateTimePicker');
1086
+ element.removeData('date');
1087
+ };
1088
+
1089
+ picker.toggle = toggle;
1090
+
1091
+ picker.show = show;
1092
+
1093
+ picker.hide = hide;
1094
+
1095
+ picker.disable = function () {
1096
+ hide();
1097
+ if (component && component.hasClass('btn')) {
1098
+ component.addClass('disabled');
1099
+ }
1100
+ input.prop('disabled', true);
1101
+ return picker;
1102
+ };
1103
+
1104
+ picker.enable = function () {
1105
+ if (component && component.hasClass('btn')) {
1106
+ component.removeClass('disabled');
1107
+ }
1108
+ input.prop('disabled', false);
1109
+ return picker;
1110
+ };
1111
+
1112
+ picker.options = function (newOptions) {
1113
+ if (arguments.length === 0) {
1114
+ return $.extend(true, {}, options);
1115
+ }
1116
+
1117
+ if (!(newOptions instanceof Object)) {
1118
+ throw new TypeError('options() options parameter should be an object');
1119
+ }
1120
+ $.extend(true, options, newOptions);
1121
+ $.each(options, function (key, value) {
1122
+ if (picker[key] !== undefined) {
1123
+ picker[key](value);
1124
+ } else {
1125
+ throw new TypeError('option ' + key + ' is not recognized!');
1126
+ }
1127
+ });
1128
+ return picker;
1129
+ };
1130
+
1131
+ picker.date = function (newDate) {
1132
+ if (arguments.length === 0) {
1133
+ if (unset) {
1134
+ return null;
1135
+ }
1136
+ return date.clone();
1137
+ }
1138
+
1139
+ if (newDate !== null && typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) {
1140
+ throw new TypeError('date() parameter must be one of [null, string, moment or Date]');
1141
+ }
1142
+
1143
+ setValue(newDate === null ? null : parseInputDate(newDate));
1144
+ return picker;
1145
+ };
1146
+
1147
+ picker.format = function (newFormat) {
1148
+ if (arguments.length === 0) {
1149
+ return options.format;
1150
+ }
1151
+
1152
+ if ((typeof newFormat !== 'string') && ((typeof newFormat !== 'boolean') || (newFormat !== false))) {
1153
+ throw new TypeError('format() expects a sting or boolean:false parameter ' + newFormat);
1154
+ }
1155
+
1156
+ options.format = newFormat;
1157
+ if (actualFormat) {
1158
+ initFormatting(); // reinit formatting
1159
+ }
1160
+ return picker;
1161
+ };
1162
+
1163
+ picker.dayViewHeaderFormat = function (newFormat) {
1164
+ if (arguments.length === 0) {
1165
+ return options.dayViewHeaderFormat;
1166
+ }
1167
+
1168
+ if (typeof newFormat !== 'string') {
1169
+ throw new TypeError('dayViewHeaderFormat() expects a string parameter');
1170
+ }
1171
+
1172
+ options.dayViewHeaderFormat = newFormat;
1173
+ return picker;
1174
+ };
1175
+
1176
+ picker.extraFormats = function (formats) {
1177
+ if (arguments.length === 0) {
1178
+ return options.extraFormats;
1179
+ }
1180
+
1181
+ if (formats !== false && !(formats instanceof Array)) {
1182
+ throw new TypeError('extraFormats() expects an array or false parameter');
1183
+ }
1184
+
1185
+ options.extraFormats = formats;
1186
+ if (parseFormats) {
1187
+ initFormatting(); // reinit formatting
1188
+ }
1189
+ return picker;
1190
+ };
1191
+
1192
+ picker.disabledDates = function (dates) {
1193
+ if (arguments.length === 0) {
1194
+ return (options.disabledDates ? $.extend({}, options.disabledDates) : options.disabledDates);
1195
+ }
1196
+
1197
+ if (!dates) {
1198
+ options.disabledDates = false;
1199
+ update();
1200
+ return picker;
1201
+ }
1202
+ if (!(dates instanceof Array)) {
1203
+ throw new TypeError('disabledDates() expects an array parameter');
1204
+ }
1205
+ options.disabledDates = indexGivenDates(dates);
1206
+ options.enabledDates = false;
1207
+ update();
1208
+ return picker;
1209
+ };
1210
+
1211
+ picker.enabledDates = function (dates) {
1212
+ if (arguments.length === 0) {
1213
+ return (options.enabledDates ? $.extend({}, options.enabledDates) : options.enabledDates);
1214
+ }
1215
+
1216
+ if (!dates) {
1217
+ options.enabledDates = false;
1218
+ update();
1219
+ return picker;
1220
+ }
1221
+ if (!(dates instanceof Array)) {
1222
+ throw new TypeError('enabledDates() expects an array parameter');
1223
+ }
1224
+ options.enabledDates = indexGivenDates(dates);
1225
+ options.disabledDates = false;
1226
+ update();
1227
+ return picker;
1228
+ };
1229
+
1230
+ picker.daysOfWeekDisabled = function (daysOfWeekDisabled) {
1231
+ if (arguments.length === 0) {
1232
+ return options.daysOfWeekDisabled.splice(0);
1233
+ }
1234
+
1235
+ if (!(daysOfWeekDisabled instanceof Array)) {
1236
+ throw new TypeError('daysOfWeekDisabled() expects an array parameter');
1237
+ }
1238
+ options.daysOfWeekDisabled = daysOfWeekDisabled.reduce(function (previousValue, currentValue) {
1239
+ currentValue = parseInt(currentValue, 10);
1240
+ if (currentValue > 6 || currentValue < 0 || isNaN(currentValue)) {
1241
+ return previousValue;
1242
+ }
1243
+ if (previousValue.indexOf(currentValue) === -1) {
1244
+ previousValue.push(currentValue);
1245
+ }
1246
+ return previousValue;
1247
+ }, []).sort();
1248
+ update();
1249
+ return picker;
1250
+ };
1251
+
1252
+ picker.maxDate = function (date) {
1253
+ if (arguments.length === 0) {
1254
+ return options.maxDate ? options.maxDate.clone() : options.maxDate;
1255
+ }
1256
+
1257
+ if ((typeof date === 'boolean') && date === false) {
1258
+ options.maxDate = false;
1259
+ update();
1260
+ return picker;
1261
+ }
1262
+
1263
+ var parsedDate = parseInputDate(date);
1264
+
1265
+ if (!parsedDate.isValid()) {
1266
+ throw new TypeError('maxDate() Could not parse date parameter: ' + date);
1267
+ }
1268
+ if (options.minDate && parsedDate.isBefore(options.minDate)) {
1269
+ throw new TypeError('maxDate() date parameter is before options.minDate: ' + parsedDate.format(actualFormat));
1270
+ }
1271
+ options.maxDate = parsedDate;
1272
+ if (options.maxDate.isBefore(date)) {
1273
+ setValue(options.maxDate);
1274
+ }
1275
+ update();
1276
+ return picker;
1277
+ };
1278
+
1279
+ picker.minDate = function (date) {
1280
+ if (arguments.length === 0) {
1281
+ return options.minDate ? options.minDate.clone() : options.minDate;
1282
+ }
1283
+
1284
+ if ((typeof date === 'boolean') && date === false) {
1285
+ options.minDate = false;
1286
+ update();
1287
+ return picker;
1288
+ }
1289
+
1290
+ var parsedDate = parseInputDate(date);
1291
+
1292
+ if (!parsedDate.isValid()) {
1293
+ throw new TypeError('minDate() Could not parse date parameter: ' + date);
1294
+ }
1295
+ if (options.maxDate && parsedDate.isAfter(options.maxDate)) {
1296
+ throw new TypeError('minDate() date parameter is after options.maxDate: ' + parsedDate.format(actualFormat));
1297
+ }
1298
+ options.minDate = parsedDate;
1299
+ if (options.minDate.isAfter(date)) {
1300
+ setValue(options.minDate);
1301
+ }
1302
+ update();
1303
+ return picker;
1304
+ };
1305
+
1306
+ picker.defaultDate = function (defaultDate) {
1307
+ if (arguments.length === 0) {
1308
+ return options.defaultDate ? options.defaultDate.clone() : options.defaultDate;
1309
+ }
1310
+ if (!defaultDate) {
1311
+ options.defaultDate = false;
1312
+ return picker;
1313
+ }
1314
+ var parsedDate = parseInputDate(defaultDate);
1315
+ if (!parsedDate.isValid()) {
1316
+ throw new TypeError('defaultDate() Could not parse date parameter: ' + defaultDate);
1317
+ }
1318
+ if (!isValid(parsedDate)) {
1319
+ throw new TypeError('defaultDate() date passed is invalid according to component setup validations');
1320
+ }
1321
+
1322
+ options.defaultDate = parsedDate;
1323
+
1324
+ if (options.defaultDate && input.val().trim() === '') {
1325
+ setValue(options.defaultDate);
1326
+ }
1327
+ return picker;
1328
+ };
1329
+
1330
+ picker.locale = function (locale) {
1331
+ if (arguments.length === 0) {
1332
+ return options.locale;
1333
+ }
1334
+
1335
+ if (!moment.localeData(locale)) {
1336
+ throw new TypeError('locale() locale ' + locale + ' is not loaded from moment locales!');
1337
+ }
1338
+
1339
+ options.locale = locale;
1340
+ date.locale(options.locale);
1341
+ viewDate.locale(options.locale);
1342
+
1343
+ if (actualFormat) {
1344
+ initFormatting(); // reinit formatting
1345
+ }
1346
+ if (widget) {
1347
+ hide();
1348
+ show();
1349
+ }
1350
+ return picker;
1351
+ };
1352
+
1353
+ picker.stepping = function (stepping) {
1354
+ if (arguments.length === 0) {
1355
+ return options.stepping;
1356
+ }
1357
+
1358
+ stepping = parseInt(stepping, 10);
1359
+ if (isNaN(stepping) || stepping < 1) {
1360
+ stepping = 1;
1361
+ }
1362
+ options.stepping = stepping;
1363
+ return picker;
1364
+ };
1365
+
1366
+ picker.useCurrent = function (useCurrent) {
1367
+ var useCurrentOptions = ['year', 'month', 'day', 'hour', 'minute'];
1368
+ if (arguments.length === 0) {
1369
+ return options.useCurrent;
1370
+ }
1371
+
1372
+ if ((typeof useCurrent !== 'boolean') && (typeof useCurrent !== 'string')) {
1373
+ throw new TypeError('useCurrent() expects a boolean or string parameter');
1374
+ }
1375
+ if (typeof useCurrent === 'string' && useCurrentOptions.indexOf(useCurrent.toLowerCase()) === -1) {
1376
+ throw new TypeError('useCurrent() expects a string parameter of ' + useCurrentOptions.join(', '));
1377
+ }
1378
+ options.useCurrent = useCurrent;
1379
+ return picker;
1380
+ };
1381
+
1382
+ picker.collapse = function (collapse) {
1383
+ if (arguments.length === 0) {
1384
+ return options.collapse;
1385
+ }
1386
+
1387
+ if (typeof collapse !== 'boolean') {
1388
+ throw new TypeError('collapse() expects a boolean parameter');
1389
+ }
1390
+ if (options.collapse === collapse) {
1391
+ return picker;
1392
+ }
1393
+ options.collapse = collapse;
1394
+ if (widget) {
1395
+ hide();
1396
+ show();
1397
+ }
1398
+ return picker;
1399
+ };
1400
+
1401
+ picker.icons = function (icons) {
1402
+ if (arguments.length === 0) {
1403
+ return $.extend({}, options.icons);
1404
+ }
1405
+
1406
+ if (!(icons instanceof Object)) {
1407
+ throw new TypeError('icons() expects parameter to be an Object');
1408
+ }
1409
+ $.extend(options.icons, icons);
1410
+ if (widget) {
1411
+ hide();
1412
+ show();
1413
+ }
1414
+ return picker;
1415
+ };
1416
+
1417
+ picker.useStrict = function (useStrict) {
1418
+ if (arguments.length === 0) {
1419
+ return options.useStrict;
1420
+ }
1421
+
1422
+ if (typeof useStrict !== 'boolean') {
1423
+ throw new TypeError('useStrict() expects a boolean parameter');
1424
+ }
1425
+ options.useStrict = useStrict;
1426
+ return picker;
1427
+ };
1428
+
1429
+ picker.sideBySide = function (sideBySide) {
1430
+ if (arguments.length === 0) {
1431
+ return options.sideBySide;
1432
+ }
1433
+
1434
+ if (typeof sideBySide !== 'boolean') {
1435
+ throw new TypeError('sideBySide() expects a boolean parameter');
1436
+ }
1437
+ options.sideBySide = sideBySide;
1438
+ if (widget) {
1439
+ hide();
1440
+ show();
1441
+ }
1442
+ return picker;
1443
+ };
1444
+
1445
+ picker.viewMode = function (newViewMode) {
1446
+ if (arguments.length === 0) {
1447
+ return options.viewMode;
1448
+ }
1449
+
1450
+ if (typeof newViewMode !== 'string') {
1451
+ throw new TypeError('viewMode() expects a string parameter');
1452
+ }
1453
+
1454
+ if (viewModes.indexOf(newViewMode) === -1) {
1455
+ throw new TypeError('viewMode() parameter must be one of (' + viewModes.join(', ') + ') value');
1456
+ }
1457
+
1458
+ options.viewMode = newViewMode;
1459
+ currentViewMode = Math.max(viewModes.indexOf(newViewMode), minViewModeNumber);
1460
+
1461
+ showMode();
1462
+ return picker;
1463
+ };
1464
+
1465
+ picker.toolbarPlacement = function (toolbarPlacement) {
1466
+ if (arguments.length === 0) {
1467
+ return options.toolbarPlacement;
1468
+ }
1469
+
1470
+ if (typeof toolbarPlacement !== 'string') {
1471
+ throw new TypeError('toolbarPlacement() expects a string parameter');
1472
+ }
1473
+ if (toolbarPlacements.indexOf(toolbarPlacement) === -1) {
1474
+ throw new TypeError('toolbarPlacement() parameter must be one of (' + toolbarPlacements.join(', ') + ') value');
1475
+ }
1476
+ options.toolbarPlacement = toolbarPlacement;
1477
+
1478
+ if (widget) {
1479
+ hide();
1480
+ show();
1481
+ }
1482
+ return picker;
1483
+ };
1484
+
1485
+ picker.widgetPositioning = function (widgetPositioning) {
1486
+ if (arguments.length === 0) {
1487
+ return $.extend({}, options.widgetPositioning);
1488
+ }
1489
+
1490
+ if (({}).toString.call(widgetPositioning) !== '[object Object]') {
1491
+ throw new TypeError('widgetPositioning() expects an object variable');
1492
+ }
1493
+ if (widgetPositioning.horizontal) {
1494
+ if (typeof widgetPositioning.horizontal !== 'string') {
1495
+ throw new TypeError('widgetPositioning() horizontal variable must be a string');
1496
+ }
1497
+ widgetPositioning.horizontal = widgetPositioning.horizontal.toLowerCase();
1498
+ if (horizontalModes.indexOf(widgetPositioning.horizontal) === -1) {
1499
+ throw new TypeError('widgetPositioning() expects horizontal parameter to be one of (' + horizontalModes.join(', ') + ')');
1500
+ }
1501
+ options.widgetPositioning.horizontal = widgetPositioning.horizontal;
1502
+ }
1503
+ if (widgetPositioning.vertical) {
1504
+ if (typeof widgetPositioning.vertical !== 'string') {
1505
+ throw new TypeError('widgetPositioning() vertical variable must be a string');
1506
+ }
1507
+ widgetPositioning.vertical = widgetPositioning.vertical.toLowerCase();
1508
+ if (verticalModes.indexOf(widgetPositioning.vertical) === -1) {
1509
+ throw new TypeError('widgetPositioning() expects vertical parameter to be one of (' + verticalModes.join(', ') + ')');
1510
+ }
1511
+ options.widgetPositioning.vertical = widgetPositioning.vertical;
1512
+ }
1513
+ update();
1514
+ return picker;
1515
+ };
1516
+
1517
+ picker.calendarWeeks = function (showCalendarWeeks) {
1518
+ if (arguments.length === 0) {
1519
+ return options.calendarWeeks;
1520
+ }
1521
+
1522
+ if (typeof showCalendarWeeks !== 'boolean') {
1523
+ throw new TypeError('calendarWeeks() expects parameter to be a boolean value');
1524
+ }
1525
+
1526
+ options.calendarWeeks = showCalendarWeeks;
1527
+ update();
1528
+ return picker;
1529
+ };
1530
+
1531
+ picker.showTodayButton = function (showTodayButton) {
1532
+ if (arguments.length === 0) {
1533
+ return options.showTodayButton;
1534
+ }
1535
+
1536
+ if (typeof showTodayButton !== 'boolean') {
1537
+ throw new TypeError('showTodayButton() expects a boolean parameter');
1538
+ }
1539
+
1540
+ options.showTodayButton = showTodayButton;
1541
+ if (widget) {
1542
+ hide();
1543
+ show();
1544
+ }
1545
+ return picker;
1546
+ };
1547
+
1548
+ picker.showClear = function (showClear) {
1549
+ if (arguments.length === 0) {
1550
+ return options.showClear;
1551
+ }
1552
+
1553
+ if (typeof showClear !== 'boolean') {
1554
+ throw new TypeError('showClear() expects a boolean parameter');
1555
+ }
1556
+
1557
+ options.showClear = showClear;
1558
+ if (widget) {
1559
+ hide();
1560
+ show();
1561
+ }
1562
+ return picker;
1563
+ };
1564
+
1565
+ picker.widgetParent = function (widgetParent) {
1566
+ if (arguments.length === 0) {
1567
+ return options.widgetParent;
1568
+ }
1569
+
1570
+ if (typeof widgetParent === 'string') {
1571
+ widgetParent = $(widgetParent);
1572
+ }
1573
+
1574
+ if (widgetParent !== null && (typeof widgetParent !== 'string' && !(widgetParent instanceof jQuery))) {
1575
+ throw new TypeError('widgetParent() expects a string or a jQuery object parameter');
1576
+ }
1577
+
1578
+ options.widgetParent = widgetParent;
1579
+ if (widget) {
1580
+ hide();
1581
+ show();
1582
+ }
1583
+ return picker;
1584
+ };
1585
+
1586
+ picker.keepOpen = function (keepOpen) {
1587
+ if (arguments.length === 0) {
1588
+ return options.format;
1589
+ }
1590
+
1591
+ if (typeof keepOpen !== 'boolean') {
1592
+ throw new TypeError('keepOpen() expects a boolean parameter');
1593
+ }
1594
+
1595
+ options.keepOpen = keepOpen;
1596
+ return picker;
1597
+ };
1598
+
1599
+ // initializing element and component attributes
1600
+ if (element.is('input')) {
1601
+ input = element;
1602
+ } else {
1603
+ input = element.find('.datepickerinput');
1604
+ if (input.size() === 0) {
1605
+ input = element.find('input');
1606
+ } else if (!input.is('input')) {
1607
+ throw new Error('CSS class "datepickerinput" cannot be applied to non input element');
1608
+ }
1609
+ }
1610
+
1611
+ if (element.hasClass('input-group')) {
1612
+ // in case there is more then one 'input-group-addon' Issue #48
1613
+ if (element.find('.datepickerbutton').size() === 0) {
1614
+ component = element.find('[class^="input-group-"]');
1615
+ } else {
1616
+ component = element.find('.datepickerbutton');
1617
+ }
1618
+ }
1619
+
1620
+ if (!input.is('input')) {
1621
+ throw new Error('Could not initialize DateTimePicker without an input element');
1622
+ }
1623
+
1624
+ $.extend(true, options, dataToOptions());
1625
+
1626
+ picker.options(options);
1627
+
1628
+ initFormatting();
1629
+
1630
+ attachDatePickerElementEvents();
1631
+
1632
+ if (input.prop('disabled')) {
1633
+ picker.disable();
1634
+ }
1635
+
1636
+ if (input.val().trim().length !== 0) {
1637
+ setValue(parseInputDate(input.val().trim()));
1638
+ } else if (options.defaultDate) {
1639
+ setValue(options.defaultDate);
1640
+ }
1641
+
1642
+ return picker;
1643
+ };
1644
+
1645
+ /********************************************************************************
1646
+ *
1647
+ * jQuery plugin constructor and defaults object
1648
+ *
1649
+ ********************************************************************************/
1650
+
1651
+ $.fn.datetimepicker = function (options) {
1652
+ return this.each(function () {
1653
+ var $this = $(this);
1654
+ if (!$this.data('DateTimePicker')) {
1655
+ // create a private copy of the defaults object
1656
+ options = $.extend(true, {}, $.fn.datetimepicker.defaults, options);
1657
+ $this.data('DateTimePicker', dateTimePicker($this, options));
1658
+ }
1659
+ });
1660
+ };
1661
+
1662
+ $.fn.datetimepicker.defaults = {
1663
+ format: false,
1664
+ dayViewHeaderFormat: 'MMMM YYYY',
1665
+ extraFormats: false,
1666
+ stepping: 1,
1667
+ minDate: false,
1668
+ maxDate: false,
1669
+ useCurrent: true,
1670
+ collapse: true,
1671
+ locale: moment.locale(),
1672
+ defaultDate: false,
1673
+ disabledDates: false,
1674
+ enabledDates: false,
1675
+ icons: {
1676
+ time: 'glyphicon glyphicon-time',
1677
+ date: 'glyphicon glyphicon-calendar',
1678
+ up: 'glyphicon glyphicon-chevron-up',
1679
+ down: 'glyphicon glyphicon-chevron-down',
1680
+ previous: 'glyphicon glyphicon-chevron-left',
1681
+ next: 'glyphicon glyphicon-chevron-right',
1682
+ today: 'glyphicon glyphicon-screenshot',
1683
+ clear: 'glyphicon glyphicon-trash'
1684
+ },
1685
+ useStrict: false,
1686
+ sideBySide: false,
1687
+ daysOfWeekDisabled: [],
1688
+ calendarWeeks: false,
1689
+ viewMode: 'days',
1690
+ toolbarPlacement: 'default',
1691
+ showTodayButton: false,
1692
+ showClear: false,
1693
+ widgetPositioning: {
1694
+ horizontal: 'auto',
1695
+ vertical: 'auto'
1696
+ },
1697
+ widgetParent: null,
1698
+ keepOpen: false
1699
+ };
1700
+ }));