Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit c2e17ad

Browse files
committed
feat(datepicker): support for ngMessages. Closes #4672.
1 parent 30f334a commit c2e17ad

File tree

5 files changed

+164
-91
lines changed

5 files changed

+164
-91
lines changed

src/components/datepicker/datePicker.js

+25-1
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,19 @@
2525
* @param {Date=} md-min-date Expression representing a min date (inclusive).
2626
* @param {Date=} md-max-date Expression representing a max date (inclusive).
2727
* @param {boolean=} disabled Whether the datepicker is disabled.
28+
* @param {boolean=} required Whether a value is required for the datepicker.
2829
*
2930
* @description
3031
* `<md-datepicker>` is a component used to select a single date.
3132
* For information on how to configure internationalization for the date picker,
3233
* see `$mdDateLocaleProvider`.
3334
*
35+
* This component supports [ngMessages](https://docs.angularjs.org/api/ngMessages/directive/ngMessages).
36+
* Supported attributes are:
37+
* * `required`: whether a required date is not set.
38+
* * `mindate`: whether the selected date is before the minimum allowed date.
39+
* * `maxdate`: whether the selected date is after the maximum allowed date.
40+
*
3441
* @usage
3542
* <hljs lang="html">
3643
* <md-datepicker ng-model="birthday"></md-datepicker>
@@ -239,6 +246,7 @@
239246
self.date = self.ngModelCtrl.$viewValue;
240247
self.inputElement.value = self.dateLocale.formatDate(self.date);
241248
self.resizeInputElement();
249+
self.setErrorFlags();
242250
};
243251
};
244252

@@ -319,6 +327,23 @@
319327
this.calendarButton.disabled = isDisabled;
320328
};
321329

