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

Commit 34eb48c

Browse files
committed
feat(calendar): imeplement virtual scrolling in calendar
1 parent 15c7362 commit 34eb48c

File tree

14 files changed

+712
-441
lines changed

14 files changed

+712
-441
lines changed

src/components/calendar/calendar.js

Lines changed: 119 additions & 364 deletions
Large diffs are not rendered by default.

src/components/calendar/calendar.scss

Lines changed: 106 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,91 @@
11
// Styles for mdCalendar.
2-
$date-cell-size: 44px !default;
3-
$date-cell-emphasis-size: 40px !default;
4-
$calendar-number-of-weeks: 7 !default;
5-
6-
// Style applied to date cells, including day-of-the-week header cells.
7-
@mixin calendar-date-cell() {
8-
height: $date-cell-size;
9-
width: $date-cell-size;
2+
$md-calendar-cell-size: 44px !default;
3+
$md-calendar-header-height: 40px;
4+
$md-calendar-cell-emphasis-size: 40px !default;
5+
$md-calendar-side-padding: 16px !default;
6+
$md-calendar-weeks-to-show: 7 !default;
7+
8+
$md-calendar-month-label-padding: 8px !default;
9+
$md-calendar-month-label-font-size: 13px !default;
10+
11+
$md-calendar-width: (7 * $md-calendar-cell-size) + (2 * $md-calendar-side-padding);
12+
$md-calendar-height:
13+
($md-calendar-weeks-to-show * $md-calendar-cell-size) + $md-calendar-header-height;
14+
15+
16+
// Styles for date cells, including day-of-the-week header cells.
17+
@mixin md-calendar-cell() {
18+
height: $md-calendar-cell-size;
19+
width: $md-calendar-cell-size;
20+
1021
text-align: center;
22+
23+
// Remove all padding and borders so we can completely
24+
// control the size of the table cells.
25+
padding: 0;
26+
border: none;
27+
28+
// The left / right padding is applied to the cells instead of the wrapper
29+
// because we want the header background and the month dividing border to
30+
// extend the entire width of the calendar.
31+
&:first-child {
32+
padding-left: $md-calendar-side-padding;
33+
}
34+
35+
&:last-child {
36+
padding-right: $md-calendar-side-padding;
37+
}
38+
}
39+
40+
// Styles for tables used in mdCalendar (the day-of-the-week header and the table of dates itself).
41+
@mixin md-calendar-table() {
42+
// Fixed table layout makes IE faster.
43+
// https://msdn.microsoft.com/en-us/library/ms533020(VS.85).aspx
44+
table-layout: fixed;
45+
border-spacing: 0;
46+
border-collapse: collapse;
1147
}
1248

1349
md-calendar {
14-
font-size: 12px;
50+
font-size: 13px;
51+
user-select: none;
1552
}
1653

17-
.md-calendar-container {
18-
position: relative;
19-
max-height: $calendar-number-of-weeks * $date-cell-size;
54+
// Wrap the scroll with overflow: hidden in order to hide the scrollbar.
55+
// The inner .md-calendar-scroller will using a padding-right to push the
56+
// scrollbar into the hidden area (done with javascript).
57+
.md-calendar-scroll-mask {
58+
display: inline-block;
2059
overflow: hidden;
60+
height: $md-calendar-weeks-to-show * $md-calendar-cell-size;
61+
62+
.md-virtual-repeat-scroller {
63+
overflow-y: scroll;
64+
-webkit-overflow-scrolling: touch;
65+
}
66+
67+
.md-virtual-repeat-offsetter {
68+
width: 100%;
69+
}
70+
}
71+
72+
// Scrolling element (the md-virtual-repeat-container).
73+
.md-calendar-scroller {
74+
display: inline-block;
75+
height: $md-calendar-weeks-to-show * $md-calendar-cell-size;
76+
width: $md-calendar-width;
77+
78+
&::-webkit-scrollbar {
79+
display: none;
80+
}
2181
}
2282

83+
// A single date cell in the calendar table.
2384
.md-calendar-date {
24-
@include calendar-date-cell();
85+
@include md-calendar-cell();
2586
}
2687

88+
// Element inside of every date cell that can indicate that the date is selected.
2789
.md-calendar-date-selection-indicator {
2890
transition-property: background-color, color;
2991
transition-duration: $swift-ease-out-duration;
@@ -34,25 +96,44 @@ md-calendar {
3496

3597
cursor: pointer;
3698

37-
width: $date-cell-emphasis-size;
38-
height: $date-cell-emphasis-size;
39-
line-height: $date-cell-emphasis-size;
99+
width: $md-calendar-cell-emphasis-size;
100+
height: $md-calendar-cell-emphasis-size;
101+
line-height: $md-calendar-cell-emphasis-size;
40102
}
41103

104+
// The label above each month (containing the month name and the year, e.g. "Jun 2014").
42105
.md-calendar-month-label {
43-
height: $date-cell-size;
106+
height: $md-calendar-cell-size;
107+
font-size: $md-calendar-month-label-font-size;
108+
padding: 0 0 0 $md-calendar-side-padding + $md-calendar-month-label-padding;
44109
}
45110

46-
.md-calendar-day-header th {
47-
@include calendar-date-cell();
48-
font-weight: normal;
111+
// Table containing the day-of-the-week header.
112+
.md-calendar-day-header {
113+
@include md-calendar-table();
114+
115+
th {
116+
@include md-calendar-cell();
117+
font-weight: normal;
118+
height: $md-calendar-header-height;
119+
}
49120
}
50121

122+
// Primary table containing all date cells. Each month is a tbody in this table.
51123
.md-calendar {
52-
// DEBUGGING: add border to container
53-
border: 1px dotted lightgray;
54-
}
124+
@include md-calendar-table();
125+
color: rgba(black, 0.7); // secondary
55126

56-
.md-calendar.ng-animate {
57-
transition: transform $swift-ease-in-out-duration $swift-ease-in-out-timing-function;
127+
// Divider between months.
128+
tr:last-child td {
129+
border-bottom: 1px solid #e9e9e9;
130+
}
131+
132+
// The divider between months doesn't actualyl change the height of the tbody in which the
133+
// border appear; it changes the height of the following tbody. The causes the first-child to be
134+
// 1px shorter than the other months. We fix this by adding an invisible border-top.
135+
&:first-child {
136+
border-top: 1px solid transparent;
137+
}
58138
}
139+

src/components/calendar/calendar.spec.js

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ describe('md-calendar', function() {
55
var JAN = 0, FEB = 1, MAR = 2, APR = 3, MAY = 4, JUN = 5, JUL = 6, AUG = 7, SEP = 8, OCT = 9,
66
NOV = 10, DEC = 11;
77

8-
var ngElement, element, scope, pageScope, controller, $animate, $compile;
9-
var $rootScope, dateLocale;
8+
var ngElement, element, scope, pageScope, controller, $animate, $compile, $$rAF;
9+
var $rootScope, dateLocale, $mdUtil;
1010

1111
/**
1212
* To apply a change in the date, a scope $apply() AND a manual triggering of animation
@@ -15,11 +15,16 @@ describe('md-calendar', function() {
1515
function applyDateChange() {
1616
pageScope.$apply();
1717
$animate.triggerCallbacks();
18+
$$rAF.flush();
19+
20+
// Internally, the calendar sets scrollTop to scroll to the month for a change.
21+
// The handler for that scroll won't be invoked unless we manually trigger it.
22+
if (controller) {
23+
angular.element(controller.calendarScroller).triggerHandler('scroll');
24+
}
1825
}
1926

20-
/**
21-
* Extracts text as an array (one element per cell) from a tr element.
22-
*/
27+
/** Extracts text as an array (one element per cell) from a tr element. */
2328
function extractRowText(tr) {
2429
var cellContents = [];
2530
angular.forEach(tr.children, function(tableElement) {
@@ -29,9 +34,7 @@ describe('md-calendar', function() {
2934
return cellContents;
3035
}
3136

32-
/**
33-
* Finds a date td given a day of the month from an .md-calendar-month element.
34-
*/
37+
/** Finds a date td given a day of the month from an .md-calendar-month element. */
3538
function findDateElement(monthElement, day) {
3639
var tds = monthElement.querySelectorAll('td');
3740
var td;
@@ -45,14 +48,14 @@ describe('md-calendar', function() {
4548
}
4649

4750

48-
/**
49-
* Creates and compiles an md-calendar element.
50-
*/
51+
/** Creates and compiles an md-calendar element. */
5152
function createElement(parentScope) {
5253
var directiveScope = parentScope || $rootScope.$new();
5354
var template = '<md-calendar ng-model="myDate"></md-calendar>';
54-
var newElement = $compile(template)(directiveScope);
55-
directiveScope.$apply();
55+
var attachedElement = angular.element(template);
56+
document.body.appendChild(attachedElement[0]);
57+
var newElement = $compile(attachedElement)(directiveScope);
58+
applyDateChange();
5659
return newElement;
5760
}
5861

@@ -62,7 +65,9 @@ describe('md-calendar', function() {
6265
$animate = $injector.get('$animate');
6366
$compile = $injector.get('$compile');
6467
$rootScope = $injector.get('$rootScope');
68+
$$rAF = $injector.get('$$rAF');
6569
dateLocale = $injector.get('$$mdDateLocale');
70+
$mdUtil = $injector.get('$mdUtil');
6671

6772
pageScope = $rootScope.$new();
6873
pageScope.myDate = null;
@@ -73,16 +78,22 @@ describe('md-calendar', function() {
7378
controller = ngElement.controller('mdCalendar');
7479
}));
7580

81+
afterEach(function() {
82+
ngElement.remove();
83+
});
84+
7685
describe('ngModel binding', function() {
7786

7887
it('should update the calendar based on ngModel change', function() {
7988
pageScope.myDate = new Date(2014, MAY, 30);
89+
8090
applyDateChange();
8191

82-
var displayedMonth = element.querySelector('.md-calendar-month-label');
8392
var selectedDate = element.querySelector('.md-calendar-selected-date');
93+
var displayedMonth =
94+
$mdUtil.getClosest(selectedDate, 'tbody').querySelector('.md-calendar-month-label');
8495

85-
expect(displayedMonth.textContent).toBe('May');
96+
expect(displayedMonth.textContent).toBe('May 2014');
8697
expect(selectedDate.textContent).toBe('30');
8798
});
8899

@@ -109,9 +120,16 @@ describe('md-calendar', function() {
109120
});
110121

111122
describe('#buildCalendarForMonth', function() {
123+
var monthCtrl;
124+
125+
beforeEach(function() {
126+
monthCtrl = angular.element(element.querySelector('[md-calendar-month]'))
127+
.controller('mdCalendarMonth');
128+
});
129+
112130
it('should render a month correctly as a table', function() {
113131
var date = new Date(2014, MAY, 30);
114-
var monthElement = controller.buildCalendarForMonth(date);
132+
var monthElement = monthCtrl.buildCalendarForMonth(date);
115133

116134
var calendarRows = monthElement.querySelectorAll('tr');
117135
var calendarDates = [];
@@ -121,21 +139,22 @@ describe('md-calendar', function() {
121139
});
122140

123141
var expectedDates = [
124-
['May', '', '', '1', '2', '3'],
142+
['May 2014', '', '', '1', '2', '3'],
125143
['4', '5', '6', '7', '8', '9', '10'],
126144
['11', '12', '13', '14', '15', '16', '17'],
127145
['18', '19', '20', '21', '22', '23', '24'],
128146
['25', '26', '27', '28', '29', '30', '31'],
147+
['', '', '', '', '', '', ''],
129148
];
130149
expect(calendarDates).toEqual(expectedDates);
131150
});
132151

133152
it('should show the month on its own row if the first day is before Tuesday', function() {
134153
var date = new Date(2014, JUN, 30); // 1st on Sunday
135-
var monthElement = controller.buildCalendarForMonth(date);
154+
var monthElement = monthCtrl.buildCalendarForMonth(date);
136155

137156
var firstRow = monthElement.querySelector('tr');
138-
expect(extractRowText(firstRow)).toEqual(['Jun']);
157+
expect(extractRowText(firstRow)).toEqual(['Jun 2014']);
139158
});
140159
});
141160

@@ -144,11 +163,9 @@ describe('md-calendar', function() {
144163
pageScope.myDate = controller.today;
145164
applyDateChange();
146165

147-
var monthElement = element.querySelector('.md-calendar-month');
148-
var day = controller.today.getDate();
149-
150-
var dateElement = findDateElement(monthElement, day);
151-
expect(dateElement.classList.contains('md-calendar-date-today')).toBe(true);
166+
var todayElement = element.querySelector('.md-calendar-date-today');
167+
expect(todayElement).not.toBeNull();
168+
expect(todayElement.textContent).toBe(controller.today.getDate() + '');
152169
});
153170

154171
it('should have ids for date elements unique to the directive instance', function() {

0 commit comments

Comments
 (0)