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

Commit 71e840b

Browse files
committed
feat(datepicker): support HTML5 date input type
1 parent 82cb637 commit 71e840b

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
@@ -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>');
@@ -544,8 +567,6 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
544567
datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
545568
}
546569

547-
// Internal API to maintain the correct ng-invalid-[key] class
548-
ngModel.$$parserName = 'date';
549570
function parseDate(viewValue) {
550571
if (angular.isNumber(viewValue)) {
551572
// presumably timestamp to date object
@@ -557,7 +578,7 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
557578
} else if (angular.isDate(viewValue) && !isNaN(viewValue)) {
558579
return viewValue;
559580
} else if (angular.isString(viewValue)) {
560-
var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue);
581+
var date = dateParser.parse(viewValue, dateFormat, scope.date) || new Date(viewValue);
561582
if (isNaN(date)) {
562583
return undefined;
563584
} else {
@@ -585,24 +606,31 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
585606
}
586607
}
587608

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

596626
// Inner change
597627
scope.dateSelection = function(dt) {
598628
if (angular.isDefined(dt)) {
599629
scope.date = dt;
600630
}
601-
if (dateFormat) {
602-
var date = scope.date ? dateFilter(scope.date, dateFormat) : '';
603-
element.val(date);
604-
}
605-
ngModel.$setViewValue(scope.date);
631+
var date = scope.date ? dateFilter(scope.date, dateFormat) : '';
632+
element.val(date);
633+
ngModel.$setViewValue(date);
606634

607635
if ( closeOnDateSelection ) {
608636
scope.isOpen = false;
@@ -612,7 +640,7 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
612640

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

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