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

Commit

Permalink
feat(datepicker): support for ngMessages. Closes #4672.
Browse files Browse the repository at this point in the history
  • Loading branch information
jelbourn committed Sep 23, 2015
1 parent 30f334a commit c2e17ad
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 91 deletions.
26 changes: 25 additions & 1 deletion src/components/datepicker/datePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,19 @@
* @param {Date=} md-min-date Expression representing a min date (inclusive).
* @param {Date=} md-max-date Expression representing a max date (inclusive).
* @param {boolean=} disabled Whether the datepicker is disabled.
* @param {boolean=} required Whether a value is required for the datepicker.
*
* @description
* `<md-datepicker>` is a component used to select a single date.
* For information on how to configure internationalization for the date picker,
* see `$mdDateLocaleProvider`.
*
* This component supports [ngMessages](https://docs.angularjs.org/api/ngMessages/directive/ngMessages).
* Supported attributes are:
* * `required`: whether a required date is not set.
* * `mindate`: whether the selected date is before the minimum allowed date.
* * `maxdate`: whether the selected date is after the maximum allowed date.
*
* @usage
* <hljs lang="html">
* <md-datepicker ng-model="birthday"></md-datepicker>
Expand Down Expand Up @@ -239,6 +246,7 @@
self.date = self.ngModelCtrl.$viewValue;
self.inputElement.value = self.dateLocale.formatDate(self.date);
self.resizeInputElement();
self.setErrorFlags();
};
};

Expand Down Expand Up @@ -319,6 +327,23 @@
this.calendarButton.disabled = isDisabled;
};

/**
* Sets the custom ngModel.$error flags to be consumed by ngMessages. Flags are:
* - mindate: whether the selected date is before the minimum date.
* - maxdate: whether the selected flag is after the maximum date.
*/
DatePickerCtrl.prototype.setErrorFlags = function() {
if (this.dateUtil.isValidDate(this.date)) {
if (this.dateUtil.isValidDate(this.minDate)) {
this.ngModelCtrl.$error['mindate'] = this.date < this.minDate;
}

if (this.dateUtil.isValidDate(this.maxDate)) {
this.ngModelCtrl.$error['maxdate'] = this.date > this.maxDate;
}
}
};

