Skip to content

Commit

Permalink
feat(grid-list): add grid-list component
Browse files Browse the repository at this point in the history
  • Loading branch information
kara committed May 10, 2016
1 parent c566242 commit 0f89b8d
Show file tree
Hide file tree
Showing 11 changed files with 761 additions and 7 deletions.
26 changes: 26 additions & 0 deletions src/components/grid-list/grid-list-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Exception thrown when cols property is missing from grid-list
*/
export class MdGridListColsError extends Error {
constructor() {
super(`md-grid-list: must pass in number of columns. Example: <md-grid-list cols="3">`);
}
}

/**
* Exception thrown when a tile's colspan is longer than the number of cols in list
*/
export class MdGridTileTooWideError extends Error {
constructor(cols: number, listLength: number) {
super(`Tile with colspan ${cols} is wider than grid with cols="${listLength}".`);
}
}

/**
* Exception thrown when an invalid ratio is passed in as a rowHeight
*/
export class MdGridListBadRatioError extends Error {
constructor(value: string) {
super(`md-grid-list: invalid ratio given for row-height: "${value}"`);
}
}
4 changes: 3 additions & 1 deletion src/components/grid-list/grid-list.html
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
I'm a grid list!
<div class="md-grid-list">
<ng-content></ng-content>
</div>
67 changes: 67 additions & 0 deletions src/components/grid-list/grid-list.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// TODO(kara): Review this to see if MD spec updates are needed

md-grid-list {
display: block;
position: relative;
}

md-grid-tile {
display: block;
position: absolute;

figure {
display: flex;
position: absolute;

align-items: center;
justify-content: center;
height: 100%;

top: 0;
right: 0;
bottom: 0;
left: 0;

padding: 0;
margin: 0;
}

// Headers & footers
md-grid-tile-header,
md-grid-tile-footer {
display: flex;
flex-direction: row;
align-items: center;
height: 48px;
color: #fff;
background: rgba(0, 0, 0, 0.18);
overflow: hidden;

// Positioning
position: absolute;
left: 0;
right: 0;

h3,
h4 {
font-weight: 400;
margin: 0 0 0 16px;
}

h3 {
font-size: 14px;
}

h4 {
font-size: 12px;
}
}

md-grid-tile-header {
top: 0;
}

md-grid-tile-footer {
bottom: 0;
}
}
154 changes: 152 additions & 2 deletions src/components/grid-list/grid-list.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,158 @@
import {Component} from '@angular/core';
import {
Component,
ViewEncapsulation,
AfterContentChecked,
OnInit,
Input,
ContentChildren,
QueryList,
Renderer,
ElementRef
} from '@angular/core';
import {MdGridTile} from './grid-tile';
import {TileCoordinator} from './tile-coordinator';
import {
TileStyler,
FitTileStyler,
RatioTileStyler,
FixedTileStyler
} from './tile-styler';
import {MdGridListColsError} from './grid-list-errors';
import {Dir} from '../../core/rtl/dir';

// TODO(kara): Conditional (responsive) column count / row size.
// TODO(kara): Re-layout on window resize / media change (debounced).
// TODO(kara): gridTileHeader and gridTileFooter.

const MD_FIT_MODE = 'fit';

