parent
2cb066215a
commit
8ef183b593
|
@ -1,5 +1,9 @@
|
|||
<style>
|
||||
md-grid-tile {
|
||||
background: lightblue;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="md-grid-list">
|
||||
<div class="md-grid-tile">
|
||||
<figure></figure>
|
||||
</div>
|
||||
<content></content>
|
||||
</div>
|
||||
|
|
|
@ -2,17 +2,24 @@ import {Component, onDestroy, onChange, onAllChangesDone} from 'angular2/src/cor
|
|||
import {View} from 'angular2/src/core/annotations_impl/view';
|
||||
import {Parent} from 'angular2/src/core/annotations_impl/visibility';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {isPresent, isString, NumberWrapper, stringify} from 'angular2/src/facade/lang';
|
||||
import {StringWrapper, isPresent, isString, NumberWrapper, RegExpWrapper} from 'angular2/src/facade/lang';
|
||||
import {Math} from 'angular2/src/facade/math';
|
||||
|
||||
// TODO(jelbourn): Set appropriate aria attributes for grid list elements.
|
||||
// TODO(jelbourn): Animations.
|
||||
// TODO(jelbourn): Conditional (responsive) column count / row size.
|
||||
// TODO(jelbourn): Re-layout on window resize / media change (debounced).
|
||||
// TODO(jelbourn): gridTileHeader and gridTileFooter.
|
||||
// TODO(jelbourn): rowHeightMode enum (after TS conversion).
|
||||
|
||||
@Component({
|
||||
selector: 'md-grid-list',
|
||||
properties: {
|
||||
'cols': 'cols',
|
||||
'rowHeight': 'row-height',
|
||||
'gutterSize': 'gutter-size'
|
||||
},
|
||||
lifecycle: [onChange]
|
||||
lifecycle: [onAllChangesDone]
|
||||
})
|
||||
@View({
|
||||
templateUrl: 'angular2_material/src/components/grid_list/grid_list.html'
|
||||
|
@ -21,14 +28,17 @@ export class MdGridList {
|
|||
/** List of tiles that are being rendered. */
|
||||
tiles: List<MdGridTile>;
|
||||
|
||||
/** Number of columns being rendered. Can be either string or number */
|
||||
cols;
|
||||
/** Number of columns being rendered. */
|
||||
_cols: number;
|
||||
|
||||
/** Number of rows being rendered (computed). */
|
||||
rows: number;
|
||||
|
||||
/** Mode used to determine row heights. See RowHeightMode. */
|
||||
rowHeightMode: string;
|
||||
|
||||
/** Fixed row height, as given by the user. Only used for 'fixed' mode. */
|
||||
fixedRowHeight: number;
|
||||
fixedRowHeight: string;
|
||||
|
||||
/** Ratio width:height given by user to determine row height. Only used for 'ratio' mode.*/
|
||||
rowHeightRatio: number;
|
||||
|
@ -36,23 +46,58 @@ export class MdGridList {
|
|||
/** The amount of space between tiles. This will be something like '5px' or '2em'. */
|
||||
gutterSize: string;
|
||||
|
||||
/** List used to track the amount of space available. */
|
||||
spaceTracker: List<number>;
|
||||
|
||||
constructor() {
|
||||
this.tiles = [];
|
||||
this.rows = 0;
|
||||
}
|
||||
|
||||
set cols(value) {
|
||||
this._cols = isString(value) ? NumberWrapper.parseInt(value, 10) : value;
|
||||
}
|
||||
|
||||
get cols() {
|
||||
return this._cols;
|
||||
}
|
||||
|
||||
/** Set internal representation of row height from the user-provided value. */
|
||||
set rowHeight(value) {
|
||||
if (value === 'fit') {
|
||||
this.rowHeightMode = 'fit';
|
||||
} else if (StringWrapper.contains(value, ':')) {
|
||||
var ratioParts = StringWrapper.split(value, RegExpWrapper.create(':'));
|
||||
if (ratioParts.length !== 2) {
|
||||
throw `md-grid-list: invalid ratio given for row-height: "${value}"`;
|
||||
}
|
||||
|
||||
this.rowHeightMode = 'ratio';
|
||||
this.rowHeightRatio =
|
||||
NumberWrapper.parseFloat(ratioParts[0]) / NumberWrapper.parseFloat(ratioParts[1]);
|
||||
} else {
|
||||
this.rowHeightMode = 'fixed';
|
||||
this.fixedRowHeight = value;
|
||||
}
|
||||
}
|
||||
|
||||
onAllChangesDone() {
|
||||
this.layoutTiles();
|
||||
}
|
||||
|
||||
onChange(_) {
|
||||
if (!isPresent(this.spaceTracker)) {
|
||||
if (isString(this.cols)) {
|
||||
this.cols = NumberWrapper.parseIntAutoRadix(this.cols);
|
||||
}
|
||||
this.spaceTracker = ListWrapper.createFixedSize(this.cols);
|
||||
ListWrapper.fill(this.spaceTracker, 0);
|
||||
/** Computes and applies the size and position for all children grid tiles. */
|
||||
layoutTiles() {
|
||||
var tracker = new TileCoordinator(this.cols, this.tiles);
|
||||
this.rows = tracker.rowCount;
|
||||
|
||||
for (var i = 0; i < this.tiles.length; i++) {
|
||||
var pos = tracker.positions[i];
|
||||
var tile = this.tiles[i];
|
||||
var style = this.getTileStyle(tile, pos.row, pos.col);
|
||||
|
||||
tile.styleWidth = style.width;
|
||||
tile.styleHeight = style.height;
|
||||
tile.styleTop = style.top;
|
||||
tile.styleLeft = style.left;
|
||||
tile.styleMarginTop = style.marginTop;
|
||||
tile.stylePaddingTop = style.paddingTop;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,15 +117,6 @@ export class MdGridList {
|
|||
ListWrapper.remove(this.tiles, tile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change handler invoked when bindings are resolved or when bindings have changed.
|
||||
* Performs a layout.
|
||||
*/
|
||||
performLayout() {
|
||||
//console.log('laying out!');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes the amount of space a single 1x1 tile would take up (width or height).
|
||||
* Used as a basis for other calculations.
|
||||
|
@ -94,7 +130,7 @@ export class MdGridList {
|
|||
// edges, each tile only uses a fration (gutterShare = numGutters / numCells) of the gutter
|
||||
// size. (Imagine having one gutter per tile, and then breaking up the extra gutter on the
|
||||
// edge evenly among the cells).
|
||||
return `${sizePercent}% - ( ${this.gutterSize} * ${gutterFraction} )`;
|
||||
return `(${sizePercent}% - ( ${this.gutterSize} * ${gutterFraction} ))`;
|
||||
}
|
||||
|
||||
|
||||
|
@ -122,43 +158,57 @@ export class MdGridList {
|
|||
}
|
||||
|
||||
|
||||
/** Gets the style properties to be applied to a tile for the given row and column index. */
|
||||
getTileStyle(tile: MdGridTile, rowIndex: number, colIndex: number): TileStyle {
|
||||
// Percent of the available horizontal space that one column takes up.
|
||||
var percentWidthPerTile = this.cols / 100;
|
||||
var percentWidthPerTile = 100 / this.cols;
|
||||
|
||||
// Fraction of the gutter size that each column takes up.
|
||||
// Fraction of the vertical gutter size that each column takes up.
|
||||
// For example, if there are 5 columns, each column uses 4/5 = 0.8 times the gutter width.
|
||||
var gutterWidthFractionPerTile = (this.cols - 1) / this.cols;
|
||||
|
||||
// Base horizontal size of a column.
|
||||
var baseTileWidth = getBaseTileSize(percentWidthPerTile, gutterWidthFractionPerTile);
|
||||
var baseTileWidth = this.getBaseTileSize(percentWidthPerTile, gutterWidthFractionPerTile);
|
||||
|
||||
// The width and horizontal position of each tile is always calculated the same way, but the
|
||||
// height and vertical position depends on the rowMode.
|
||||
var tileStyle = new TileStyle();
|
||||
tileStyle.left = getTilePosition(baseTileWidth, colIndex);
|
||||
tileStyle.width = getTileSize(baseTileWidth, tile.colspan);
|
||||
tileStyle.left = this.getTilePosition(baseTileWidth, colIndex);
|
||||
tileStyle.width = this.getTileSize(baseTileWidth, tile.colspan);
|
||||
|
||||
// TODO: make cases enums when we support enums
|
||||
switch (this.rowHeightMode) {
|
||||
case 'fixed':
|
||||
// In fixed mode, simply use the given row height.
|
||||
tileStyle.top = getTilePosition(stringify(this.fixedRowHeight), rowIndex);
|
||||
tileStyle.height = getTileSize(stringify(this.fixedRowHeight), tile.rowspan);
|
||||
tileStyle.top = this.getTilePosition(this.fixedRowHeight, rowIndex);
|
||||
tileStyle.height = this.getTileSize(this.fixedRowHeight, tile.rowspan);
|
||||
break;
|
||||
|
||||
case 'ratio':
|
||||
var percentHeightPerTile = percentWidthPerTile / this.rowHeightRatio;
|
||||
let baseTileHeight = getBaseTileSize(percentHeightPerTile, gutterWidthFractionPerTile);
|
||||
var baseTileHeight = this.getBaseTileSize(percentHeightPerTile, gutterWidthFractionPerTile);
|
||||
|
||||
// Use paddingTop and marginTop to maintain the given aspect ratio, as
|
||||
// a percentage-based value for these properties is applied to the *width* of the
|
||||
// a percentage-based value for these properties is applied versus the *width* of the
|
||||
// containing block. See http://www.w3.org/TR/CSS2/box.html#margin-properties
|
||||
tileStyle.marginTop = getTilePosition(baseTileHeight, rowIndex);
|
||||
tileStyle.paddingTop = getTileSize(baseTileHeight, tile.rowspan);
|
||||
tileStyle.marginTop = this.getTilePosition(baseTileHeight, rowIndex);
|
||||
tileStyle.paddingTop = this.getTileSize(baseTileHeight, tile.rowspan);
|
||||
break;
|
||||
|
||||
case 'fit':
|
||||
// Percent of the available vertical space that one row takes up.
|
||||
var percentHeightPerTile = 100 / this.cols;
|
||||
|
||||
// Fraction of the horizontal gutter size that each column takes up.
|
||||
var gutterHeightFractionPerTile = (this.rows - 1) / this.rows;
|
||||
|
||||
// Base vertical size of a column.
|
||||
var baseTileHeight =
|
||||
this.getBaseTileSize(percentHeightPerTile, gutterHeightFractionPerTile);
|
||||
|
||||
tileStyle.top = this.getTilePosition(baseTileHeight, rowIndex);
|
||||
tileStyle.height = this.getTileSize(baseTileHeight, tile.rowspan);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -184,20 +234,20 @@ export class MdGridList {
|
|||
lifecycle: [onDestroy, onChange]
|
||||
})
|
||||
@View({
|
||||
template: `<figure><content></content></figure>`
|
||||
templateUrl: 'angular2_material/src/components/grid_list/grid_tile.html'
|
||||
})
|
||||
export class MdGridTile {
|
||||
gridList: MdGridList;
|
||||
rowspan: number;
|
||||
colspan: number;
|
||||
_rowspan: number;
|
||||
_colspan: number;
|
||||
|
||||
styleHeight:any;
|
||||
styleWidth:any;
|
||||
styleTop:any;
|
||||
styleLeft:any;
|
||||
styleMarginTop:any;
|
||||
stylePaddingTop:any;
|
||||
role:any;
|
||||
styleHeight: string;
|
||||
styleWidth: string;
|
||||
styleTop: string;
|
||||
styleLeft: string;
|
||||
styleMarginTop: string;
|
||||
stylePaddingTop: string;
|
||||
role: string;
|
||||
|
||||
isRegisteredWithGridList: boolean;
|
||||
|
||||
|
@ -209,9 +259,22 @@ export class MdGridTile {
|
|||
// Tiles default to 1x1, but rowspan and colspan can be changed via binding.
|
||||
this.rowspan = 1;
|
||||
this.colspan = 1;
|
||||
}
|
||||
|
||||
// DEBUG
|
||||
this.styleHeight = `${gridList.tiles.length * 100}px`;
|
||||
set rowspan(value) {
|
||||
this._rowspan = isString(value) ? NumberWrapper.parseInt(value, 10) : value;
|
||||
}
|
||||
|
||||
get rowspan() {
|
||||
return this._rowspan;
|
||||
}
|
||||
|
||||
set colspan(value) {
|
||||
this._colspan = isString(value) ? NumberWrapper.parseInt(value, 10) : value;
|
||||
}
|
||||
|
||||
get colspan() {
|
||||
return this._colspan;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -223,8 +286,6 @@ export class MdGridTile {
|
|||
if (!this.isRegisteredWithGridList) {
|
||||
this.gridList.addTile(this);
|
||||
this.isRegisteredWithGridList = true;
|
||||
} else {
|
||||
this.gridList.performLayout();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,6 +297,141 @@ export class MdGridTile {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Class for determining, from a list of tiles, the (row, col) position of each of those tiles
|
||||
* in the grid. This is necessary (rather than just rendering the tiles in normal document flow)
|
||||
* because the tiles can have a rowspan.
|
||||
*
|
||||
* The positioning algorithm greedily places each tile as soon as it encounters a gap in the grid
|
||||
* large enough to accomodate it so that the tiles still render in the same order in which they
|
||||
* are given.
|
||||
*
|
||||
* The basis of the algorithm is the use of an array to track the already placed tiles. Each
|
||||
* element of the array corresponds to a column, and the value indicates how many cells in that
|
||||
* column are already occupied; zero indicates an empty cell. Moving "down" to the next row
|
||||
* decrements each value in the tracking array (indicating that the column is one cell closer to
|
||||
* being free).
|
||||
*/
|
||||
class TileCoordinator {
|
||||
// Tracking array (see class description).
|
||||
tracker: List<int>;
|
||||
|
||||
// Index at which the search for the next gap will start.
|
||||
columnIndex: int;
|
||||
|
||||
// The current row index.
|
||||
rowIndex: int;
|
||||
|
||||
// The computed (row, col) position of each tile (the output).
|
||||
positions: List<Position>;
|
||||
|
||||
constructor(numColumns: number, tiles: List<MdGridTile>) {
|
||||
this.columnIndex = 0;
|
||||
this.rowIndex = 0;
|
||||
|
||||
this.tracker = ListWrapper.createFixedSize(numColumns);
|
||||
ListWrapper.fill(this.tracker, 0);
|
||||
|
||||
this.positions = ListWrapper.map(tiles, tile => this._trackTile(tile));
|
||||
}
|
||||
|
||||
/** Gets the number of rows occupied by tiles. */
|
||||
get rowCount() {
|
||||
return this.rowIndex + 1;
|
||||
}
|
||||
|
||||
_trackTile(tile: MdGridTile): Position {
|
||||
if (tile.colspan > this.tracker.length) {
|
||||
throw `Tile with colspan ${tile.colspan} is wider
|
||||
than grid with cols="${this.tracker.length}".`
|
||||
}
|
||||
|
||||
// Start index is inclusive, end index is exclusive.
|
||||
var gapStartIndex = -1;
|
||||
var gapEndIndex = -1;
|
||||
|
||||
// Look for a gap large enough to fit the given tile. Empty spaces are marked with a zero.
|
||||
do {
|
||||
// If we've reached the end of the row, go to the next row
|
||||
if (this.columnIndex + tile.colspan > this.tracker.length) {
|
||||
this._nextRow();
|
||||
continue;
|
||||
}
|
||||
|
||||
gapStartIndex = ListWrapper.indexOf(this.tracker, 0, this.columnIndex);
|
||||
|
||||
// If there are no more empty spaces in this row at all, move on to the next row.
|
||||
if (gapStartIndex == -1) {
|
||||
this._nextRow();
|
||||
continue;
|
||||
}
|
||||
|
||||
gapEndIndex = this._findGapEndIndex(gapStartIndex);
|
||||
|
||||
// If a gap large enough isn't found, we want to start looking immediately after the current
|
||||
// gap on the next iteration.
|
||||
this.columnIndex = gapStartIndex + 1;
|
||||
|
||||
// Continue iterating until we find a gap wide enough for this tile.
|
||||
} while (gapEndIndex - gapStartIndex < tile.colspan);
|
||||
|
||||
// We now have a space big enough for this tile, so place it.
|
||||
this._markTilePosition(gapStartIndex, tile);
|
||||
|
||||
// The next time we look for a gap, the search will start at columnIndex, which should be
|
||||
// immediately after the tile that has just been placed.
|
||||
this.columnIndex = gapStartIndex + tile.colspan;
|
||||
|
||||
return new Position(this.rowIndex, gapStartIndex);
|
||||
}
|
||||
|
||||
/** Move "down" to the next row. */
|
||||
_nextRow() {
|
||||
this.columnIndex = 0;
|
||||
this.rowIndex++;
|
||||
|
||||
// Decrement all spaces by one to reflect moving down one row.
|
||||
for (var i = 0; i < this.tracker.length; i++) {
|
||||
this.tracker[i] = Math.max(0, this.tracker[i] - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the end index (exclusive) of a gap given the index from which to start looking.
|
||||
* The gap ends when a non-zero value is found.
|
||||
*/
|
||||
_findGapEndIndex(gapStartIndex: number): number {
|
||||
for (var i = gapStartIndex + 1; i < this.tracker.length; i++) {
|
||||
if (this.tracker[i] != 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// The gap ends with the end of the row.
|
||||
return this.tracker.length;
|
||||
}
|
||||
|
||||
/** Update the tile tracker to account for the given tile in the given space. */
|
||||
_markTilePosition(start, tile) {
|
||||
for (var i = 0; i < tile.colspan; i++) {
|
||||
this.tracker[start + i] = tile.rowspan;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Simple data structure for tile position (row, col). */
|
||||
class Position {
|
||||
row: number;
|
||||
col: number;
|
||||
|
||||
constructor(row: number, col: number) {
|
||||
this.row = row;
|
||||
this.col = col;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Simple data structure for style values to be applied to a tile. */
|
||||
class TileStyle {
|
||||
height: string;
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<style>@import "angular2_material/src/components/grid_list/grid-list.css";</style>
|
||||
|
||||
<figure>
|
||||
<content></content>
|
||||
</figure>
|
|
@ -1,28 +1,40 @@
|
|||
<style>@import "angular2_material/src/components/grid_list/grid-list.css";</style>
|
||||
|
||||
<style>
|
||||
md-grid-tile {
|
||||
background-color: lightblue;
|
||||
}
|
||||
|
||||
md-grid-list {
|
||||
min-height: 400px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
|
||||
<h2>grid-list demo</h2>
|
||||
|
||||
<md-grid-list cols="5" gutter-size="2em">
|
||||
<md-grid-list cols="4" row-height="50px" gutter-size="2em">
|
||||
|
||||
<md-grid-tile cols="5">
|
||||
Tile #1
|
||||
</md-grid-tile>
|
||||
<md-grid-tile rowspan="1" colspan="2"> Tile #1 </md-grid-tile>
|
||||
<md-grid-tile rowspan="1" colspan="1"> Tile #2 </md-grid-tile>
|
||||
<md-grid-tile rowspan="3" colspan="1"> Tile #3 </md-grid-tile>
|
||||
<md-grid-tile rowspan="2" colspan="2"> Tile #4 </md-grid-tile>
|
||||
<md-grid-tile rowspan="1" colspan="3"> Tile #5 </md-grid-tile>
|
||||
|
||||
<md-grid-tile rowspan="2" colspan="2">
|
||||
Tile #2
|
||||
</md-grid-tile>
|
||||
</md-grid-list>
|
||||
|
||||
<md-grid-tile [rowspan]="tile3RowSpan" [colspan]="tile3RowSpan">
|
||||
Tile #3
|
||||
</md-grid-tile>
|
||||
<hr>
|
||||
|
||||
|
||||
|
||||
<md-grid-list cols="4" row-height="50px" gutter-size="2em">
|
||||
|
||||
<md-grid-tile rowspan="1" colspan="1"> Tile #1 </md-grid-tile>
|
||||
<md-grid-tile rowspan="1" colspan="1"> Tile #2 </md-grid-tile>
|
||||
<md-grid-tile rowspan="1" colspan="1"> Tile #3 </md-grid-tile>
|
||||
<md-grid-tile rowspan="1" colspan="1"> Tile #4 </md-grid-tile>
|
||||
<md-grid-tile rowspan="1" colspan="1"> Tile #5 </md-grid-tile>
|
||||
<md-grid-tile rowspan="1" colspan="1"> Tile #6 </md-grid-tile>
|
||||
<md-grid-tile rowspan="1" colspan="1"> Tile #7 </md-grid-tile>
|
||||
|
||||
</md-grid-list>
|
||||
|
||||
|
|
Loading…
Reference in New Issue