Skip to content
This repository was archived by the owner on May 29, 2019. It is now read-only.

Commit 736d464

Browse files
committed
feat(datepicker): support HTML5 date input type
1 parent d024dd7 commit 736d464

File tree

4 files changed

+148
-29
lines changed

4 files changed

+148
-29
lines changed

Diff for: src/dateparser/dateparser.js

+17-3
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ angular.module('ui.bootstrap.dateparser', [])
102102
};
103103
}
104104

105-
this.parse = function(input, format) {
105+
this.parse = function(input, format, baseDate) {
106106
if ( !angular.isString(input) || !format ) {
107107
return input;
108108
}
@@ -120,7 +120,20 @@ angular.module('ui.bootstrap.dateparser', [])
120120
results = input.match(regex);
121121

122122
if ( results && results.length ) {
123-
var fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0 }, dt;
123+
var fields, dt;
124+
if (baseDate) {
125+
fields = {
126+
year: baseDate.getFullYear(),
127+
month: baseDate.getMonth(),
128+
date: baseDate.getDate(),
129+
hours: baseDate.getHours(),
130+
minutes: baseDate.getMinutes(),
131+
seconds: baseDate.getSeconds(),
132+
milliseconds: baseDate.getMilliseconds() / 1000
133+
};
134+
} else {
135+
fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
136+
}
124137

125138
for( var i = 1, n = results.length; i < n; i++ ) {
126139
var mapper = map[i-1];
@@ -130,7 +143,8 @@ angular.module('ui.bootstrap.dateparser', [])
130143
}
131144

132145
if ( isValid(fields.year, fields.month, fields.date) ) {
133-
dt = new Date(fields.year, fields.month, fields.date, fields.hours, fields.minutes, fields.seconds);
146+
dt = new Date(fields.year, fields.month, fields.date, fields.hours, fields.minutes, fields.seconds,
147+
fields.milliseconds * 1000 || 0);
134148
}
135149

136150
return dt;

Diff for: src/datepicker/datepicker.js

+54-26
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,11 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
444444

445445
.constant('datepickerPopupConfig', {
446446
datepickerPopup: 'yyyy-MM-dd',
447+
html5Types: {
448+
date: 'yyyy-MM-dd',
449+
'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss'
450+
// TODO: Add other formats/type support
451+
},
447452
currentText: 'Today',
448453
clearText: 'Clear',
449454
closeText: 'Done',
@@ -476,16 +481,34 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
476481
return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
477482
};
478483

479-
dateFormat = attrs.datepickerPopup || datepickerPopupConfig.datepickerPopup;
480-
attrs.$observe('datepickerPopup', function(value, oldValue) {
481-
var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
482-
// Invalidate the $modelValue to ensure that formatters re-run
483-
// FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
484-
if (newDateFormat !== dateFormat) {
485-
dateFormat = newDateFormat;
486-
ngModel.$modelValue = null;
487-
}
488-
});
484+
var isHtml5DateInput = false;
485+
if (datepickerPopupConfig.html5Types[attrs.type]) {
486+
dateFormat = datepickerPopupConfig.html5Types[attrs.type];
487+
isHtml5DateInput = true;
488+
} else {
489+
dateFormat = attrs.datepickerPopup || datepickerPopupConfig.datepickerPopup;
490+
attrs.$observe('datepickerPopup', function(value, oldValue) {
491+
var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
492+
// Invalidate the $modelValue to ensure that formatters re-run
493+
// FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
494+
if (newDateFormat !== dateFormat) {
495+
dateFormat = newDateFormat;
496+
ngModel.$modelValue = null;
497+
498+
if (!dateFormat) {
499+
throw new Error('datepickerPopup must have a date format specified.');
500+
}
501+
}
502+
});
503+
}
504+
505+
if (!dateFormat) {
506+
throw new Error('datepickerPopup must have a date format specified.');
507+
}
508+
509+
if (isHtml5DateInput && attrs.datepickerPopup) {
510+
throw new Error('HTML5 date input types do not support custom formats.');
511+
}
489512

490513
// popup element used to display calendar
491514
var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>');
@@ -539,8 +562,6 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
539562
datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
540563
}
541564

542-
// Internal API to maintain the correct ng-invalid-[key] class
543-
ngModel.$$parserName = 'date';
544565
function parseDate(viewValue) {
545566
if (angular.isNumber(viewValue)) {
546567
// presumably timestamp to date object
@@ -552,7 +573,7 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
552573
} else if (angular.isDate(viewValue) && !isNaN(viewValue)) {
553574
return viewValue;
554575
} else if (angular.isString(viewValue)) {
555-
var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue);
576+
var date = dateParser.parse(viewValue, dateFormat, scope.date) || new Date(viewValue);
556577
if (isNaN(date)) {
557578
return undefined;
558579
} else {
@@ -580,24 +601,31 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
580601
}
581602
}
582603

583-
ngModel.$validators.date = validator;
584-
ngModel.$parsers.unshift(parseDate);
585-
586-
ngModel.$formatters.push(function (value) {
587-
scope.date = value;
588-
return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat);
589-
});
604+
if (!isHtml5DateInput) {
605+
// Internal API to maintain the correct ng-invalid-[key] class
606+
ngModel.$$parserName = 'date';
607+
ngModel.$validators.date = validator;
608+
ngModel.$parsers.unshift(parseDate);
609+
ngModel.$formatters.push(function (value) {
610+
scope.date = value;
611+
return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat);
612+
});
613+
}
614+
else {
615+
ngModel.$formatters.push(function (value) {
616+
scope.date = value;
617+
return value;
618+
});
619+
}
590620