@Component({
selector: 'md-grid-list',
host: { 'role': 'list' },
templateUrl: './components/grid-list/grid-list.html',
styleUrls: ['./components/grid-list/grid-list.css'],
encapsulation: ViewEncapsulation.None,
})
export class MdGridList {}
export class MdGridList implements OnInit, AfterContentChecked {
/** Number of columns being rendered. */
_cols: number;

/** Row height value passed in by user. This can be one of three types:
* - Number value (ex: "100px"): sets a fixed row height to that value
* - Ratio value (ex: "4:3"): sets the row height based on width:height ratio
* - "Fit" mode (ex: "fit"): sets the row height to total height divided by number of rows
* */
_rowHeight: string;

/** The amount of space between tiles. This will be something like '5px' or '2em'. */
_gutter: string = '1px';

/** Sets position and size styles for a tile */
_tileStyler: TileStyler;

/** Query list of tiles that are being rendered. */
@ContentChildren(MdGridTile) _tiles: QueryList<MdGridTile>;

constructor(private _renderer: Renderer, private _element: ElementRef,
private _dir: Dir) {}

@Input()
get cols() {
return this._cols;
}

set cols(value: any) {
this._cols = coerceToNumber(value);
}

@Input('gutterSize')
get gutterSize() {
return this._gutter;
}

set gutterSize(value: any) {
this._gutter = coerceToString(value);
}

/** Set internal representation of row height from the user-provided value. */
@Input()
set rowHeight(value: string | number) {
this._rowHeight = coerceToString(value);
this._setTileStyler();
}

ngOnInit() {
this._checkCols();
this._checkRowHeight();
}

/** The layout calculation is fairly cheap if nothing changes, so there's little cost
* to run it frequently. */
ngAfterContentChecked() {
this._layoutTiles();
}

/** Throw a friendly error if cols property is missing */
private _checkCols() {
if (!this.cols) {
throw new MdGridListColsError();
}
}

/** Default to equal width:height if rowHeight property is missing */
private _checkRowHeight(): void {
if (!this._rowHeight) {
this._tileStyler = new RatioTileStyler('1:1');
}
}

/** Creates correct Tile Styler subtype based on rowHeight passed in by user */
private _setTileStyler(): void {
if (this._rowHeight === MD_FIT_MODE) {
this._tileStyler = new FitTileStyler();
} else if (this._rowHeight && this._rowHeight.match(/:/g)) {
this._tileStyler = new RatioTileStyler(this._rowHeight);
} else {
this._tileStyler = new FixedTileStyler(this._rowHeight);
}
}

/** Computes and applies the size and position for all children grid tiles. */
private _layoutTiles(): void {
let tiles = this._tiles.toArray();
let tracker = new TileCoordinator(this.cols, tiles);
this._tileStyler.init(this.gutterSize, tracker, this.cols, this._dir);

for (let i = 0; i < tiles.length; i++) {
let pos = tracker.positions[i];
let tile = tiles[i];
this._tileStyler.setStyle(tile, pos.row, pos.col);
}
this.setListStyle(this._tileStyler.getComputedHeight());
}

/** Sets style on the main grid-list element, given the style name and value.
* @internal
*/
setListStyle(style: [string, string]): void {
if (style) {
this._renderer.setElementStyle(this._element.nativeElement, style[0], style[1]);
}
}
}

/** Converts values into strings. Falsy values become empty strings.
* @internal
*/
export function coerceToString(value: string | number): string {
return `${value || ''}`;
}

/** Converts a value that might be a string into a number.
* @internal
*/
export function coerceToNumber(value: string | number): number {
return typeof value === 'string' ? parseInt(value, 10) : value;
}

export const MD_GRID_LIST_DIRECTIVES: any[] = [MdGridList, MdGridTile];
4 changes: 4 additions & 0 deletions src/components/grid-list/grid-tile.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<!-- TODO(kara): Revisit why this is a figure.-->
<figure>
<ng-content></ng-content>
</figure>
53 changes: 53 additions & 0 deletions src/components/grid-list/grid-tile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
Component,
ViewEncapsulation,
Renderer,
ElementRef,
Input,
} from '@angular/core';

import {coerceToNumber} from './grid-list';

@Component({
selector: 'md-grid-tile',
host: { 'role': 'listitem' },
templateUrl: './components/grid-list/grid-tile.html',
styleUrls: ['./components/grid-list/grid-list.css'],
encapsulation: ViewEncapsulation.None,
})
export class MdGridTile {
_rowspan: number = 1;
_colspan: number = 1;
_element: HTMLElement;

constructor(private _renderer: Renderer, element: ElementRef) {
this._element = element.nativeElement;
}

@Input()
get rowspan() {
return this._rowspan;
}

@Input()
get colspan() {
return this._colspan;
}

set rowspan(value) {
this._rowspan = coerceToNumber(value);
}

set colspan(value) {
this._colspan = coerceToNumber(value);
}

/** Sets the style of the grid-tile element. Needs to be set manually to avoid
* "Changed after checked" errors that would occur with HostBinding.
* @internal
*/
setStyle(property: string, value: string): void {
this._renderer.setElementStyle(this._element, property, value);
}

}
Loading

0 comments on commit 0f89b8d

Please sign in to comment.