/** Resizes the input element based on the size of its content. */
DatePickerCtrl.prototype.resizeInputElement = function() {
this.inputElement.size = this.inputElement.value.length + EXTRA_INPUT_SIZE;
Expand All @@ -332,7 +357,6 @@
var inputString = this.inputElement.value;
var parsedDate = this.dateLocale.parseDate(inputString);
this.dateUtil.setDateTimeToMidnight(parsedDate);

if (inputString === '') {
this.ngModelCtrl.$setViewValue(null);
this.date = null;
Expand Down
187 changes: 109 additions & 78 deletions src/components/datepicker/datePicker.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe('md-date-picker', function() {
'md-max-date="maxDate" ' +
'md-min-date="minDate" ' +
'ng-model="myDate" ' +
'ng-required="isRequired" ' +
'ng-disabled="isDisabled">' +
'</md-datepicker>';
ngElement = $compile(template)(pageScope);
Expand Down Expand Up @@ -58,73 +59,6 @@ describe('md-date-picker', function() {
expect(pageScope.myDate).toBeNull();
});

it('should open and close the floating calendar pane element', function() {
// We can asset that the calendarPane is in the DOM by checking if it has a height.
expect(controller.calendarPane.offsetHeight).toBe(0);

element.querySelector('md-button').click();
$timeout.flush();

expect(controller.calendarPane.offsetHeight).toBeGreaterThan(0);
expect(controller.inputMask.style.left).toBe(controller.inputContainer.clientWidth + 'px');

// Click off of the calendar.
document.body.click();
expect(controller.calendarPane.offsetHeight).toBe(0);
});

it('should open and close the floating calendar pane element via keyboard', function() {
controller.ngInputElement.triggerHandler({
type: 'keydown',
altKey: true,
keyCode: keyCodes.DOWN_ARROW
});
$timeout.flush();

expect(controller.calendarPane.offsetHeight).toBeGreaterThan(0);

// Fake an escape event closing the calendar.
pageScope.$broadcast('md-calendar-close');

});

it('should adjust the position of the floating pane if it would go off-screen', function() {
// Absolutely position the picker near the edge of the screen.
var bodyRect = document.body.getBoundingClientRect();
element.style.position = 'absolute';
element.style.top = bodyRect.bottom + 'px';
element.style.left = bodyRect.right + 'px';
document.body.appendChild(element);

// Open the pane.
element.querySelector('md-button').click();
$timeout.flush();

// Expect that the whole pane is on-screen.
var paneRect = controller.calendarPane.getBoundingClientRect();
expect(paneRect.right).toBeLessThan(bodyRect.right + 1);
expect(paneRect.bottom).toBeLessThan(bodyRect.bottom + 1);
expect(paneRect.top).toBeGreaterThan(0);
expect(paneRect.left).toBeGreaterThan(0);

document.body.removeChild(element);
});

it('should shink the calendar pane when it would otherwise not fit on the screen', function() {
// Make the body narrow so that the calendar pane won't fit on-screen.
document.body.style.width = '300px';

// Open the calendar pane.
element.querySelector('md-button').click();
$timeout.flush();

// Expect the calendarPane to be scaled by an amount between zero and one.
expect(controller.calendarPane.style.transform).toMatch(/scale\(0\.\d+\)/);

// Reset the body width.
document.body.style.width = '';
});

it('should disable the internal inputs based on ng-disabled binding', function() {
expect(controller.inputElement.disabled).toBe(false);
expect(controller.calendarButton.disabled).toBe(false);
Expand All @@ -136,23 +70,41 @@ describe('md-date-picker', function() {
expect(controller.calendarButton.disabled).toBe(true);
});

it('should not open the calendar pane if disabled', function() {
controller.setDisabled(true);
controller.openCalendarPane({
target: controller.inputElement
});
scope.$apply();
expect(controller.isCalendarOpen).toBeFalsy();
expect(controller.calendarPane.offsetHeight).toBe(0);
});

it('should update the internal input placeholder', function() {
expect(controller.inputElement.placeholder).toBeFalsy();
controller.placeholder = 'Fancy new placeholder';

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

describe('ngMessages suport', function() {
it('should set the `required` $error flag', function() {
pageScope.isRequired = true;
populateInputElement('');
pageScope.$apply();

expect(controller.ngModelCtrl.$error['required']).toBe(true);
});

it('should set the `mindate` $error flag', function() {
pageScope.minDate = new Date(2015, JAN, 1);
populateInputElement('2014-01-01');
pageScope.$apply();
controller.ngModelCtrl.$render();

expect(controller.ngModelCtrl.$error['mindate']).toBe(true);
});

it('should set the `mindate` $error flag', function() {
pageScope.maxDate = new Date(2015, JAN, 1);
populateInputElement('2016-01-01');
pageScope.$apply();
controller.ngModelCtrl.$render();

expect(controller.ngModelCtrl.$error['maxdate']).toBe(true);
});
});

describe('input event', function() {
it('should update the model value when user enters a valid date', function() {
var expectedDate = new Date(2015, JUN, 1);
Expand Down Expand Up @@ -189,7 +141,85 @@ describe('md-date-picker', function() {
});
});

it('should close the calendar pane on md-calendar-close', function() {
describe('floating calendar pane', function() {
it('should open and close the floating calendar pane element', function() {
// We can asset that the calendarPane is in the DOM by checking if it has a height.
expect(controller.calendarPane.offsetHeight).toBe(0);

element.querySelector('md-button').click();
$timeout.flush();

expect(controller.calendarPane.offsetHeight).toBeGreaterThan(0);
expect(controller.inputMask.style.left).toBe(controller.inputContainer.clientWidth + 'px');

// Click off of the calendar.
document.body.click();
expect(controller.calendarPane.offsetHeight).toBe(0);
});

it('should open and close the floating calendar pane element via keyboard', function() {
controller.ngInputElement.triggerHandler({
type: 'keydown',
altKey: true,
keyCode: keyCodes.DOWN_ARROW
});
$timeout.flush();

expect(controller.calendarPane.offsetHeight).toBeGreaterThan(0);

// Fake an escape event closing the calendar.
pageScope.$broadcast('md-calendar-close');

});

it('should adjust the position of the floating pane if it would go off-screen', function() {
// Absolutely position the picker near the edge of the screen.
var bodyRect = document.body.getBoundingClientRect();
element.style.position = 'absolute';
element.style.top = bodyRect.bottom + 'px';
element.style.left = bodyRect.right + 'px';
document.body.appendChild(element);

// Open the pane.
element.querySelector('md-button').click();
$timeout.flush();

// Expect that the whole pane is on-screen.
var paneRect = controller.calendarPane.getBoundingClientRect();
expect(paneRect.right).toBeLessThan(bodyRect.right + 1);
expect(paneRect.bottom).toBeLessThan(bodyRect.bottom + 1);
expect(paneRect.top).toBeGreaterThan(0);
expect(paneRect.left).toBeGreaterThan(0);

document.body.removeChild(element);
});

it('should shink the calendar pane when it would otherwise not fit on the screen', function() {
// Make the body narrow so that the calendar pane won't fit on-screen.
document.body.style.width = '300px';

// Open the calendar pane.
element.querySelector('md-button').click();
$timeout.flush();

// Expect the calendarPane to be scaled by an amount between zero and one.
expect(controller.calendarPane.style.transform).toMatch(/scale\(0\.\d+\)/);

// Reset the body width.
document.body.style.width = '';
});

it('should not open the calendar pane if disabled', function() {
controller.setDisabled(true);
controller.openCalendarPane({
target: controller.inputElement
});
scope.$apply();
expect(controller.isCalendarOpen).toBeFalsy();
expect(controller.calendarPane.offsetHeight).toBe(0);
});

it('should close the calendar pane on md-calendar-close', function() {
controller.openCalendarPane({
target: controller.inputElement
});
Expand All @@ -199,6 +229,7 @@ describe('md-date-picker', function() {
expect(controller.calendarPaneOpenedFrom).toBe(null);
expect(controller.isCalendarOpen).toBe(false);
});
});

describe('md-calendar-change', function() {
it('should update the model value and close the calendar pane', function() {
Expand Down
12 changes: 12 additions & 0 deletions src/components/datepicker/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,17 @@ <h4>Disabled date-picker</h4>
<h4>Date-picker with min date and max date</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
md-min-date="minDate" md-max-date="maxDate"></md-datepicker>

<h4>With ngMessages</h4>
<form name="myForm">
<md-datepicker name="dateField" ng-model="myDate" md-placeholder="Enter date"
required md-min-date="minDate" md-max-date="maxDate"></md-datepicker>

<div class="validation-messages" ng-messages="myForm.dateField.$error">
<div ng-message="required">This date is required!</div>
<div ng-message="mindate">Date is too early!</div>
<div ng-message="maxdate">Date is too late!</div>
</div>
</form>
</md-content>
</div>
24 changes: 12 additions & 12 deletions src/components/datepicker/demoBasicUsage/script.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
angular.module('datepickerBasicUsage', ['ngMaterial'])
.controller('AppCtrl', function($scope) {
$scope.myDate = new Date();
angular.module('datepickerBasicUsage',
['ngMaterial', 'ngMessages']).controller('AppCtrl', function($scope) {
$scope.myDate = new Date();

$scope.minDate = new Date(
$scope.myDate.getFullYear(),
$scope.myDate.getMonth() - 2,
$scope.myDate.getDate());
$scope.minDate = new Date(
$scope.myDate.getFullYear(),
$scope.myDate.getMonth() - 2,
$scope.myDate.getDate());

$scope.maxDate = new Date(
$scope.myDate.getFullYear(),
$scope.myDate.getMonth() + 2,
$scope.myDate.getDate());
});
$scope.maxDate = new Date(
$scope.myDate.getFullYear(),
$scope.myDate.getMonth() + 2,
$scope.myDate.getDate());
});
6 changes: 6 additions & 0 deletions src/components/datepicker/demoBasicUsage/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@
md-content {
padding-bottom: 200px;
}

.validation-messages {
font-size: 11px;
color: darkred;
margin: 10px 0 0 25px;
}

0 comments on commit c2e17ad

Please sign in to comment.