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 7fe7931 commit f3457b8
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 50 deletions.
92 changes: 48 additions & 44 deletions src/components/calendar/calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@
angular.module('material.components.calendar', ['material.core'])
.directive('mdCalendar', calendarDirective);

// TODO(jelbourn): i18n [month names, day names, days of month, date formatting]
// TODO(jelbourn): Date cell IDs need to be unique per-calendar.

// TODO(jelbourn): a11y (announcements and labels)
// TODO(jelbourn): internationalize a11y announcements.

// TODO(jelbourn): Update the selected date on [click, tap, enter]

// TODO(jelbourn): Shown month transition on [swipe, scroll, keyboard, ngModel change]
// TODO(jelbourn): Introduce free scrolling that works w/ mobile momemtum scrolling (+snapping)

Expand All @@ -26,24 +23,17 @@
// TODO(jelbourn): Minimum and maximum date
// TODO(jelbourn): Make sure the *time* on the written date makes sense (probably midnight).
// TODO(jelbourn): Refactor "sections" into separate files.
// TODO(jelbourn): Highlight today.
// TODO(jelbourn): Horizontal line between months (pending spec finalization)
// TODO(jelbourn): Alt+down in date input to open calendar
// TODO(jelbourn): Animations should use `.finally()` instead of `.then()`

var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

var fullMonths = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
'September', 'October', 'November', 'December'];
var fullDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
// TODO(jelbourn): improve default date parser in locale provider.
// TODO(jelbourn): read-only state.

