Skip to content

Commit d4e5daa

Browse files
author
Eonasdan
committed
2 parents ba176a4 + 4c9d308 commit d4e5daa

File tree

7 files changed

+632
-94
lines changed

7 files changed

+632
-94
lines changed

Gruntfile.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,19 +103,35 @@ module.exports = function (grunt) {
103103
}
104104
},
105105

106+
env: {
107+
paris: {
108+
TZ : 'Europe/Paris' // sets env for phantomJS https://github.com/ariya/phantomjs/issues/10379#issuecomment-36058589
109+
}
110+
},
111+
112+
connect: {
113+
server: {
114+
options: {
115+
port: 8099
116+
}
117+
}
118+
},
119+
106120
jasmine: {
107121
customTemplate: {
108122
src: 'src/js/*.js',
109123
options: {
110124
specs: 'test/*Spec.js',
111125
helpers: 'test/*Helper.js',
126+
host: 'http://127.0.0.1:8099',
112127
styles: [
113128
'node_modules/bootstrap/dist/css/bootstrap.min.css',
114129
'build/css/bootstrap-datetimepicker.min.css'
115130
],
116131
vendor: [
117132
'node_modules/jquery/dist/jquery.min.js',
118133
'node_modules/moment/min/moment-with-locales.min.js',
134+
'node_modules/moment-timezone/moment-timezone.js',
119135
'node_modules/bootstrap/dist/js/bootstrap.min.js'
120136
],
121137
display: 'none',
@@ -144,14 +160,16 @@ module.exports = function (grunt) {
144160

145161
grunt.loadTasks('tasks');
146162

163+
grunt.loadNpmTasks('grunt-env');
164+
grunt.loadNpmTasks('grunt-contrib-connect');
147165
grunt.loadNpmTasks('grunt-contrib-jasmine');
148166
grunt.loadNpmTasks('grunt-nuget');
149167

150168
// These plugins provide necessary tasks.
151169
require('load-grunt-tasks')(grunt);
152170

153171
// Default task.
154-
grunt.registerTask('default', ['jshint', 'jscs', 'less', 'jasmine']);
172+
grunt.registerTask('default', ['jshint', 'jscs', 'less', 'env:paris', 'connect', 'jasmine']);
155173

156174
// travis build task
157175
grunt.registerTask('build:travis', [
@@ -160,15 +178,15 @@ module.exports = function (grunt) {
160178
// build
161179
'uglify', 'less',
162180
// tests
163-
'jasmine'
181+
'env:paris', 'connect', 'jasmine'
164182
]);
165183

166184
// Task to be run when building
167185
grunt.registerTask('build', [
168186
'jshint', 'jscs', 'uglify', 'less'
169187
]);
170188

171-
grunt.registerTask('test', ['jshint', 'jscs', 'uglify', 'less', 'jasmine']);
189+
grunt.registerTask('test', ['jshint', 'jscs', 'uglify', 'less', 'env:paris', 'connect', 'jasmine']);
172190

173191
grunt.registerTask('docs', 'Generate docs', function () {
174192
grunt.util.spawn({

docs/Events.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Emitted from:
3737

3838
### dp.change
3939

40-
Fired when the date is changed.
40+
Fired when the date is changed, including when changed to a non-date (e.g. When keepInvalid=true).
4141

4242
Parameters:
4343

@@ -68,6 +68,7 @@ Parameters:
6868
```
6969
e = {
7070
date //the invalid date. Type: moment object (clone)
71+
oldDate //previous date. Type: moment object (clone) or false in the event of a null
7172
}
7273
```
7374

package.json

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,45 @@
11
{
2-
"author": {
3-
"name": "Jonathan Peterson"
4-
},
5-
"bugs": {
6-
"url": "https://github.com/eonasdan/bootstrap-datetimepicker/issues"
7-
},
8-
"dependencies": {
9-
"moment": "~2.10.5",
10-
"bootstrap": "^3.3",
11-
"jquery": ">=1.8.3 <2.2.0"
12-
},
13-
"description": "A date/time picker component designed to work with Bootstrap 3 and Momentjs. For usage, installation and demos see Project Site on GitHub",
14-
"devDependencies": {
15-
"grunt": "latest",
16-
"grunt-contrib-jasmine": "^0.7.0",
17-
"grunt-contrib-jshint": "latest",
18-
"grunt-contrib-less": "latest",
19-
"grunt-contrib-uglify": "latest",
20-
"grunt-jscs": "latest",
21-
"grunt-string-replace": "latest",
22-
"load-grunt-tasks": "latest",
23-
"grunt-nuget": "^0.1.4"
24-
},
25-
"homepage": "http://eonasdan.github.io/bootstrap-datetimepicker/",
26-
"keywords": [
27-
"twitter-bootstrap",
28-
"bootstrap",
29-
"datepicker",
30-
"datetimepicker",
31-
"timepicker",
32-
"moment"
33-
],
34-
"license": "MIT",
35-
"main": "src/js/bootstrap-datetimepicker.js",
36-
"name": "eonasdan-bootstrap-datetimepicker",
37-
"repository": {
38-
"type": "git",
39-
"url": "https://github.com/eonasdan/bootstrap-datetimepicker.git"
40-
},
41-
"version": "4.17.42"
2+
"author": {
3+
"name": "Jonathan Peterson"
4+
},
5+
"bugs": {
6+
"url": "https://github.com/eonasdan/bootstrap-datetimepicker/issues"
7+
},
8+
"dependencies": {
9+
"bootstrap": "^3.3",
10+
"jquery": ">=1.8.3 <2.2.0",
11+
"moment": "~2.10.5",
12+
"moment-timezone": "^0.4.0"
13+
},
14+
"description": "A date/time picker component designed to work with Bootstrap 3 and Momentjs. For usage, installation and demos see Project Site on GitHub",
15+
"devDependencies": {
16+
"grunt": "latest",
17+
"grunt-contrib-connect": "^0.11.2",
18+
"grunt-contrib-jasmine": "^0.7.0",
19+
"grunt-contrib-jshint": "latest",
20+
"grunt-contrib-less": "latest",
21+
"grunt-contrib-uglify": "latest",
22+
"grunt-env": "^0.4.4",
23+
"grunt-jscs": "latest",
24+
"grunt-nuget": "^0.1.4",
25+
"grunt-string-replace": "latest",
26+
"load-grunt-tasks": "latest"
27+
},
28+
"homepage": "http://eonasdan.github.io/bootstrap-datetimepicker/",
29+
"keywords": [
30+
"twitter-bootstrap",
31+
"bootstrap",
32+
"datepicker",
33+
"datetimepicker",
34+
"timepicker",
35+
"moment"
36+
],
37+
"license": "MIT",
38+
"main": "src/js/bootstrap-datetimepicker.js",
39+
"name": "eonasdan-bootstrap-datetimepicker",
40+
"repository": {
41+
"type": "git",
42+
"url": "https://github.com/eonasdan/bootstrap-datetimepicker.git"
43+
},
44+
"version": "4.17.42"
4245
}

src/js/bootstrap-datetimepicker.js

Lines changed: 83 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -133,37 +133,21 @@
133133
*
134134
********************************************************************************/
135135
getMoment = function (d) {
136-
var tzEnabled = false,
137-
returnMoment,
138-
currentZoneOffset,
139-
incomingZoneOffset,
140-
timeZoneIndicator,
141-
dateWithTimeZoneInfo;
136+
var returnMoment;
142137

143-
if (moment.tz !== undefined && options.timeZone !== undefined && options.timeZone !== null && options.timeZone !== '') {
144-
tzEnabled = true;
145-
}
146138
if (d === undefined || d === null) {
147-
if (tzEnabled) {
148-
returnMoment = moment().tz(options.timeZone);
149-
} else {
150-
returnMoment = moment(); //TODO should this use format? and locale?
151-
}
139+
returnMoment = moment(); //TODO should this use format? and locale?
140+
} else if (hasTimeZone()) { // There is a string to parse and a default time zone
141+
// parse with the tz function which takes a default time zone if it is not in the format string
142+
returnMoment = moment.tz(d, parseFormats, options.useStrict, options.timeZone);
152143
} else {
153-
if (tzEnabled) {
154-
currentZoneOffset = moment().tz(options.timeZone).utcOffset();
155-
incomingZoneOffset = moment(d, parseFormats, options.useStrict).utcOffset();
156-
if (incomingZoneOffset !== currentZoneOffset) {
157-
timeZoneIndicator = moment().tz(options.timeZone).format('Z');
158-
dateWithTimeZoneInfo = moment(d, parseFormats, options.useStrict).format('YYYY-MM-DD[T]HH:mm:ss') + timeZoneIndicator;
159-
returnMoment = moment(dateWithTimeZoneInfo, parseFormats, options.useStrict).tz(options.timeZone);
160-
} else {
161-
returnMoment = moment(d, parseFormats, options.useStrict).tz(options.timeZone);
162-
}
163-
} else {
164-
returnMoment = moment(d, parseFormats, options.useStrict);
165-
}
144+
returnMoment = moment(d, parseFormats, options.useStrict);
145+
}
146+
147+
if (hasTimeZone()) {
148+
returnMoment.tz(options.timeZone);
166149
}
150+
167151
return returnMoment;
168152
},
169153

@@ -194,6 +178,10 @@
194178
return (isEnabled('h') || isEnabled('m') || isEnabled('s'));
195179
},
196180

181+
hasTimeZone = function () {
182+
return moment.tz !== undefined && options.timeZone !== undefined && options.timeZone !== null && options.timeZone !== '';
183+
},
184+
197185
hasDate = function () {
198186
return (isEnabled('y') || isEnabled('M') || isEnabled('d'));
199187
},
@@ -474,7 +462,7 @@
474462

475463
widget.css({
476464
top: vertical === 'top' ? 'auto' : position.top + element.outerHeight(),
477-
bottom: vertical === 'top' ? position.top + element.outerHeight() : 'auto',
465+
bottom: vertical === 'top' ? parent.outerHeight() - (parent === element ? 0 : position.top) : 'auto',
478466
left: horizontal === 'left' ? (parent === element ? 0 : position.left) : 'auto',
479467
right: horizontal === 'left' ? 'auto' : parent.outerWidth() - element.outerWidth() - (parent === element ? 0 : position.left)
480468
});
@@ -659,6 +647,9 @@
659647
startDecade = moment({y: viewDate.year() - (viewDate.year() % 100) - 1}),
660648
endDecade = startDecade.clone().add(100, 'y'),
661649
startedAt = startDecade.clone(),
650+
minDateDecade = false,
651+
maxDateDecade = false,
652+
endDecadeYear,
662653
html = '';
663654

664655
decadesViewHeader.eq(0).find('span').attr('title', options.tooltips.prevCentury);
@@ -677,8 +668,11 @@
677668
}
678669

679670
while (!startDecade.isAfter(endDecade, 'y')) {
680-
html += '<span data-action="selectDecade" class="decade' + (startDecade.isSame(date, 'y') ? ' active' : '') +
681-
(!isValid(startDecade, 'y') ? ' disabled' : '') + '" data-selection="' + (startDecade.year() + 6) + '">' + (startDecade.year() + 1) + ' - ' + (startDecade.year() + 12) + '</span>';
671+
endDecadeYear = startDecade.year() + 12;
672+
minDateDecade = options.minDate && options.minDate.isAfter(startDecade, 'y') && options.minDate.year() <= endDecadeYear;
673+
maxDateDecade = options.maxDate && options.maxDate.isAfter(startDecade, 'y') && options.maxDate.year() <= endDecadeYear;
674+
html += '<span data-action="selectDecade" class="decade' + (date.isAfter(startDecade) && date.year() <= endDecadeYear ? ' active' : '') +
675+
(!isValid(startDecade, 'y') && !minDateDecade && !maxDateDecade ? ' disabled' : '') + '" data-selection="' + (startDecade.year() + 6) + '">' + (startDecade.year() + 1) + ' - ' + (startDecade.year() + 12) + '</span>';
682676
startDecade.add(12, 'y');
683677
}
684678
html += '<span></span><span></span><span></span>'; //push the dangling block over, at least this way it's even
@@ -863,8 +857,12 @@
863857

864858
targetMoment = targetMoment.clone().locale(options.locale);
865859

860+
if (hasTimeZone()) {
861+
targetMoment.tz(options.timeZone);
862+
}
863+
866864
if (options.stepping !== 1) {
867-
targetMoment.minutes((Math.round(targetMoment.minutes() / options.stepping) * options.stepping) % 60).seconds(0);
865+
targetMoment.minutes((Math.round(targetMoment.minutes() / options.stepping) * options.stepping)).seconds(0);
868866
}
869867

870868
if (isValid(targetMoment)) {
@@ -882,10 +880,17 @@
882880
} else {
883881
if (!options.keepInvalid) {
884882
input.val(unset ? '' : date.format(actualFormat));
883+
} else {
884+
notifyEvent({
885+
type: 'dp.change',
886+
date: targetMoment,
887+
oldDate: oldDate
888+
});
885889
}
886890
notifyEvent({
887891
type: 'dp.error',
888-
date: targetMoment
892+
date: targetMoment,
893+
oldDate: oldDate
889894
});
890895
}
891896
},
@@ -1520,7 +1525,7 @@
15201525
}
15211526

15221527
if ((typeof newFormat !== 'string') && ((typeof newFormat !== 'boolean') || (newFormat !== false))) {
1523-
throw new TypeError('format() expects a sting or boolean:false parameter ' + newFormat);
1528+
throw new TypeError('format() expects a string or boolean:false parameter ' + newFormat);
15241529
}
15251530

15261531
options.format = newFormat;
@@ -2097,6 +2102,10 @@
20972102
};
20982103

20992104
picker.keyBinds = function (keyBinds) {
2105+
if (arguments.length === 0) {
2106+
return options.keyBinds;
2107+
}
2108+
21002109
options.keyBinds = keyBinds;
21012110
return picker;
21022111
};
@@ -2312,7 +2321,7 @@
23122321
input = element;
23132322
} else {
23142323
input = element.find(options.datepickerInput);
2315-
if (input.size() === 0) {
2324+
if (input.length === 0) {
23162325
input = element.find('input');
23172326
} else if (!input.is('input')) {
23182327
throw new Error('CSS class "' + options.datepickerInput + '" cannot be applied to non input element');
@@ -2321,7 +2330,7 @@
23212330

23222331
if (element.hasClass('input-group')) {
23232332
// in case there is more then one 'input-group-addon' Issue #48
2324-
if (element.find('.datepickerbutton').size() === 0) {
2333+
if (element.find('.datepickerbutton').length === 0) {
23252334
component = element.find('.input-group-addon');
23262335
} else {
23272336
component = element.find('.datepickerbutton');
@@ -2388,14 +2397,42 @@
23882397
* @memberOf jQuery.fn
23892398
*/
23902399
$.fn.datetimepicker = function (options) {
2391-
return this.each(function () {
2392-
var $this = $(this);
2393-
if (!$this.data('DateTimePicker')) {
2394-
// create a private copy of the defaults object
2395-
options = $.extend(true, {}, $.fn.datetimepicker.defaults, options);
2396-
$this.data('DateTimePicker', dateTimePicker($this, options));
2397-
}
2398-
});
2400+
options = options || {};
2401+
2402+
var args = Array.prototype.slice.call(arguments, 1),
2403+
isInstance = true,
2404+
thisMethods = ['destroy', 'hide', 'show', 'toggle'],
2405+
returnValue;
2406+
2407+
if (typeof options === 'object') {
2408+
return this.each(function () {
2409+
var $this = $(this);
2410+
if (!$this.data('DateTimePicker')) {
2411+
// create a private copy of the defaults object
2412+
options = $.extend(true, {}, $.fn.datetimepicker.defaults, options);
2413+
$this.data('DateTimePicker', dateTimePicker($this, options));
2414+
}
2415+
});
2416+
} else if (typeof options === 'string') {
2417+
this.each(function () {
2418+
var $this = $(this),
2419+
instance = $this.data('DateTimePicker');
2420+
if (!instance) {
2421+
throw new Error('bootstrap-datetimepicker("' + options + '") method was called on an element that is not using DateTimePicker');
2422+
}
2423+
2424+
returnValue = instance[options].apply(instance, args);
2425+
isInstance = returnValue === instance;
2426+
});
2427+
2428+
if (isInstance || $.inArray(options, thisMethods) > -1) {
2429+
return this;
2430+
}
2431+
2432+
return returnValue;
2433+
}
2434+
2435+
throw new TypeError('Invalid arguments for DateTimePicker: ' + options);
23992436
};
24002437

24012438
$.fn.datetimepicker.defaults = {
@@ -2581,4 +2618,7 @@
25812618
enabledHours: false,
25822619
viewDate: false
25832620
};
2621+
if (typeof module !== 'undefined') {
2622+
module.exports = $.fn.datetimepicker;
2623+
}
25842624
}));

0 commit comments

Comments
 (0)