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

Commit

Permalink
feat(calendar): fix up $$mdDateLocaleProvider and use in calendar.
Browse files Browse the repository at this point in the history
  • Loading branch information
jelbourn committed Aug 13, 2015
1 parent 8443fd5 commit 36eae11
Show file tree
Hide file tree
Showing 7 changed files with 26 additions and 230 deletions.
19 changes: 13 additions & 6 deletions src/components/calendar/dateLocaleProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
* @param $locale
* @returns {DateLocale}
*/
DateLocaleProvider.prototype.$get = function($locale, $filter) {
DateLocaleProvider.prototype.$get = function($locale) {
/**
* Default date-to-string formatting function.
* @param {Date} date
Expand All @@ -63,21 +63,28 @@
return new Date(dateString);
}

// The default "short" day strings are the first character of each day.
// The default "short" day strings are the first character of each day,
// e.g., "Monday" => "M".
var defaultShortDays = $locale.DATETIME_FORMATS.DAY.map(function(day) {
return day[0];
});

// The default dates are simply the numbers 1 through 31.
var defaultDates = Array(32);
for (var i = 1; i <= 31; i++) {
defaultDates[i] = i;
}

window.$locale = $locale;
window.$filter = $filter;

var dateLocale = {
return {
months: this.months || $locale.DATETIME_FORMATS.MONTH,
shortMonths: this.shortMonths || $locale.DATETIME_FORMATS.SHORTMONTH,
days: this.days || $locale.DATETIME_FORMATS.DAY,
shortDays: this.shortDays || defaultShortDays,
formatDate: defaultFormatDate,
parseDate: defaultParseDate
dates: this.dates || defaultDates,
formatDate: this.formatDate || defaultFormatDate,
parseDate: this.parseDate || defaultParseDate
};
};

Expand Down
192 changes: 12 additions & 180 deletions src/components/calendar/datePicker.js
Original file line number Diff line number Diff line change
@@ -1,91 +1,35 @@
(function() {
'use strict';

// TODO(jelbourn): md-calendar shown in floating panel.
// TODO(jelbourn): little calendar icon next to input
// TODO(jelbourn): only one open md-calendar panel at a time per application


angular.module('material.components.calendar')
.directive('mdDatePicker', datePickerDirective);

function datePickerDirective() {
return {
template:
'<input><button type="button" ng-click="ctrl.openCalendarPane()">📅</button>' +
'<div class="md-date-calendar-pane">' +
'<md-calendar ng-model="ctrl.date" ng-if="ctrl.isCalendarOpen"></md-calendar>' +
'</div>',
// <md-calendar ng-model="ctrl.date"></md-calendar>
'<div>' +
'<input ng-model="textValue"> <br>' +
'<md-calendar ng-model="dateValue"></md-calendar>' +
'</div>',
require: ['ngModel', 'mdDatePicker'],
scope: {},
controller: DatePickerCtrl,
controllerAs: 'ctrl',
link: function(scope, element, attr, controllers) {
var ngModelCtrl = controllers[0];
var mdDatePickerCtrl = controllers[1];

mdDatePickerCtrl.configureNgModel(ngModelCtrl);
}
controllerAs: 'ctrl'
};
}

/**
* Controller for md-date-picker.
*
* @ngInject @constructor
*/
function DatePickerCtrl($scope, $element, $compile, $timeout, $mdConstant, $mdUtil,
$$mdDateLocale, $$mdDateUtil) {
/** @final */
this.$compile = $compile;

/** @final */
this.$timeout = $timeout;

function DatePickerCtrl($$mdDateLocale) {
/** @final */
this.dateLocale = $$mdDateLocale;

/** @final */
this.dateUtil = $$mdDateUtil;

/** @final */
this.$mdConstant = $mdConstant;

/* @final */
this.$mdUtil = $mdUtil;

/** @type {!angular.NgModelController} */
this.ngModelCtrl = null;

/** @type {HTMLInputElement} */
this.inputElement = $element[0].querySelector('input');

/** @type {HTMLElement} Floating calendar pane (instantiated lazily) */
this.calendarPane = $element[0].querySelector('.md-date-calendar-pane');
/** @type {string} */
this.textValue = '';

/** @type {Date} */
this.date = null;

/** @final {!angular.JQLite} */
this.$element = $element;

/** @final {!angular.Scope} */
this.$scope = $scope;

/** @type {boolean} Whether the date-picker's calendar pane is open. */
this.isCalendarOpen = false;

/** Pre-bound click handler is saved so that the event listener can be removed. */
this.bodyClickHandler = this.handleBodyClick.bind(this);

this.attachChangeListeners();
this.attachInterationListeners();

var self = this;
$scope.$on('$destroy', function() {
self.detachCalendarPane();
});
this.dateValue = null;
}

/**
Expand All @@ -97,120 +41,8 @@

var self = this;
ngModelCtrl.$render = function() {
self.date = self.ngModelCtrl.$viewValue;
self.inputElement.value = self.dateLocale.formatDate(self.date);
self.dateValue = self.ngModelCtrl.$viewValue
self.textValue = self.dateLocale.format(self.ngModelCtrl.$viewValue);
};
};

/**
* Attach event listeners for both the text input and the md-calendar.
* Events are used instead of ng-model so that updates don't infinitely update the other
* on a change. This should also be more performant than using a $watch.
*/
DatePickerCtrl.prototype.attachChangeListeners = function() {
var self = this;

self.$scope.$on('md-calendar-change', function(event, date) {
self.ngModelCtrl.$setViewValue(date);
self.inputElement.value = self.dateLocale.formatDate(date);
self.closeCalendarPane();
});

// TODO(jelbourn): debounce
self.inputElement.addEventListener('input', function() {
var parsedDate = self.dateLocale.parseDate(self.inputElement.value);
if (self.dateUtil.isValidDate(parsedDate)) {
self.date = parsedDate;
self.$scope.$apply();
}
});
};

/** Attach event listeners for user interaction. */
DatePickerCtrl.prototype.attachInterationListeners = function() {
var self = this;
var $scope = this.$scope;
var keyCodes = this.$mdConstant.KEY_CODE;

self.inputElement.addEventListener('keydown', function(event) {
$scope.$apply(function() {
if (event.altKey && event.keyCode == keyCodes.DOWN_ARROW) {
self.openCalendarPane();
}
});
});

self.$scope.$on('md-calendar-escape', function() {
self.closeCalendarPane();
});
};

/** Position and attach the floating calendar to the document. */
DatePickerCtrl.prototype.attachCalendarPane = function() {
var elementRect = this.$element[0].getBoundingClientRect();

this.calendarPane.style.left = elementRect.left + 'px';
this.calendarPane.style.top = elementRect.bottom + 'px';
document.body.appendChild(this.calendarPane);
};

/** Detach the floating calendar pane from the document. */
DatePickerCtrl.prototype.detachCalendarPane = function() {
// Use native DOM removal because we do not want any of the angular state of this element
// to be disposed.
this.calendarPane.parentNode.removeChild(this.calendarPane);
};

/** Open the floating calendar pane. */
DatePickerCtrl.prototype.openCalendarPane = function() {
if (!this.isCalendarOpen) {
this.isCalendarOpen = true;
this.attachCalendarPane();
// TODO(jelbourn): dispatch to tell other date pickers to close.
this.focusCalendar();

// Attach click listener inside of a timeout because, if this open call was triggered by a
// click, we don't want it to be immediately propogated up to the body and handled.
var self = this;
this.$timeout(function() {
document.body.addEventListener('click', self.bodyClickHandler);
}, 0, false);
}
};

/** Close the floating calendar pane. */
DatePickerCtrl.prototype.closeCalendarPane = function() {
this.isCalendarOpen = false;
this.detachCalendarPane();
this.inputElement.focus();
document.body.removeEventListener('click', this.bodyClickHandler);
};

/** Gets the controller instance for the calendar in the floating pane. */
DatePickerCtrl.prototype.getCalendarCtrl = function() {
return angular.element(this.calendarPane.querySelector('md-calendar')).controller('mdCalendar');
};

/** Focus the calendar in the floating pane. */
DatePickerCtrl.prototype.focusCalendar = function() {
// Use a timeout in order to allow the calendar to be rendered, as it is gated behind an ng-if.
var self = this;
this.$timeout(function() {
self.getCalendarCtrl().focus();
}, 0, false);
};

/**
* Handles a click on the document body when the floating calendar pane is open.
* Closes the floating calendar pane if the click is not inside of it.
* @param {MouseEvent} event
*/
DatePickerCtrl.prototype.handleBodyClick = function(event) {
if (this.isCalendarOpen) {
var isInCalendar = this.$mdUtil.getClosest(event.target, 'md-calendar');
if (!isInCalendar) {
this.closeCalendarPane();
}
}
};
}
})();
28 changes: 0 additions & 28 deletions src/components/calendar/demoBasicUsage/index.html

This file was deleted.

12 changes: 0 additions & 12 deletions src/components/calendar/demoBasicUsage/script.js

This file was deleted.

1 change: 0 additions & 1 deletion src/components/calendar/demoBasicUsage/style.css

This file was deleted.

1 change: 1 addition & 0 deletions src/components/calendar/demoDatePicker/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ <h2>Development tools</h2>
<p>Here is a bunch of stuff after the calendar</p>
<p>Here is a bunch of stuff after the calendar</p>
<p>Here is a bunch of stuff after the calendar</p>
<input>

</md-content>
</div>
3 changes: 0 additions & 3 deletions src/components/calendar/demoDatePicker/style.css
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
/** Demo styles for mdCalendar. */
.md-date-picker {
border: 2px solid darkred;
}

0 comments on commit 36eae11

Please sign in to comment.