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

Commit 1a9e88f

Browse files
chrisirhcrvanbaalen
authored and
rvanbaalen
committed
feat(datepicker): support HTML5 date input type
Closes #3499
1 parent e909b92 commit 1a9e88f

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
@@ -106,7 +106,7 @@ angular.module('ui.bootstrap.dateparser', [])
106106
};
107107
}
108108

109-
this.parse = function(input, format) {
109+
this.parse = function(input, format, baseDate) {
110110
if ( !angular.isString(input) || !format ) {
111111
return input;
112112
}
@@ -124,7 +124,20 @@ angular.module('ui.bootstrap.dateparser', [])
124124
results = input.match(regex);
125125

126126
if ( results && results.length ) {
127-
var fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }, dt;
127+
var fields, dt;
128+
if (baseDate) {
129+
fields = {
130+
year: baseDate.getFullYear(),
131+
month: baseDate.getMonth(),
132+
date: baseDate.getDate(),
133+
hours: baseDate.getHours(),
134+
minutes: baseDate.getMinutes(),
135+
seconds: baseDate.getSeconds(),
136+
milliseconds: baseDate.getMilliseconds()
137+
};
138+
} else {
139+
fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
140+
}
128141

129142
for( var i = 1, n = results.length; i < n; i++ ) {
130143
var mapper = map[i-1];
@@ -134,7 +147,8 @@ angular.module('ui.bootstrap.dateparser', [])
134147
}
135148

136149
if ( isValid(fields.year, fields.month, fields.date) ) {
137-
dt = new Date(fields.year, fields.month, fields.date, fields.hours, fields.minutes, fields.seconds, fields.milliseconds);
150+
dt = new Date(fields.year, fields.month, fields.date, fields.hours, fields.minutes, fields.seconds,
151+
fields.milliseconds || 0);
138152
}
139153

140154
return dt;

Diff for: src/datepicker/datepicker.js

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

448448
.constant('datepickerPopupConfig', {
449449
datepickerPopup: 'yyyy-MM-dd',
450+
html5Types: {
451+
date: 'yyyy-MM-dd',
452+
'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss'
453+
// TODO: Add other formats/type support
454+
},
450455
currentText: 'Today',
451456
clearText: 'Clear',
452457
closeText: 'Done',
@@ -479,16 +484,34 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
479484
return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
480485
};
481486

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

493516
// popup element used to display calendar
494517
var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>');
@@ -547,8 +570,6 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
547570
datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
548571
}
549572

550-
// Internal API to maintain the correct ng-invalid-[key] class
551-
ngModel.$$parserName = 'date';
552573
function parseDate(viewValue) {
553574
if (angular.isNumber(viewValue)) {
554575
// presumably timestamp to date object
@@ -560,7 +581,7 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
560581
} else if (angular.isDate(viewValue) && !isNaN(viewValue)) {
561582
return viewValue;
562583
} else if (angular.isString(viewValue)) {
563-
var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue);
584+
var date = dateParser.parse(viewValue, dateFormat, scope.date) || new Date(viewValue);
564585
if (isNaN(date)) {
565586
return undefined;
566587
} else {
@@ -588,24 +609,31 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
588609
}
589610
}
590611

591-
ngModel.$validators.date = validator;
592-
ngModel.$parsers.unshift(parseDate);
593-
594-
ngModel.$formatters.push(function (value) {
595-
scope.date = value;
596-
return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat);
597-
});
612+
if (!isHtml5DateInput) {
613+
// Internal API to maintain the correct ng-invalid-[key] class
614+
ngModel.$$parserName = 'date';
615+
ngModel.$validators.date = validator;
616+
ngModel.$parsers.unshift(parseDate);
617+
ngModel.$formatters.push(function (value) {
618+
scope.date = value;
619+
return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat);
620+
});
621+
}
622+
else {
623+
ngModel.$formatters.push(function (value) {
624+
scope.date = value;
625+
return value;
626+
});
627+
}
598628

599629
// Inner change
600630
scope.dateSelection = function(dt) {
601631
if (angular.isDefined(dt)) {
602632
scope.date = dt;
603633
}
604-
if (dateFormat) {
605-
var date = scope.date ? dateFilter(scope.date, dateFormat) : '';
606-
element.val(date);
607-
}
608-
ngModel.$setViewValue(scope.date);
634+
var date = scope.date ? dateFilter(scope.date, dateFormat) : '';
635+
element.val(date);
636+
ngModel.$setViewValue(date);
609637

610638
if ( closeOnDateSelection ) {
611639
scope.isOpen = false;
@@ -615,7 +643,7 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
615643

616644
// Detect changes in the view from the text box
617645
ngModel.$viewChangeListeners.push(function () {
618-
scope.date = ngModel.$viewValue;
646+
scope.date = dateParser.parse(ngModel.$viewValue, dateFormat, scope.date) || new Date(ngModel.$viewValue);
619647
});
620648

621649
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)