591621
// Inner change
592622
scope.dateSelection = function(dt) {
593623
if (angular.isDefined(dt)) {
594624
scope.date = dt;
595625
}
596-
if (dateFormat) {
597-
var date = scope.date ? dateFilter(scope.date, dateFormat) : '';
598-
element.val(date);
599-
}
600-
ngModel.$setViewValue(scope.date);
626+
var date = scope.date ? dateFilter(scope.date, dateFormat) : '';
627+
element.val(date);
628+
ngModel.$setViewValue(date);
601629

602630
if ( closeOnDateSelection ) {
603631
scope.isOpen = false;
@@ -607,7 +635,7 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
607635

608636
// Detect changes in the view from the text box
609637
ngModel.$viewChangeListeners.push(function () {
610-
scope.date = ngModel.$viewValue;
638+
scope.date = dateParser.parse(ngModel.$viewValue, dateFormat, scope.date) || new Date(ngModel.$viewValue);
611639
});
612640

613641
var documentClickBind = function(event) {

Diff for: src/datepicker/docs/demo.html

+9
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ <h4>Popup</h4>
2828
</span>
2929
</p>
3030
</div>
31+
32+
<div class="col-md-6">
33+
<p class="input-group">
34+
<input type="date" class="form-control" datepicker-popup ng-model="dt" is-open="opened" min-date="minDate" max-date="'2015-06-22'" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close" />
35+
<span class="input-group-btn">
36+
<button type="button" class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
37+
</span>
38+
</p>
39+
</div>
3140
</div>
3241
<div class="row">
3342
<div class="col-md-6">

Diff for: src/datepicker/test/datepicker.spec.js

+68
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ describe('datepicker directive', function () {
9999
return element.find('tbody').find('button');
100100
}
101101

102+
function selectedElementIndex() {
103+
var buttons = getAllOptionsEl();
104+
for (var i = 0; i < buttons.length; i++) {
105+
if (angular.element(buttons[i]).hasClass('btn-info')) {
106+
return i;
107+
}
108+
}
109+
}
110+
102111
function expectSelectedElement( index ) {
103112
var buttons = getAllOptionsEl();
104113
angular.forEach( buttons, function( button, idx ) {
@@ -1402,6 +1411,65 @@ describe('datepicker directive', function () {
14021411
});
14031412
});
14041413

1414+
describe('works with HTML5 date input types', function () {
1415+
var date2 = new Date('October 1, 2010 12:34:56.789');
1416+
beforeEach(inject(function(_$document_) {
1417+
$document = _$document_;
1418+
$rootScope.isopen = true;
1419+
$rootScope.date = new Date('September 30, 2010 15:30:00');
1420+
}));
1421+
1422+
it('works as date', function() {
1423+
setupInputWithType('date');
1424+
expect(dropdownEl).toBeHidden();
1425+
expect(inputEl.val()).toBe('2010-09-30');
1426+
1427+
changeInputValueTo(inputEl, '1980-03-05');
1428+
1429+
expect($rootScope.date.getFullYear()).toEqual(1980);
1430+
expect($rootScope.date.getMonth()).toEqual(2);
1431+
expect($rootScope.date.getDate()).toEqual(5);
1432+
1433+
expect(getOptions(true)).toEqual([
1434+
['24', '25', '26', '27', '28', '29', '01'],
1435+
['02', '03', '04', '05', '06', '07', '08'],
1436+
['09', '10', '11', '12', '13', '14', '15'],
1437+
['16', '17', '18', '19', '20', '21', '22'],
1438+
['23', '24', '25', '26', '27', '28', '29'],
1439+
['30', '31', '01', '02', '03', '04', '05']
1440+
]);
1441+
expect(selectedElementIndex()).toEqual( 10 );
1442+
});
1443+
1444+
it('works as datetime-local', function() {
1445+
setupInputWithType('datetime-local');
1446+
expect(inputEl.val()).toBe('2010-09-30T15:30:00.000');
1447+
1448+
changeInputValueTo(inputEl, '1980-03-05T12:34:56.000');
1449+
1450+
expect($rootScope.date.getFullYear()).toEqual(1980);
1451+
expect($rootScope.date.getMonth()).toEqual(2);
1452+
expect($rootScope.date.getDate()).toEqual(5);
1453+
1454+
expect(getOptions(true)).toEqual([
1455+
['24', '25', '26', '27', '28', '29', '01'],
1456+
['02', '03', '04', '05', '06', '07', '08'],
1457+
['09', '10', '11', '12', '13', '14', '15'],
1458+
['16', '17', '18', '19', '20', '21', '22'],
1459+
['23', '24', '25', '26', '27', '28', '29'],
1460+
['30', '31', '01', '02', '03', '04', '05']
1461+
]);
1462+
expect(selectedElementIndex()).toEqual( 10 );
1463+
});
1464+
1465+
function setupInputWithType(type) {
1466+
var wrapElement = $compile('<div><input type="' +
1467+
type + '" ng-model="date" datepicker-popup><div>')($rootScope);
1468+
$rootScope.$digest();
1469+
assignElements(wrapElement);
1470+
}
1471+
});
1472+
14051473
});
14061474

14071475
describe('attribute `datepickerOptions`', function () {

0 commit comments

Comments
 (0)