Skip to content

Commit 0f89b8d

Browse files
committed
feat(grid-list): add grid-list component
1 parent c566242 commit 0f89b8d

11 files changed

+761
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Exception thrown when cols property is missing from grid-list
3+
*/
4+
export class MdGridListColsError extends Error {
5+
constructor() {
6+
super(`md-grid-list: must pass in number of columns. Example: <md-grid-list cols="3">`);
7+
}
8+
}
9+
10+
/**
11+
* Exception thrown when a tile's colspan is longer than the number of cols in list
12+
*/
13+
export class MdGridTileTooWideError extends Error {
14+
constructor(cols: number, listLength: number) {
15+
super(`Tile with colspan ${cols} is wider than grid with cols="${listLength}".`);
16+
}
17+
}
18+
19+
/**
20+
* Exception thrown when an invalid ratio is passed in as a rowHeight
21+
*/
22+
export class MdGridListBadRatioError extends Error {
23+
constructor(value: string) {
24+
super(`md-grid-list: invalid ratio given for row-height: "${value}"`);
25+
}
26+
}
+3-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
I'm a grid list!
1+
<div class="md-grid-list">
2+
<ng-content></ng-content>
3+
</div>
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// TODO(kara): Review this to see if MD spec updates are needed
2+
3+
md-grid-list {
4+
display: block;
5+
position: relative;
6+
}
7+
8+
md-grid-tile {
9+
display: block;
10+
position: absolute;
11+
12+
figure {
13+
display: flex;
14+
position: absolute;
15+
16+
align-items: center;
17+
justify-content: center;
18+
height: 100%;
19+
20+
top: 0;
21+
right: 0;
22+
bottom: 0;
23+
left: 0;
24+
25+
padding: 0;
26+
margin: 0;
27+
}
28+
29+
// Headers & footers
30+
md-grid-tile-header,
31+
md-grid-tile-footer {
32+
display: flex;
33+
flex-direction: row;
34+
align-items: center;
35+
height: 48px;
36+
color: #fff;
37+
background: rgba(0, 0, 0, 0.18);
38+
overflow: hidden;
39+
40+
// Positioning
41+
position: absolute;
42+
left: 0;
43+
right: 0;
44+
45+
h3,
46+
h4 {
47+
font-weight: 400;
48+
margin: 0 0 0 16px;
49+
}
50+
51+
h3 {
52+
font-size: 14px;
53+
}
54+
55+
h4 {
56+
font-size: 12px;
57+
}
58+
}
59+
60+
md-grid-tile-header {
61+
top: 0;
62+
}
63+
64+
md-grid-tile-footer {
65+
bottom: 0;
66+
}
67+
}

src/components/grid-list/grid-list.ts

+152-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,158 @@
1-
import {Component} from '@angular/core';
1+
import {
2+
Component,
3+
ViewEncapsulation,
4+
AfterContentChecked,
5+
OnInit,
6+
Input,
7+
ContentChildren,
8+
QueryList,
9+
Renderer,
10+
ElementRef
11+
} from '@angular/core';
12+
import {MdGridTile} from './grid-tile';
13+
import {TileCoordinator} from './tile-coordinator';
14+
import {
15+
TileStyler,
16+
FitTileStyler,
17+
RatioTileStyler,
18+
FixedTileStyler
19+
} from './tile-styler';
20+
import {MdGridListColsError} from './grid-list-errors';
21+
import {Dir} from '../../core/rtl/dir';
22+
23+
// TODO(kara): Conditional (responsive) column count / row size.
24+
// TODO(kara): Re-layout on window resize / media change (debounced).
25+
// TODO(kara): gridTileHeader and gridTileFooter.
26+
27+
const MD_FIT_MODE = 'fit';
228