function calendarDirective() {
return {
template:
'<div>' +
'<table class="md-calendar-day-header"><thead><tr>' +
'<th>S</th><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th>' +
'</tr></thead></table>' +
'<table class="md-calendar-day-header"><thead></thead></table>' +
'<div class="md-calendar-container">' +
'<table class="md-calendar"></table>' +
'</div>' +
Expand Down Expand Up @@ -74,20 +64,6 @@
DISTANT_PAST: 4
};

// TODO(jelbourn): Refactor this to core and share with other components.
/** @enum {number} */
var Keys = {
ENTER: 13,
PAGE_UP: 33,
PAGE_DOWN: 34,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40
};

/** Class applied to the selected date cell/. */
var SELECTED_DATE_CLASS = 'md-calendar-selected-date';

Expand All @@ -108,7 +84,9 @@
* Controller for the mdCalendar component.
* @ngInject @constructor
*/
function CalendarCtrl($element, $scope, $animate, $q, $$mdDateUtil, $$mdDateLocale, $mdInkRipple, $mdUtil) {
function CalendarCtrl($element, $scope, $animate, $q, $mdConstant,
$$mdDateUtil, $$mdDateLocale, $mdInkRipple, $mdUtil) {

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

Expand All @@ -121,9 +99,15 @@
/** @final */
this.$mdUtil = $mdUtil;

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

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

/** @final */
this.dateLocale = $$mdDateLocale;

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

Expand Down Expand Up @@ -209,6 +193,8 @@
* Initialization should occur after the ngModel value is known.
*/
CalendarCtrl.prototype.buildInitialCalendarDisplay = function() {
this.buildWeekHeader();

this.displayDate = this.selectedDate || new Date();
var nextMonth = this.dateUtil.getDateInNextMonth(this.displayDate);
this.calendarElement.appendChild(this.buildCalendarForMonth(this.displayDate));
Expand Down Expand Up @@ -250,7 +236,7 @@
this.$scope.$apply(function() {
// Handled key events fall into two categories: selection and navigation.
// Start by checking if this is a selection event.
if (event.which === Keys.ENTER) {
if (event.which === self.keyCode.ENTER) {
self.ngModelCtrl.$setViewValue(self.displayDate);
self.ngModelCtrl.$render();
event.preventDefault();
Expand Down Expand Up @@ -280,16 +266,17 @@
*/
CalendarCtrl.prototype.getFocusDateFromKeyEvent = function(event) {
var dateUtil = this.dateUtil;
var keyCode = this.keyCode;

switch (event.which) {
case Keys.RIGHT: return dateUtil.incrementDays(this.displayDate, 1);
case Keys.LEFT: return dateUtil.incrementDays(this.displayDate, -1);
case Keys.DOWN: return dateUtil.incrementDays(this.displayDate, 7);
case Keys.UP: return dateUtil.incrementDays(this.displayDate, -7);
case Keys.PAGE_DOWN: return dateUtil.incrementMonths(this.displayDate, 1);
case Keys.PAGE_UP: return dateUtil.incrementMonths(this.displayDate, -1);
case Keys.HOME: return dateUtil.getFirstDateOfMonth(this.displayDate);
case Keys.END: return dateUtil.getLastDateOfMonth(this.displayDate);
case keyCode.RIGHT_ARROW: return dateUtil.incrementDays(this.displayDate, 1);
case keyCode.LEFT_ARROW: return dateUtil.incrementDays(this.displayDate, -1);
case keyCode.DOWN_ARROW: return dateUtil.incrementDays(this.displayDate, 7);
case keyCode.UP_ARROW: return dateUtil.incrementDays(this.displayDate, -7);
case keyCode.PAGE_DOWN: return dateUtil.incrementMonths(this.displayDate, 1);
case keyCode.PAGE_UP: return dateUtil.incrementMonths(this.displayDate, -1);
case keyCode.HOME: return dateUtil.getFirstDateOfMonth(this.displayDate);
case keyCode.END: return dateUtil.getLastDateOfMonth(this.displayDate);
default: return this.displayDate;
}
};
Expand Down Expand Up @@ -572,11 +559,13 @@
var annoucement = '';

if (!previousDate || !this.dateUtil.isSameMonthAndYear(previousDate, currentDate)) {
annoucement += currentDate.getFullYear() + '. ' + fullMonths[currentDate.getMonth()] + '. ';
annoucement += currentDate.getFullYear() +
'. ' +
this.dateLocale.months[currentDate.getMonth()] + '. ';
}

if (previousDate.getDate() !== currentDate.getDate()) {
annoucement += fullDays[currentDate.getDay()] + '. ' + currentDate.getDate() ;
annoucement += this.dateLocale.days[currentDate.getDay()] + '. ' + currentDate.getDate() ;
}

this.ariaLiveElement.textContent = annoucement;
Expand All @@ -585,6 +574,21 @@

/*** Constructing the calendar table ***/

/**
* Builds and appends a day-of-the-week header to the calendar.
* This should only need to be called once during initialization.
*/
CalendarCtrl.prototype.buildWeekHeader = function() {
var row = document.createElement('tr');
for (var i = 0; i < 7; i++) {
var th = document.createElement('th');
th.textContent = this.dateLocale.shortDays[i];
row.appendChild(th);
}

this.$element.find('thead').append(row);
};

/**
* Creates a single cell to contain a date in the calendar with all appropriate
* attributes and classes added. If a date is given, the cell content will be set
Expand All @@ -601,7 +605,7 @@
var selectionIndicator = document.createElement('span');
cell.appendChild(selectionIndicator);
selectionIndicator.classList.add('md-calendar-date-selection-indicator');
selectionIndicator.textContent = opt_date.getDate();
selectionIndicator.textContent = this.dateLocale.dates[opt_date.getDate()];
//selectionIndicator.setAttribute('aria-label', '');

cell.setAttribute('tabindex', '-1');
Expand Down Expand Up @@ -629,7 +633,7 @@
// Store rows for the month in a document fragment so that we can append them all at once.
var monthBody = document.createElement('tbody');
monthBody.classList.add('md-calendar-month');
monthBody.setAttribute('aria-hidden', 'true')
monthBody.setAttribute('aria-hidden', 'true');

var row = document.createElement('tr');
monthBody.appendChild(row);
Expand All @@ -642,15 +646,15 @@
monthLabelCell.classList.add('md-calendar-month-label');
if (firstDayOfTheWeek <= 1) {
monthLabelCell.setAttribute('colspan', '7');
monthLabelCell.textContent = months[date.getMonth()];
monthLabelCell.textContent = this.dateLocale.shortMonths[date.getMonth()];

var monthLabelRow = document.createElement('tr');
monthLabelRow.appendChild(monthLabelCell);
monthBody.insertBefore(monthLabelRow, row);
} else {
blankCellOffset = 2;
monthLabelCell.setAttribute('colspan', '2');
monthLabelCell.textContent = months[date.getMonth()];
monthLabelCell.textContent = this.dateLocale.shortMonths[date.getMonth()];

row.appendChild(monthLabelCell);
}
Expand Down
88 changes: 88 additions & 0 deletions src/components/calendar/dateLocale.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@

describe('$$mdDateLocale', function() {
var dateLocale, dateUtil;

// Fake $locale with made-up days and months.
var $localeFake = {
DATETIME_FORMATS: {
DAY: ['Sundog', 'Mondog', 'Tuesdog', 'Wednesdog', 'Thursdog', 'Fridog', 'Saturdog'],
MONTH: ['JanZ', 'FebZ', 'MarZ', 'AprZ', 'MayZ', 'JunZ', 'JulZ', 'AugZ', 'SeptZ',
'OctZ', 'NovZ', 'DecZ'],
SHORTMONTH: ['JZ', 'FZ', 'MZ', 'AZ', 'MZ', 'JZ', 'JZ', 'AZ', 'SZ', 'OZ', 'NZ', 'DZ']
}
};

beforeEach(module('material.components.calendar'));

describe('with default values', function() {
beforeEach(module(function($provide) {
$provide.value('$locale', $localeFake);
}));

beforeEach(inject(function($$mdDateLocale, $$mdDateUtil) {
dateLocale = $$mdDateLocale;
dateUtil = $$mdDateUtil;
}));

it('should expose default days, months, and dates', function() {
expect(dateLocale.months).toEqual($localeFake.DATETIME_FORMATS.MONTH);
expect(dateLocale.shortMonths).toEqual($localeFake.DATETIME_FORMATS.SHORTMONTH);
expect(dateLocale.days).toEqual($localeFake.DATETIME_FORMATS.DAY);
expect(dateLocale.shortDays).toEqual(['S', 'M', 'T', 'W', 'T', 'F', 'S']);
expect(dateLocale.dates).toEqual([undefined, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]);
});

it('should have a default date formatter', function() {
var date = new Date(2014, 2, 25);
var dateStr = dateLocale.formatDate(date);
expect(dateStr).toEqual(jasmine.any(String));
expect(dateStr).toBeTruthy();
});

it('should have a default date parser', function() {
var dateStr = '2014-03-25';
expect(dateLocale.parseDate(dateStr)).toEqual(jasmine.any(Date));
});
});

describe('with custom values', function() {
var fakeMonths = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'];
var fakeshortMonths = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'j', 'l'];
var fakeDays = ['D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7'];
var fakeShortDays = ['1', '2', '3', '4', '5', '6', '7'];
var fakeDates = [undefined, 'X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8', 'X9', 'X10', 'X11',
'X12', 'X13', 'X14', 'X15', 'X16', 'X17', 'X18', 'X19', 'X20', 'X21', 'X22', 'X23', 'X24',
'X25', 'X26', 'X27', 'X28', 'X29', 'X30', 'X31'];

beforeEach(module(function($$mdDateLocaleProvider) {
$$mdDateLocaleProvider.months = fakeMonths;
$$mdDateLocaleProvider.shortMonths = fakeshortMonths;
$$mdDateLocaleProvider.days = fakeDays;
$$mdDateLocaleProvider.shortDays = fakeShortDays;
$$mdDateLocaleProvider.dates = fakeDates;
$$mdDateLocaleProvider.formatDate = function() {
return 'Your birthday!'
};
$$mdDateLocaleProvider.parseDate = function() {
return new Date(1969, 6, 16);
};
}));


beforeEach(inject(function($$mdDateLocale, $$mdDateUtil) {
dateLocale = $$mdDateLocale;
dateUtil = $$mdDateUtil;
}));

it('should expose custom settings', function() {
expect(dateLocale.months).toEqual(fakeMonths);
expect(dateLocale.shortMonths).toEqual(fakeshortMonths);
expect(dateLocale.days).toEqual(fakeDays);
expect(dateLocale.shortDays).toEqual(fakeShortDays);
expect(dateLocale.dates).toEqual(fakeDates);
expect(dateLocale.formatDate(new Date())).toEqual('Your birthday!');
expect(dateLocale.parseDate('2020-01-20')).toEqual(new Date(1969, 6, 16));
});
});
});
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
48 changes: 48 additions & 0 deletions src/components/calendar/datePicker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
(function() {
'use strict';

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

function datePickerDirective() {
return {
template:
'<div>' +
'<input ng-model="textValue"> <br>' +
'<md-calendar ng-model="dateValue"></md-calendar>' +
'</div>',
require: ['ngModel', 'mdDatePicker'],
scope: {},
controller: DatePickerCtrl,
controllerAs: 'ctrl'
};
}

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

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

/** @type {string} */
this.textValue = '';

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

/**
* Sets up the controller's reference to ngModelController.
* @param {!angular.NgModelController} ngModelCtrl
*/
DatePickerCtrl.prototype.configureNgModel = function(ngModelCtrl) {
this.ngModelCtrl = ngModelCtrl;

var self = this;
ngModelCtrl.$render = function() {
self.dateValue = self.ngModelCtrl.$viewValue
self.textValue = self.dateLocale.format(self.ngModelCtrl.$viewValue);
};
}
})();
Loading

0 comments on commit f3457b8

Please sign in to comment.