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

feat(datepicker): is-open attribute, ability to hide icons, demo layout #8743

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions src/components/datepicker/datePicker.scss
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ md-datepicker {
// If the datepicker is inside of a md-input-container
._md-datepicker-floating-label {
> label:not(.md-no-float):not(._md-container-ignore) {
$offset: $md-datepicker-triangle-button-width + $md-datepicker-button-gap + $icon-button-margin + $baseline-grid;
@include rtl-prop(left, right, $offset);
width: calc(100% - #{$offset + $md-datepicker-triangle-button-width});
$offset: $md-datepicker-triangle-button-width * 2 + $md-datepicker-button-gap + $icon-button-margin + $baseline-grid;
@include rtl(right, $icon-button-margin, auto);
@include rtl(left, auto, $icon-button-margin);
width: calc(100% - #{$offset});
}

> md-datepicker {
Expand Down Expand Up @@ -80,7 +81,10 @@ md-datepicker {

display: inline-block;
width: auto;
@include rtl-prop(margin-left, margin-right, $md-datepicker-button-gap);

.md-icon-button + & {
@include rtl-prop(margin-left, margin-right, $md-datepicker-button-gap);
}

&.md-datepicker-focused {
border-bottom-width: 2px;
Expand Down Expand Up @@ -209,13 +213,15 @@ md-datepicker[disabled] {
overflow: hidden;

.md-datepicker-input-container {
@include rtl-prop(margin-left, margin-right, -$md-datepicker-button-gap);

// The negative bottom margin prevents the content around the datepicker
// from jumping when it gets opened.
margin-bottom: -$md-datepicker-border-bottom-gap;
}

.md-icon-button + .md-datepicker-input-container {
@include rtl-prop(margin-left, margin-right, -$md-datepicker-button-gap);
}

.md-datepicker-input,
label:not(.md-no-float):not(._md-container-ignore) {
margin-bottom: -$md-datepicker-border-bottom-gap;
Expand Down
117 changes: 69 additions & 48 deletions src/components/datepicker/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
@@ -1,59 +1,80 @@
<div ng-controller="AppCtrl" style='padding: 40px;' ng-cloak>
<md-content>

<h4>Standard date-picker</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date"></md-datepicker>

<h4>Disabled date-picker</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date" disabled></md-datepicker>

<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>Only weekends are selectable</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
md-date-filter="onlyWeekendsPredicate"></md-datepicker>

<h4>Only weekends within given range are selectable</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
md-min-date="minDate" md-max-date="maxDate"
md-date-filter="onlyWeekendsPredicate"></md-datepicker>

<h4>Opening the calendar when the input is focused</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
md-open-on-focus></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-date-filter="onlyWeekendsPredicate"></md-datepicker>

<div class="validation-messages" ng-messages="myForm.dateField.$error">
<div ng-message="valid">The entered value is not a date!</div>
<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 ng-message="filtered">Only weekends are allowed!</div>
<div ng-controller="AppCtrl" ng-cloak>
<md-content layout-padding>

<div layout-gt-xs="row">
<div flex-gt-xs>
<h4>Standard date-picker</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date"></md-datepicker>
</div>

<div flex-gt-xs>
<h4>Disabled date-picker</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date" disabled></md-datepicker>
</div>
</div>

<div layout-gt-xs="row">
<div flex-gt-xs>
<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>
</div>

<div flex-gt-xs>
<h4>Only weekends are selectable</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
md-date-filter="onlyWeekendsPredicate"></md-datepicker>
</div>
</form>
</div>

<h4>Inside a md-input-container</h4>
<form name="myOtherForm">
<md-input-container>
<label>Enter date</label>
<div layout-gt-xs="row">
<div flex-gt-xs>
<h4>Only weekends within given range are selectable</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
md-min-date="minDate" md-max-date="maxDate"
md-date-filter="onlyWeekendsPredicate"></md-datepicker>
</div>

<div flex-gt-xs>
<h4>Opening the calendar when the input is focused</h4>
<md-datepicker ng-model="myDate" md-placeholder="Enter date"
md-open-on-focus></md-datepicker>
</div>
</div>

<md-datepicker ng-model="myDate" name="dateField" md-min-date="minDate"
md-max-date="maxDate"></md-datepicker>
<div layout-gt-xs="row">
<form name="myForm" flex-gt-xs>
<h4>With ngMessages</h4>
<md-datepicker name="dateField" ng-model="myDate" md-placeholder="Enter date"
required md-min-date="minDate" md-max-date="maxDate"
md-date-filter="onlyWeekendsPredicate"></md-datepicker>

<div class="validation-messages" ng-messages="myOtherForm.dateField.$error">
<div class="validation-messages" ng-messages="myForm.dateField.$error">
<div ng-message="valid">The entered value is not a date!</div>
<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 ng-message="filtered">Only weekends are allowed!</div>
</div>
</md-input-container>
</form>
</form>

<form name="myOtherForm" flex-gt-xs>
<h4>Inside a md-input-container</h4>

<md-input-container>
<label>Enter date</label>
<md-datepicker ng-model="myDate" name="dateField" md-min-date="minDate"
md-max-date="maxDate"></md-datepicker>

<div class="validation-messages" ng-messages="myOtherForm.dateField.$error">
<div ng-message="valid">The entered value is not a date!</div>
<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>
</md-input-container>
</form>
</div>

</md-content>
</div>
4 changes: 2 additions & 2 deletions src/components/datepicker/demoBasicUsage/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ angular.module('datepickerBasicUsage',
$scope.myDate.getFullYear(),
$scope.myDate.getMonth() + 2,
$scope.myDate.getDate());

$scope.onlyWeekendsPredicate = function(date) {
var day = date.getDay();
return day === 0 || day === 6;
}
};
});
99 changes: 66 additions & 33 deletions src/components/datepicker/js/datepickerDirective.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
* @param {(function(Date): boolean)=} md-date-filter Function expecting a date and returning a boolean whether it can be selected or not.
* @param {String=} md-placeholder The date input placeholder value.
* @param {String=} md-open-on-focus When present, the calendar will be opened when the input is focused.
* @param {Boolean=} md-is-open Expression that can be used to open the datepicker's calendar on-demand.
* @param {String=} md-hide-icons Determines which datepicker icons should be hidden. Note that this may cause the
* datepicker to not align properly with other components. **Use at your own risk.** Possible values are:
* * `"all"` - Hides all icons.
* * `"calendar"` - Only hides the calendar icon.
* * `"triangle"` - Only hides the triangle icon.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice idea 👍

* @param {boolean=} ng-disabled Whether the datepicker is disabled.
* @param {boolean=} ng-required Whether a value is required for the datepicker.
*
Expand All @@ -49,47 +55,58 @@
*/
function datePickerDirective($$mdSvgRegistry) {
return {
template:
// Buttons are not in the tab order because users can open the calendar via keyboard
// interaction on the text input, and multiple tab stops for one component (picker)
// may be confusing.
template: function(tElement, tAttrs) {
// Buttons are not in the tab order because users can open the calendar via keyboard
// interaction on the text input, and multiple tab stops for one component (picker)
// may be confusing.
var hiddenIcons = tAttrs.mdHideIcons;

var calendarButton = (hiddenIcons === 'all' || hiddenIcons === 'calendar') ? '' :
'<md-button class="md-datepicker-button md-icon-button" type="button" ' +
'tabindex="-1" aria-hidden="true" ' +
'ng-click="ctrl.openCalendarPane($event)">' +
'<md-icon class="md-datepicker-calendar-icon" aria-label="md-calendar" ' +
'md-svg-src="' + $$mdSvgRegistry.mdCalendar + '"></md-icon>' +
'</md-button>' +
'<div class="md-datepicker-input-container" ' +
'ng-class="{\'md-datepicker-focused\': ctrl.isFocused}">' +
'<input class="md-datepicker-input" aria-haspopup="true" ' +
'ng-focus="ctrl.setFocused(true)" ng-blur="ctrl.setFocused(false)">' +
'<md-button type="button" md-no-ink ' +
'class="md-datepicker-triangle-button md-icon-button" ' +
'ng-click="ctrl.openCalendarPane($event)" ' +
'aria-label="{{::ctrl.dateLocale.msgOpenCalendar}}">' +
'<div class="md-datepicker-expand-triangle"></div>' +
'</md-button>' +
'</md-button>';

var triangleButton = (hiddenIcons === 'all' || hiddenIcons === 'triangle') ? '' :
'<md-button type="button" md-no-ink ' +
'class="md-datepicker-triangle-button md-icon-button" ' +
'ng-click="ctrl.openCalendarPane($event)" ' +
'aria-label="{{::ctrl.dateLocale.msgOpenCalendar}}">' +
'<div class="md-datepicker-expand-triangle"></div>' +
'</md-button>';

return '' +
calendarButton +
'<div class="md-datepicker-input-container" ' +
'ng-class="{\'md-datepicker-focused\': ctrl.isFocused}">' +
'<input class="md-datepicker-input" aria-haspopup="true" ' +
'ng-focus="ctrl.setFocused(true)" ng-blur="ctrl.setFocused(false)">' +
triangleButton +
'</div>' +

// This pane will be detached from here and re-attached to the document body.
'<div class="md-datepicker-calendar-pane md-whiteframe-z1">' +
'<div class="md-datepicker-input-mask">' +
'<div class="md-datepicker-input-mask-opaque"></div>' +
'</div>' +

// This pane will be detached from here and re-attached to the document body.
'<div class="md-datepicker-calendar-pane md-whiteframe-z1">' +
'<div class="md-datepicker-input-mask">' +
'<div class="md-datepicker-input-mask-opaque"></div>' +
'</div>' +
'<div class="md-datepicker-calendar">' +
'<md-calendar role="dialog" aria-label="{{::ctrl.dateLocale.msgCalendar}}" ' +
'md-min-date="ctrl.minDate" md-max-date="ctrl.maxDate"' +
'md-date-filter="ctrl.dateFilter"' +
'ng-model="ctrl.date" ng-if="ctrl.isCalendarOpen">' +
'</md-calendar>' +
'</div>' +
'</div>',
'<div class="md-datepicker-calendar">' +
'<md-calendar role="dialog" aria-label="{{::ctrl.dateLocale.msgCalendar}}" ' +
'md-min-date="ctrl.minDate" md-max-date="ctrl.maxDate"' +
'md-date-filter="ctrl.dateFilter"' +
'ng-model="ctrl.date" ng-if="ctrl.isCalendarOpen">' +
'</md-calendar>' +
'</div>' +
'</div>';
},
require: ['ngModel', 'mdDatepicker', '?^mdInputContainer'],
scope: {
minDate: '=mdMinDate',
maxDate: '=mdMaxDate',
placeholder: '@mdPlaceholder',
dateFilter: '=mdDateFilter'
dateFilter: '=mdDateFilter',
isOpen: '=?mdIsOpen'
},
controller: DatePickerCtrl,
controllerAs: 'ctrl',
Expand Down Expand Up @@ -287,9 +304,22 @@
this.attachInteractionListeners();

var self = this;

$scope.$on('$destroy', function() {
self.detachCalendarPane();
});

if ($attrs.mdIsOpen) {
$scope.$watch('ctrl.isOpen', function(shouldBeOpen) {
if (shouldBeOpen) {
self.openCalendarPane({
target: self.inputElement
});
} else {
self.closeCalendarPane();
}
});
}
}

/**
Expand Down Expand Up @@ -396,7 +426,10 @@
DatePickerCtrl.prototype.setDisabled = function(isDisabled) {
this.isDisabled = isDisabled;
this.inputElement.disabled = isDisabled;
this.calendarButton.disabled = isDisabled;

if (this.calendarButton) {
this.calendarButton.disabled = isDisabled;
}
};

/**
Expand Down Expand Up @@ -594,7 +627,7 @@
*/
DatePickerCtrl.prototype.openCalendarPane = function(event) {
if (!this.isCalendarOpen && !this.isDisabled) {
this.isCalendarOpen = true;
this.isCalendarOpen = this.isOpen = true;
this.calendarPaneOpenedFrom = event.target;

// Because the calendar pane is attached directly to the body, it is possible that the
Expand Down Expand Up @@ -643,7 +676,7 @@

function detach() {
self.detachCalendarPane();
self.isCalendarOpen = false;
self.isCalendarOpen = self.isOpen = false;
self.ngModelCtrl.$setTouched();

self.documentElement.off('click touchstart', self.bodyClickHandler);
Expand Down
Loading