329
@Component({
430
selector: 'md-grid-list',
31+
host: { 'role': 'list' },
532
templateUrl: './components/grid-list/grid-list.html',
633
styleUrls: ['./components/grid-list/grid-list.css'],
34+
encapsulation: ViewEncapsulation.None,
735
})
8-
export class MdGridList {}
36+
export class MdGridList implements OnInit, AfterContentChecked {
37+
/** Number of columns being rendered. */
38+
_cols: number;
39+
40+
/** Row height value passed in by user. This can be one of three types:
41+
* - Number value (ex: "100px"): sets a fixed row height to that value
42+
* - Ratio value (ex: "4:3"): sets the row height based on width:height ratio
43+
* - "Fit" mode (ex: "fit"): sets the row height to total height divided by number of rows
44+
* */
45+
_rowHeight: string;
46+
47+
/** The amount of space between tiles. This will be something like '5px' or '2em'. */
48+
_gutter: string = '1px';
49+
50+
/** Sets position and size styles for a tile */
51+
_tileStyler: TileStyler;
52+
53+
/** Query list of tiles that are being rendered. */
54+
@ContentChildren(MdGridTile) _tiles: QueryList<MdGridTile>;
55+
56+
constructor(private _renderer: Renderer, private _element: ElementRef,
57+
private _dir: Dir) {}
58+
59+
@Input()
60+
get cols() {
61+
return this._cols;
62+
}
63+
64+
set cols(value: any) {
65+
this._cols = coerceToNumber(value);
66+
}
67+
68+
@Input('gutterSize')
69+
get gutterSize() {
70+
return this._gutter;
71+
}
72+
73+
set gutterSize(value: any) {
74+
this._gutter = coerceToString(value);
75+
}
76+
77+
/** Set internal representation of row height from the user-provided value. */
78+
@Input()
79+
set rowHeight(value: string | number) {
80+
this._rowHeight = coerceToString(value);
81+
this._setTileStyler();
82+
}
83+
84+
ngOnInit() {
85+
this._checkCols();
86+
this._checkRowHeight();
87+
}
88+
89+
/** The layout calculation is fairly cheap if nothing changes, so there's little cost
90+
* to run it frequently. */
91+
ngAfterContentChecked() {
92+
this._layoutTiles();
93+
}
94+
95+
/** Throw a friendly error if cols property is missing */
96+
private _checkCols() {
97+
if (!this.cols) {
98+
throw new MdGridListColsError();
99+
}
100+
}
101+
102+
/** Default to equal width:height if rowHeight property is missing */
103+
private _checkRowHeight(): void {
104+
if (!this._rowHeight) {
105+
this._tileStyler = new RatioTileStyler('1:1');
106+
}
107+
}
108+
109+
/** Creates correct Tile Styler subtype based on rowHeight passed in by user */
110+
private _setTileStyler(): void {
111+
if (this._rowHeight === MD_FIT_MODE) {
112+
this._tileStyler = new FitTileStyler();
113+
} else if (this._rowHeight && this._rowHeight.match(/:/g)) {
114+
this._tileStyler = new RatioTileStyler(this._rowHeight);
115+
} else {
116+
this._tileStyler = new FixedTileStyler(this._rowHeight);
117+
}
118+
}
119+
120+
/** Computes and applies the size and position for all children grid tiles. */
121+
private _layoutTiles(): void {
122+
let tiles = this._tiles.toArray();
123+
let tracker = new TileCoordinator(this.cols, tiles);
124+
this._tileStyler.init(this.gutterSize, tracker, this.cols, this._dir);
125+
126+
for (let i = 0; i < tiles.length; i++) {
127+
let pos = tracker.positions[i];
128+
let tile = tiles[i];
129+
this._tileStyler.setStyle(tile, pos.row, pos.col);
130+
}
131+
this.setListStyle(this._tileStyler.getComputedHeight());
132+
}
133+
134+
/** Sets style on the main grid-list element, given the style name and value.
135+
* @internal
136+
*/
137+
setListStyle(style: [string, string]): void {
138+
if (style) {
139+
this._renderer.setElementStyle(this._element.nativeElement, style[0], style[1]);
140+
}
141+
}
142+
}
143+
144+
/** Converts values into strings. Falsy values become empty strings.
145+
* @internal
146+
*/
147+
export function coerceToString(value: string | number): string {
148+
return `${value || ''}`;
149+
}
150+
151+
/** Converts a value that might be a string into a number.
152+
* @internal
153+
*/
154+
export function coerceToNumber(value: string | number): number {
155+
return typeof value === 'string' ? parseInt(value, 10) : value;
156+
}
157+
158+
export const MD_GRID_LIST_DIRECTIVES: any[] = [MdGridList, MdGridTile];
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<!-- TODO(kara): Revisit why this is a figure.-->
2+
<figure>
3+
<ng-content></ng-content>
4+
</figure>

src/components/grid-list/grid-tile.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {
2+
Component,
3+
ViewEncapsulation,
4+
Renderer,
5+
ElementRef,
6+
Input,
7+
} from '@angular/core';
8+
9+
import {coerceToNumber} from './grid-list';
10+
11+
@Component({
12+
selector: 'md-grid-tile',
13+
host: { 'role': 'listitem' },
14+
templateUrl: './components/grid-list/grid-tile.html',
15+
styleUrls: ['./components/grid-list/grid-list.css'],
16+
encapsulation: ViewEncapsulation.None,
17+
})
18+
export class MdGridTile {
19+
_rowspan: number = 1;
20+
_colspan: number = 1;
21+
_element: HTMLElement;
22+
23+
constructor(private _renderer: Renderer, element: ElementRef) {
24+
this._element = element.nativeElement;
25+
}
26+
27+
@Input()
28+
get rowspan() {
29+
return this._rowspan;
30+
}
31+
32+
@Input()
33+
get colspan() {
34+
return this._colspan;
35+
}
36+
37+
set rowspan(value) {
38+
this._rowspan = coerceToNumber(value);
39+
}
40+
41+
set colspan(value) {
42+
this._colspan = coerceToNumber(value);
43+
}
44+
45+
/** Sets the style of the grid-tile element. Needs to be set manually to avoid
46+
* "Changed after checked" errors that would occur with HostBinding.
47+
* @internal
48+
*/
49+
setStyle(property: string, value: string): void {
50+
this._renderer.setElementStyle(this._element, property, value);
51+
}
52+
53+
}

0 commit comments

Comments
 (0)