330+
/**
331+
* Sets the custom ngModel.$error flags to be consumed by ngMessages. Flags are:
332+
* - mindate: whether the selected date is before the minimum date.
333+
* - maxdate: whether the selected flag is after the maximum date.
334+
*/
335+
DatePickerCtrl.prototype.setErrorFlags = function() {
336+
if (this.dateUtil.isValidDate(this.date)) {
337+
if (this.dateUtil.isValidDate(this.minDate)) {
338+
this.ngModelCtrl.$error['mindate'] = this.date < this.minDate;
339+
}
340+
341+
if (this.dateUtil.isValidDate(this.maxDate)) {
342+
this.ngModelCtrl.$error['maxdate'] = this.date > this.maxDate;
343+
}
344+
}
345+
};
346+
322347
/** Resizes the input element based on the size of its content. */
323348
DatePickerCtrl.prototype.resizeInputElement = function() {
324349
this.inputElement.size = this.inputElement.value.length + EXTRA_INPUT_SIZE;
@@ -332,7 +357,6 @@
332357
var inputString = this.inputElement.value;
333358
var parsedDate = this.dateLocale.parseDate(inputString);
334359
this.dateUtil.setDateTimeToMidnight(parsedDate);
335-
336360
if (inputString === '') {
337361
this.ngModelCtrl.$setViewValue(null);
338362
this.date = null;

src/components/datepicker/datePicker.spec.js

+109-78
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ describe('md-date-picker', function() {
2828
'md-max-date="maxDate" ' +
2929
'md-min-date="minDate" ' +
3030
'ng-model="myDate" ' +
31+
'ng-required="isRequired" ' +
3132
'ng-disabled="isDisabled">' +
3233
'</md-datepicker>';
3334
ngElement = $compile(template)(pageScope);
@@ -58,73 +59,6 @@ describe('md-date-picker', function() {
5859
expect(pageScope.myDate).toBeNull();
5960
});
6061

61-
it('should open and close the floating calendar pane element', function() {
62-
// We can asset that the calendarPane is in the DOM by checking if it has a height.
63-
expect(controller.calendarPane.offsetHeight).toBe(0);
64-
65-
element.querySelector('md-button').click();
66-
$timeout.flush();
67-
68-
expect(controller.calendarPane.offsetHeight).toBeGreaterThan(0);
69-
expect(controller.inputMask.style.left).toBe(controller.inputContainer.clientWidth + 'px');
70-
71-
// Click off of the calendar.
72-
document.body.click();
73-
expect(controller.calendarPane.offsetHeight).toBe(0);
74-
});
75-
76-
it('should open and close the floating calendar pane element via keyboard', function() {
77-
controller.ngInputElement.triggerHandler({
78-
type: 'keydown',
79-
altKey: true,
80-
keyCode: keyCodes.DOWN_ARROW
81-
});
82-
$timeout.flush();
83-
84-
expect(controller.calendarPane.offsetHeight).toBeGreaterThan(0);
85-
86-
// Fake an escape event closing the calendar.
87-
pageScope.$broadcast('md-calendar-close');
88-
89-
});
90-
91-
it('should adjust the position of the floating pane if it would go off-screen', function() {
92-
// Absolutely position the picker near the edge of the screen.
93-
var bodyRect = document.body.getBoundingClientRect();
94-
element.style.position = 'absolute';
95-
element.style.top = bodyRect.bottom + 'px';
96-
element.style.left = bodyRect.right + 'px';
97-
document.body.appendChild(element);
98-
99-
// Open the pane.
100-
element.querySelector('md-button').click();
101-
$timeout.flush();
102-
103-
// Expect that the whole pane is on-screen.
104-
var paneRect = controller.calendarPane.getBoundingClientRect();
105-
expect(paneRect.right).toBeLessThan(bodyRect.right + 1);
106-
expect(paneRect.bottom).toBeLessThan(bodyRect.bottom + 1);
107-
expect(paneRect.top).toBeGreaterThan(0);
108-
expect(paneRect.left).toBeGreaterThan(0);
109-
110-
document.body.removeChild(element);
111-
});
112-
113-
it('should shink the calendar pane when it would otherwise not fit on the screen', function() {
114-
// Make the body narrow so that the calendar pane won't fit on-screen.
115-
document.body.style.width = '300px';
116-
117-
// Open the calendar pane.
118-
element.querySelector('md-button').click();
119-
$timeout.flush();
120-
121-
// Expect the calendarPane to be scaled by an amount between zero and one.
122-
expect(controller.calendarPane.style.transform).toMatch(/scale\(0\.\d+\)/);
123-
124-
// Reset the body width.
125-
document.body.style.width = '';
126-
});
127-
12862
it('should disable the internal inputs based on ng-disabled binding', function() {
12963
expect(controller.inputElement.disabled).toBe(false);
13064
expect(controller.calendarButton.disabled).toBe(false);
@@ -136,23 +70,41 @@ describe('md-date-picker', function() {
13670
expect(controller.calendarButton.disabled).toBe(true);
13771
});
13872

139-
it('should not open the calendar pane if disabled', function() {
140-
controller.setDisabled(true);
141-
controller.openCalendarPane({
142-
target: controller.inputElement
143-
});
144-
scope.$apply();
145-
expect(controller.isCalendarOpen).toBeFalsy();
146-
expect(controller.calendarPane.offsetHeight).toBe(0);
147-
});
148-
14973
it('should update the internal input placeholder', function() {
15074
expect(controller.inputElement.placeholder).toBeFalsy();
15175
controller.placeholder = 'Fancy new placeholder';
15276

15377
expect(controller.inputElement.placeholder).toBe('Fancy new placeholder');
15478
});
15579

80+
describe('ngMessages suport', function() {
81+
it('should set the `required` $error flag', function() {
82+
pageScope.isRequired = true;
83+
populateInputElement('');
84+
pageScope.$apply();
85+
86+
expect(controller.ngModelCtrl.$error['required']).toBe(true);
87+
});
88+
89+
it('should set the `mindate` $error flag', function() {
90+
pageScope.minDate = new Date(2015, JAN, 1);
91+
populateInputElement('2014-01-01');
92+
pageScope.$apply();
93+
controller.ngModelCtrl.$render();
94+
95+
expect(controller.ngModelCtrl.$error['mindate']).toBe(true);
96+
});
97+
98+
it('should set the `mindate` $error flag', function() {
99+
pageScope.maxDate = new Date(2015, JAN, 1);
100+
populateInputElement('2016-01-01');
101+
pageScope.$apply();
102+
controller.ngModelCtrl.$render();
103+
104+
expect(controller.ngModelCtrl.$error['maxdate']).toBe(true);
105+
});
106+
});
107+
156108
describe('input event', function() {
157109
it('should update the model value when user enters a valid date', function() {
158110
var expectedDate = new Date(2015, JUN, 1);
@@ -189,7 +141,85 @@ describe('md-date-picker', function() {
189141
});
190142
});
191143

192-
it('should close the calendar pane on md-calendar-close', function() {
144+
describe('floating calendar pane', function() {
145+
it('should open and close the floating calendar pane element', function() {
146+
// We can asset that the calendarPane is in the DOM by checking if it has a height.
147+
expect(controller.calendarPane.offsetHeight).toBe(0);
148+
149+
element.querySelector('md-button').click();
150+
$timeout.flush();
151+
152+
expect(controller.calendarPane.offsetHeight).toBeGreaterThan(0);
153+
expect(controller.inputMask.style.left).toBe(controller.inputContainer.clientWidth + 'px');
154+
155+
// Click off of the calendar.
156+
document.body.click();
157+
expect(controller.calendarPane.offsetHeight).toBe(0);
158+
});
159+
160+
it('should open and close the floating calendar pane element via keyboard', function() {
161+
controller.ngInputElement.triggerHandler({
162+
type: 'keydown',
163+
altKey: true,
164+
keyCode: keyCodes.DOWN_ARROW
165+
});
166+
$timeout.flush();
167+
168+
expect(controller.calendarPane.offsetHeight).toBeGreaterThan(0);
169+
170+
// Fake an escape event closing the calendar.
171+
pageScope.$broadcast('md-calendar-close');
172+
173+
});
174+
175+
it('should adjust the position of the floating pane if it would go off-screen', function() {
176+
// Absolutely position the picker near the edge of the screen.
177+
var bodyRect = document.body.getBoundingClientRect();
178+
element.style.position = 'absolute';
179+
element.style.top = bodyRect.bottom + 'px';
180+
element.style.left = bodyRect.right + 'px';
181+
document.body.appendChild(element);
182+
183+
// Open the pane.
184+
element.querySelector('md-button').click();
185+
$timeout.flush();
186+
187+
// Expect that the whole pane is on-screen.
188+
var paneRect = controller.calendarPane.getBoundingClientRect();
189+
expect(paneRect.right).toBeLessThan(bodyRect.right + 1);
190+
expect(paneRect.bottom).toBeLessThan(bodyRect.bottom + 1);
191+
expect(paneRect.top).toBeGreaterThan(0);
192+
expect(paneRect.left).toBeGreaterThan(0);
193+
194+
document.body.removeChild(element);
195+
});
196+
197+
it('should shink the calendar pane when it would otherwise not fit on the screen', function() {
198+
// Make the body narrow so that the calendar pane won't fit on-screen.
199+
document.body.style.width = '300px';
200+
201+
// Open the calendar pane.
202+
element.querySelector('md-button').click();
203+
$timeout.flush();
204+
205+
// Expect the calendarPane to be scaled by an amount between zero and one.
206+
expect(controller.calendarPane.style.transform).toMatch(/scale\(0\.\d+\)/);
207+
208+
// Reset the body width.
209+
document.body.style.width = '';
210+
});
211+
212+
it('should not open the calendar pane if disabled', function() {
213+
controller.setDisabled(true);
214+
controller.openCalendarPane({
215+
target: controller.inputElement
216+
});
217+
scope.$apply();
218+
expect(controller.isCalendarOpen).toBeFalsy();
219+
expect(controller.calendarPane.offsetHeight).toBe(0);
220+
});
221+
222+
it('should close the calendar pane on md-calendar-close', function() {
193223
controller.openCalendarPane({
194224
target: controller.inputElement
195225
});
@@ -199,6 +229,7 @@ describe('md-date-picker', function() {
199229
expect(controller.calendarPaneOpenedFrom).toBe(null);
200230
expect(controller.isCalendarOpen).toBe(false);
201231
});
232+
});
202233

203234
describe('md-calendar-change', function() {
204235
it('should update the model value and close the calendar pane', function() {

src/components/datepicker/demoBasicUsage/index.html

+12
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,17 @@ <h4>Disabled date-picker</h4>
1010
<h4>Date-picker with min date and max date</h4>
1111
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
1212
md-min-date="minDate" md-max-date="maxDate"></md-datepicker>
13+
14+
<h4>With ngMessages</h4>
15+
<form name="myForm">
16+
<md-datepicker name="dateField" ng-model="myDate" md-placeholder="Enter date"
17+
required md-min-date="minDate" md-max-date="maxDate"></md-datepicker>
18+
19+
<div class="validation-messages" ng-messages="myForm.dateField.$error">
20+
<div ng-message="required">This date is required!</div>
21+
<div ng-message="mindate">Date is too early!</div>
22+
<div ng-message="maxdate">Date is too late!</div>
23+
</div>
24+
</form>
1325
</md-content>
1426
</div>
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
angular.module('datepickerBasicUsage', ['ngMaterial'])
2-
.controller('AppCtrl', function($scope) {
3-
$scope.myDate = new Date();
1+
angular.module('datepickerBasicUsage',
2+
['ngMaterial', 'ngMessages']).controller('AppCtrl', function($scope) {
3+
$scope.myDate = new Date();
44

5-
$scope.minDate = new Date(
6-
$scope.myDate.getFullYear(),
7-
$scope.myDate.getMonth() - 2,
8-
$scope.myDate.getDate());
5+
$scope.minDate = new Date(
6+
$scope.myDate.getFullYear(),
7+
$scope.myDate.getMonth() - 2,
8+
$scope.myDate.getDate());
99

10-
$scope.maxDate = new Date(
11-
$scope.myDate.getFullYear(),
12-
$scope.myDate.getMonth() + 2,
13-
$scope.myDate.getDate());
14-
});
10+
$scope.maxDate = new Date(
11+
$scope.myDate.getFullYear(),
12+
$scope.myDate.getMonth() + 2,
13+
$scope.myDate.getDate());
14+
});

src/components/datepicker/demoBasicUsage/style.css

+6
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,9 @@
22
md-content {
33
padding-bottom: 200px;
44
}
5+
6+
.validation-messages {
7+
font-size: 11px;
8+
color: darkred;
9+
margin: 10px 0 0 25px;
10+
}

0 commit comments

Comments
 (0)