/*!
|
|
* Angular Material Design
|
|
* https://github.com/angular/material
|
|
* @license MIT
|
|
* v1.1.1
|
|
*/
|
|
(function( window, angular, undefined ){
|
|
"use strict";
|
|
|
|
/**
|
|
* @ngdoc module
|
|
* @name material.components.gridList
|
|
*/
|
|
GridListController.$inject = ["$mdUtil"];
|
|
GridLayoutFactory.$inject = ["$mdUtil"];
|
|
GridListDirective.$inject = ["$interpolate", "$mdConstant", "$mdGridLayout", "$mdMedia"];
|
|
GridTileDirective.$inject = ["$mdMedia"];
|
|
angular.module('material.components.gridList', ['material.core'])
|
|
.directive('mdGridList', GridListDirective)
|
|
.directive('mdGridTile', GridTileDirective)
|
|
.directive('mdGridTileFooter', GridTileCaptionDirective)
|
|
.directive('mdGridTileHeader', GridTileCaptionDirective)
|
|
.factory('$mdGridLayout', GridLayoutFactory);
|
|
|
|
/**
|
|
* @ngdoc directive
|
|
* @name mdGridList
|
|
* @module material.components.gridList
|
|
* @restrict E
|
|
* @description
|
|
* Grid lists are an alternative to standard list views. Grid lists are distinct
|
|
* from grids used for layouts and other visual presentations.
|
|
*
|
|
* A grid list is best suited to presenting a homogenous data type, typically
|
|
* images, and is optimized for visual comprehension and differentiating between
|
|
* like data types.
|
|
*
|
|
* A grid list is a continuous element consisting of tessellated, regular
|
|
* subdivisions called cells that contain tiles (`md-grid-tile`).
|
|
*
|
|
* <img src="//material-design.storage.googleapis.com/publish/v_2/material_ext_publish/0Bx4BSt6jniD7OVlEaXZ5YmU1Xzg/components_grids_usage2.png"
|
|
* style="width: 300px; height: auto; margin-right: 16px;" alt="Concept of grid explained visually">
|
|
* <img src="//material-design.storage.googleapis.com/publish/v_2/material_ext_publish/0Bx4BSt6jniD7VGhsOE5idWlJWXM/components_grids_usage3.png"
|
|
* style="width: 300px; height: auto;" alt="Grid concepts legend">
|
|
*
|
|
* Cells are arrayed vertically and horizontally within the grid.
|
|
*
|
|
* Tiles hold content and can span one or more cells vertically or horizontally.
|
|
*
|
|
* ### Responsive Attributes
|
|
*
|
|
* The `md-grid-list` directive supports "responsive" attributes, which allow
|
|
* different `md-cols`, `md-gutter` and `md-row-height` values depending on the
|
|
* currently matching media query.
|
|
*
|
|
* In order to set a responsive attribute, first define the fallback value with
|
|
* the standard attribute name, then add additional attributes with the
|
|
* following convention: `{base-attribute-name}-{media-query-name}="{value}"`
|
|
* (ie. `md-cols-lg="8"`)
|
|
*
|
|
* @param {number} md-cols Number of columns in the grid.
|
|
* @param {string} md-row-height One of
|
|
* <ul>
|
|
* <li>CSS length - Fixed height rows (eg. `8px` or `1rem`)</li>
|
|
* <li>`{width}:{height}` - Ratio of width to height (eg.
|
|
* `md-row-height="16:9"`)</li>
|
|
* <li>`"fit"` - Height will be determined by subdividing the available
|
|
* height by the number of rows</li>
|
|
* </ul>
|
|
* @param {string=} md-gutter The amount of space between tiles in CSS units
|
|
* (default 1px)
|
|
* @param {expression=} md-on-layout Expression to evaluate after layout. Event
|
|
* object is available as `$event`, and contains performance information.
|
|
*
|
|
* @usage
|
|
* Basic:
|
|
* <hljs lang="html">
|
|
* <md-grid-list md-cols="5" md-gutter="1em" md-row-height="4:3">
|
|
* <md-grid-tile></md-grid-tile>
|
|
* </md-grid-list>
|
|
* </hljs>
|
|
*
|
|
* Fixed-height rows:
|
|
* <hljs lang="html">
|
|
* <md-grid-list md-cols="4" md-row-height="200px" ...>
|
|
* <md-grid-tile></md-grid-tile>
|
|
* </md-grid-list>
|
|
* </hljs>
|
|
*
|
|
* Fit rows:
|
|
* <hljs lang="html">
|
|
* <md-grid-list md-cols="4" md-row-height="fit" style="height: 400px;" ...>
|
|
* <md-grid-tile></md-grid-tile>
|
|
* </md-grid-list>
|
|
* </hljs>
|
|
*
|
|
* Using responsive attributes:
|
|
* <hljs lang="html">
|
|
* <md-grid-list
|
|
* md-cols-sm="2"
|
|
* md-cols-md="4"
|
|
* md-cols-lg="8"
|
|
* md-cols-gt-lg="12"
|
|
* ...>
|
|
* <md-grid-tile></md-grid-tile>
|
|
* </md-grid-list>
|
|
* </hljs>
|
|
*/
|
|
function GridListDirective($interpolate, $mdConstant, $mdGridLayout, $mdMedia) {
|
|
return {
|
|
restrict: 'E',
|
|
controller: GridListController,
|
|
scope: {
|
|
mdOnLayout: '&'
|
|
},
|
|
link: postLink
|
|
};
|
|
|
|
function postLink(scope, element, attrs, ctrl) {
|
|
element.addClass('_md'); // private md component indicator for styling
|
|
|
|
// Apply semantics
|
|
element.attr('role', 'list');
|
|
|
|
// Provide the controller with a way to trigger layouts.
|
|
ctrl.layoutDelegate = layoutDelegate;
|
|
|
|
var invalidateLayout = angular.bind(ctrl, ctrl.invalidateLayout),
|
|
unwatchAttrs = watchMedia();
|
|
scope.$on('$destroy', unwatchMedia);
|
|
|
|
/**
|
|
* Watches for changes in media, invalidating layout as necessary.
|
|
*/
|
|
function watchMedia() {
|
|
for (var mediaName in $mdConstant.MEDIA) {
|
|
$mdMedia(mediaName); // initialize
|
|
$mdMedia.getQuery($mdConstant.MEDIA[mediaName])
|
|
.addListener(invalidateLayout);
|
|
}
|
|
return $mdMedia.watchResponsiveAttributes(
|
|
['md-cols', 'md-row-height', 'md-gutter'], attrs, layoutIfMediaMatch);
|
|
}
|
|
|
|
function unwatchMedia() {
|
|
ctrl.layoutDelegate = angular.noop;
|
|
|
|
unwatchAttrs();
|
|
for (var mediaName in $mdConstant.MEDIA) {
|
|
$mdMedia.getQuery($mdConstant.MEDIA[mediaName])
|
|
.removeListener(invalidateLayout);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performs grid layout if the provided mediaName matches the currently
|
|
* active media type.
|
|
*/
|
|
function layoutIfMediaMatch(mediaName) {
|
|
if (mediaName == null) {
|
|
// TODO(shyndman): It would be nice to only layout if we have
|
|
// instances of attributes using this media type
|
|
ctrl.invalidateLayout();
|
|
} else if ($mdMedia(mediaName)) {
|
|
ctrl.invalidateLayout();
|
|
}
|
|
}
|
|
|
|
var lastLayoutProps;
|
|
|
|
/**
|
|
* Invokes the layout engine, and uses its results to lay out our
|
|
* tile elements.
|
|
*
|
|
* @param {boolean} tilesInvalidated Whether tiles have been
|
|
* added/removed/moved since the last layout. This is to avoid situations
|
|
* where tiles are replaced with properties identical to their removed
|
|
* counterparts.
|
|
*/
|
|
function layoutDelegate(tilesInvalidated) {
|
|
var tiles = getTileElements();
|
|
var props = {
|
|
tileSpans: getTileSpans(tiles),
|
|
colCount: getColumnCount(),
|
|
rowMode: getRowMode(),
|
|
rowHeight: getRowHeight(),
|
|
gutter: getGutter()
|
|
};
|
|
|
|
if (!tilesInvalidated && angular.equals(props, lastLayoutProps)) {
|
|
return;
|
|
}
|
|
|
|
var performance =
|
|
$mdGridLayout(props.colCount, props.tileSpans, tiles)
|
|
.map(function(tilePositions, rowCount) {
|
|
return {
|
|
grid: {
|
|
element: element,
|
|
style: getGridStyle(props.colCount, rowCount,
|
|
props.gutter, props.rowMode, props.rowHeight)
|
|
},
|
|
tiles: tilePositions.map(function(ps, i) {
|
|
return {
|
|
element: angular.element(tiles[i]),
|
|
style: getTileStyle(ps.position, ps.spans,
|
|
props.colCount, rowCount,
|
|
props.gutter, props.rowMode, props.rowHeight)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
.reflow()
|
|
.performance();
|
|
|
|
// Report layout
|
|
scope.mdOnLayout({
|
|
$event: {
|
|
performance: performance
|
|
}
|
|
});
|
|
|
|
lastLayoutProps = props;
|
|
}
|
|
|
|
// Use $interpolate to do some simple string interpolation as a convenience.
|
|
|
|
var startSymbol = $interpolate.startSymbol();
|
|
var endSymbol = $interpolate.endSymbol();
|
|
|
|
// Returns an expression wrapped in the interpolator's start and end symbols.
|
|
function expr(exprStr) {
|
|
return startSymbol + exprStr + endSymbol;
|
|
}
|
|
|
|
// The amount of space a single 1x1 tile would take up (either width or height), used as
|
|
// a basis for other calculations. This consists of taking the base size percent (as would be
|
|
// if evenly dividing the size between cells), and then subtracting the size of one gutter.
|
|
// However, since there are no gutters on the 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).
|
|
var UNIT = $interpolate(expr('share') + '% - (' + expr('gutter') + ' * ' + expr('gutterShare') + ')');
|
|
|
|
// The horizontal or vertical position of a tile, e.g., the 'top' or 'left' property value.
|
|
// The position comes the size of a 1x1 tile plus gutter for each previous tile in the
|
|
// row/column (offset).
|
|
var POSITION = $interpolate('calc((' + expr('unit') + ' + ' + expr('gutter') + ') * ' + expr('offset') + ')');
|
|
|
|
// The actual size of a tile, e.g., width or height, taking rowSpan or colSpan into account.
|
|
// This is computed by multiplying the base unit by the rowSpan/colSpan, and then adding back
|
|
// in the space that the gutter would normally have used (which was already accounted for in
|
|
// the base unit calculation).
|
|
var DIMENSION = $interpolate('calc((' + expr('unit') + ') * ' + expr('span') + ' + (' + expr('span') + ' - 1) * ' + expr('gutter') + ')');
|
|
|
|
/**
|
|
* Gets the styles applied to a tile element described by the given parameters.
|
|
* @param {{row: number, col: number}} position The row and column indices of the tile.
|
|
* @param {{row: number, col: number}} spans The rowSpan and colSpan of the tile.
|
|
* @param {number} colCount The number of columns.
|
|
* @param {number} rowCount The number of rows.
|
|
* @param {string} gutter The amount of space between tiles. This will be something like
|
|
* '5px' or '2em'.
|
|
* @param {string} rowMode The row height mode. Can be one of:
|
|
* 'fixed': all rows have a fixed size, given by rowHeight,
|
|
* 'ratio': row height defined as a ratio to width, or
|
|
* 'fit': fit to the grid-list element height, divinding evenly among rows.
|
|
* @param {string|number} rowHeight The height of a row. This is only used for 'fixed' mode and
|
|
* for 'ratio' mode. For 'ratio' mode, this is the *ratio* of width-to-height (e.g., 0.75).
|
|
* @returns {Object} Map of CSS properties to be applied to the style element. Will define
|
|
* values for top, left, width, height, marginTop, and paddingTop.
|
|
*/
|
|
function getTileStyle(position, spans, colCount, rowCount, gutter, rowMode, rowHeight) {
|
|
// TODO(shyndman): There are style caching opportunities here.
|
|
|
|
// Percent of the available horizontal space that one column takes up.
|
|
var hShare = (1 / colCount) * 100;
|
|
|
|
// Fraction of the gutter size that each column takes up.
|
|
var hGutterShare = (colCount - 1) / colCount;
|
|
|
|
// Base horizontal size of a column.
|
|
var hUnit = UNIT({share: hShare, gutterShare: hGutterShare, gutter: gutter});
|
|
|
|
// 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 style = {
|
|
left: POSITION({ unit: hUnit, offset: position.col, gutter: gutter }),
|
|
width: DIMENSION({ unit: hUnit, span: spans.col, gutter: gutter }),
|
|
// resets
|
|
paddingTop: '',
|
|
marginTop: '',
|
|
top: '',
|
|
height: ''
|
|
};
|
|
|
|
switch (rowMode) {
|
|
case 'fixed':
|
|
// In fixed mode, simply use the given rowHeight.
|
|
style.top = POSITION({ unit: rowHeight, offset: position.row, gutter: gutter });
|
|
style.height = DIMENSION({ unit: rowHeight, span: spans.row, gutter: gutter });
|
|
break;
|
|
|
|
case 'ratio':
|
|
// Percent of the available vertical space that one row takes up. Here, rowHeight holds
|
|
// the ratio value. For example, if the width:height ratio is 4:3, rowHeight = 1.333.
|
|
var vShare = hShare / rowHeight;
|
|
|
|
// Base veritcal size of a row.
|
|
var vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter });
|
|
|
|
// padidngTop and marginTop are used to maintain the given aspect ratio, as
|
|
// a percentage-based value for these properties is applied to the *width* of the
|
|
// containing block. See http://www.w3.org/TR/CSS2/box.html#margin-properties
|
|
style.paddingTop = DIMENSION({ unit: vUnit, span: spans.row, gutter: gutter});
|
|
style.marginTop = POSITION({ unit: vUnit, offset: position.row, gutter: gutter });
|
|
break;
|
|
|
|
case 'fit':
|
|
// Fraction of the gutter size that each column takes up.
|
|
var vGutterShare = (rowCount - 1) / rowCount;
|
|
|
|
// Percent of the available vertical space that one row takes up.
|
|
var vShare = (1 / rowCount) * 100;
|
|
|
|
// Base vertical size of a row.
|
|
var vUnit = UNIT({share: vShare, gutterShare: vGutterShare, gutter: gutter});
|
|
|
|
style.top = POSITION({unit: vUnit, offset: position.row, gutter: gutter});
|
|
style.height = DIMENSION({unit: vUnit, span: spans.row, gutter: gutter});
|
|
break;
|
|
}
|
|
|
|
return style;
|
|
}
|
|
|
|
function getGridStyle(colCount, rowCount, gutter, rowMode, rowHeight) {
|
|
var style = {};
|
|
|
|
switch(rowMode) {
|
|
case 'fixed':
|
|
style.height = DIMENSION({ unit: rowHeight, span: rowCount, gutter: gutter });
|
|
style.paddingBottom = '';
|
|
break;
|
|
|
|
case 'ratio':
|
|
// rowHeight is width / height
|
|
var hGutterShare = colCount === 1 ? 0 : (colCount - 1) / colCount,
|
|
hShare = (1 / colCount) * 100,
|
|
vShare = hShare * (1 / rowHeight),
|
|
vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter });
|
|
|
|
style.height = '';
|
|
style.paddingBottom = DIMENSION({ unit: vUnit, span: rowCount, gutter: gutter});
|
|
break;
|
|
|
|
case 'fit':
|
|
// noop, as the height is user set
|
|
break;
|
|
}
|
|
|
|
return style;
|
|
}
|
|
|
|
function getTileElements() {
|
|
return [].filter.call(element.children(), function(ele) {
|
|
return ele.tagName == 'MD-GRID-TILE' && !ele.$$mdDestroyed;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Gets an array of objects containing the rowspan and colspan for each tile.
|
|
* @returns {Array<{row: number, col: number}>}
|
|
*/
|
|
function getTileSpans(tileElements) {
|
|
return [].map.call(tileElements, function(ele) {
|
|
var ctrl = angular.element(ele).controller('mdGridTile');
|
|
return {
|
|
row: parseInt(
|
|
$mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-rowspan'), 10) || 1,
|
|
col: parseInt(
|
|
$mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-colspan'), 10) || 1
|
|
};
|
|
});
|
|
}
|
|
|
|
function getColumnCount() {
|
|
var colCount = parseInt($mdMedia.getResponsiveAttribute(attrs, 'md-cols'), 10);
|
|
if (isNaN(colCount)) {
|
|
throw 'md-grid-list: md-cols attribute was not found, or contained a non-numeric value';
|
|
}
|
|
return colCount;
|
|
}
|
|
|
|
function getGutter() {
|
|
return applyDefaultUnit($mdMedia.getResponsiveAttribute(attrs, 'md-gutter') || 1);
|
|
}
|
|
|
|
function getRowHeight() {
|
|
var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height');
|
|
if (!rowHeight) {
|
|
throw 'md-grid-list: md-row-height attribute was not found';
|
|
}
|
|
|
|
switch (getRowMode()) {
|
|
case 'fixed':
|
|
return applyDefaultUnit(rowHeight);
|
|
case 'ratio':
|
|
var whRatio = rowHeight.split(':');
|
|
return parseFloat(whRatio[0]) / parseFloat(whRatio[1]);
|
|
case 'fit':
|
|
return 0; // N/A
|
|
}
|
|
}
|
|
|
|
function getRowMode() {
|
|
var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height');
|
|
if (!rowHeight) {
|
|
throw 'md-grid-list: md-row-height attribute was not found';
|
|
}
|
|
|
|
if (rowHeight == 'fit') {
|
|
return 'fit';
|
|
} else if (rowHeight.indexOf(':') !== -1) {
|
|
return 'ratio';
|
|
} else {
|
|
return 'fixed';
|
|
}
|
|
}
|
|
|
|
function applyDefaultUnit(val) {
|
|
return /\D$/.test(val) ? val : val + 'px';
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ngInject */
|
|
function GridListController($mdUtil) {
|
|
this.layoutInvalidated = false;
|
|
this.tilesInvalidated = false;
|
|
this.$timeout_ = $mdUtil.nextTick;
|
|
this.layoutDelegate = angular.noop;
|
|
}
|
|
|
|
GridListController.prototype = {
|
|
invalidateTiles: function() {
|
|
this.tilesInvalidated = true;
|
|
this.invalidateLayout();
|
|
},
|
|
|
|
invalidateLayout: function() {
|
|
if (this.layoutInvalidated) {
|
|
return;
|
|
}
|
|
this.layoutInvalidated = true;
|
|
this.$timeout_(angular.bind(this, this.layout));
|
|
},
|
|
|
|
layout: function() {
|
|
try {
|
|
this.layoutDelegate(this.tilesInvalidated);
|
|
} finally {
|
|
this.layoutInvalidated = false;
|
|
this.tilesInvalidated = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/* ngInject */
|
|
function GridLayoutFactory($mdUtil) {
|
|
var defaultAnimator = GridTileAnimator;
|
|
|
|
/**
|
|
* Set the reflow animator callback
|
|
*/
|
|
GridLayout.animateWith = function(customAnimator) {
|
|
defaultAnimator = !angular.isFunction(customAnimator) ? GridTileAnimator : customAnimator;
|
|
};
|
|
|
|
return GridLayout;
|
|
|
|
/**
|
|
* Publish layout function
|
|
*/
|
|
function GridLayout(colCount, tileSpans) {
|
|
var self, layoutInfo, gridStyles, layoutTime, mapTime, reflowTime;
|
|
|
|
layoutTime = $mdUtil.time(function() {
|
|
layoutInfo = calculateGridFor(colCount, tileSpans);
|
|
});
|
|
|
|
return self = {
|
|
|
|
/**
|
|
* An array of objects describing each tile's position in the grid.
|
|
*/
|
|
layoutInfo: function() {
|
|
return layoutInfo;
|
|
},
|
|
|
|
/**
|
|
* Maps grid positioning to an element and a set of styles using the
|
|
* provided updateFn.
|
|
*/
|
|
map: function(updateFn) {
|
|
mapTime = $mdUtil.time(function() {
|
|
var info = self.layoutInfo();
|
|
gridStyles = updateFn(info.positioning, info.rowCount);
|
|
});
|
|
return self;
|
|
},
|
|
|
|
/**
|
|
* Default animator simply sets the element.css( <styles> ). An alternate
|
|
* animator can be provided as an argument. The function has the following
|
|
* signature:
|
|
*
|
|
* function({grid: {element: JQLite, style: Object}, tiles: Array<{element: JQLite, style: Object}>)
|
|
*/
|
|
reflow: function(animatorFn) {
|
|
reflowTime = $mdUtil.time(function() {
|
|
var animator = animatorFn || defaultAnimator;
|
|
animator(gridStyles.grid, gridStyles.tiles);
|
|
});
|
|
return self;
|
|
},
|
|
|
|
/**
|
|
* Timing for the most recent layout run.
|
|
*/
|
|
performance: function() {
|
|
return {
|
|
tileCount: tileSpans.length,
|
|
layoutTime: layoutTime,
|
|
mapTime: mapTime,
|
|
reflowTime: reflowTime,
|
|
totalTime: layoutTime + mapTime + reflowTime
|
|
};
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Default Gridlist animator simple sets the css for each element;
|
|
* NOTE: any transitions effects must be manually set in the CSS.
|
|
* e.g.
|
|
*
|
|
* md-grid-tile {
|
|
* transition: all 700ms ease-out 50ms;
|
|
* }
|
|
*
|
|
*/
|
|
function GridTileAnimator(grid, tiles) {
|
|
grid.element.css(grid.style);
|
|
tiles.forEach(function(t) {
|
|
t.element.css(t.style);
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Calculates the positions of tiles.
|
|
*
|
|
* The algorithm works as follows:
|
|
* An Array<Number> with length colCount (spaceTracker) keeps track of
|
|
* available tiling positions, where elements of value 0 represents an
|
|
* empty position. Space for a tile is reserved by finding a sequence of
|
|
* 0s with length <= than the tile's colspan. When such a space has been
|
|
* found, the occupied tile positions are incremented by the tile's
|
|
* rowspan value, as these positions have become unavailable for that
|
|
* many rows.
|
|
*
|
|
* If the end of a row has been reached without finding space for the
|
|
* tile, spaceTracker's elements are each decremented by 1 to a minimum
|
|
* of 0. Rows are searched in this fashion until space is found.
|
|
*/
|
|
function calculateGridFor(colCount, tileSpans) {
|
|
var curCol = 0,
|
|
curRow = 0,
|
|
spaceTracker = newSpaceTracker();
|
|
|
|
return {
|
|
positioning: tileSpans.map(function(spans, i) {
|
|
return {
|
|
spans: spans,
|
|
position: reserveSpace(spans, i)
|
|
};
|
|
}),
|
|
rowCount: curRow + Math.max.apply(Math, spaceTracker)
|
|
};
|
|
|
|
function reserveSpace(spans, i) {
|
|
if (spans.col > colCount) {
|
|
throw 'md-grid-list: Tile at position ' + i + ' has a colspan ' +
|
|
'(' + spans.col + ') that exceeds the column count ' +
|
|
'(' + colCount + ')';
|
|
}
|
|
|
|
var start = 0,
|
|
end = 0;
|
|
|
|
// TODO(shyndman): This loop isn't strictly necessary if you can
|
|
// determine the minimum number of rows before a space opens up. To do
|
|
// this, recognize that you've iterated across an entire row looking for
|
|
// space, and if so fast-forward by the minimum rowSpan count. Repeat
|
|
// until the required space opens up.
|
|
while (end - start < spans.col) {
|
|
if (curCol >= colCount) {
|
|
nextRow();
|
|
continue;
|
|
}
|
|
|
|
start = spaceTracker.indexOf(0, curCol);
|
|
if (start === -1 || (end = findEnd(start + 1)) === -1) {
|
|
start = end = 0;
|
|
nextRow();
|
|
continue;
|
|
}
|
|
|
|
curCol = end + 1;
|
|
}
|
|
|
|
adjustRow(start, spans.col, spans.row);
|
|
curCol = start + spans.col;
|
|
|
|
return {
|
|
col: start,
|
|
row: curRow
|
|
};
|
|
}
|
|
|
|
function nextRow() {
|
|
curCol = 0;
|
|
curRow++;
|
|
adjustRow(0, colCount, -1); // Decrement row spans by one
|
|
}
|
|
|
|
function adjustRow(from, cols, by) {
|
|
for (var i = from; i < from + cols; i++) {
|
|
spaceTracker[i] = Math.max(spaceTracker[i] + by, 0);
|
|
}
|
|
}
|
|
|
|
function findEnd(start) {
|
|
var i;
|
|
for (i = start; i < spaceTracker.length; i++) {
|
|
if (spaceTracker[i] !== 0) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
if (i === spaceTracker.length) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
function newSpaceTracker() {
|
|
var tracker = [];
|
|
for (var i = 0; i < colCount; i++) {
|
|
tracker.push(0);
|
|
}
|
|
return tracker;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @ngdoc directive
|
|
* @name mdGridTile
|
|
* @module material.components.gridList
|
|
* @restrict E
|
|
* @description
|
|
* Tiles contain the content of an `md-grid-list`. They span one or more grid
|
|
* cells vertically or horizontally, and use `md-grid-tile-{footer,header}` to
|
|
* display secondary content.
|
|
*
|
|
* ### Responsive Attributes
|
|
*
|
|
* The `md-grid-tile` directive supports "responsive" attributes, which allow
|
|
* different `md-rowspan` and `md-colspan` values depending on the currently
|
|
* matching media query.
|
|
*
|
|
* In order to set a responsive attribute, first define the fallback value with
|
|
* the standard attribute name, then add additional attributes with the
|
|
* following convention: `{base-attribute-name}-{media-query-name}="{value}"`
|
|
* (ie. `md-colspan-sm="4"`)
|
|
*
|
|
* @param {number=} md-colspan The number of columns to span (default 1). Cannot
|
|
* exceed the number of columns in the grid. Supports interpolation.
|
|
* @param {number=} md-rowspan The number of rows to span (default 1). Supports
|
|
* interpolation.
|
|
*
|
|
* @usage
|
|
* With header:
|
|
* <hljs lang="html">
|
|
* <md-grid-tile>
|
|
* <md-grid-tile-header>
|
|
* <h3>This is a header</h3>
|
|
* </md-grid-tile-header>
|
|
* </md-grid-tile>
|
|
* </hljs>
|
|
*
|
|
* With footer:
|
|
* <hljs lang="html">
|
|
* <md-grid-tile>
|
|
* <md-grid-tile-footer>
|
|
* <h3>This is a footer</h3>
|
|
* </md-grid-tile-footer>
|
|
* </md-grid-tile>
|
|
* </hljs>
|
|
*
|
|
* Spanning multiple rows/columns:
|
|
* <hljs lang="html">
|
|
* <md-grid-tile md-colspan="2" md-rowspan="3">
|
|
* </md-grid-tile>
|
|
* </hljs>
|
|
*
|
|
* Responsive attributes:
|
|
* <hljs lang="html">
|
|
* <md-grid-tile md-colspan="1" md-colspan-sm="3" md-colspan-md="5">
|
|
* </md-grid-tile>
|
|
* </hljs>
|
|
*/
|
|
function GridTileDirective($mdMedia) {
|
|
return {
|
|
restrict: 'E',
|
|
require: '^mdGridList',
|
|
template: '<figure ng-transclude></figure>',
|
|
transclude: true,
|
|
scope: {},
|
|
// Simple controller that exposes attributes to the grid directive
|
|
controller: ["$attrs", function($attrs) {
|
|
this.$attrs = $attrs;
|
|
}],
|
|
link: postLink
|
|
};
|
|
|
|
function postLink(scope, element, attrs, gridCtrl) {
|
|
// Apply semantics
|
|
element.attr('role', 'listitem');
|
|
|
|
// If our colspan or rowspan changes, trigger a layout
|
|
var unwatchAttrs = $mdMedia.watchResponsiveAttributes(['md-colspan', 'md-rowspan'],
|
|
attrs, angular.bind(gridCtrl, gridCtrl.invalidateLayout));
|
|
|
|
// Tile registration/deregistration
|
|
gridCtrl.invalidateTiles();
|
|
scope.$on('$destroy', function() {
|
|
// Mark the tile as destroyed so it is no longer considered in layout,
|
|
// even if the DOM element sticks around (like during a leave animation)
|
|
element[0].$$mdDestroyed = true;
|
|
unwatchAttrs();
|
|
gridCtrl.invalidateLayout();
|
|
});
|
|
|
|
if (angular.isDefined(scope.$parent.$index)) {
|
|
scope.$watch(function() { return scope.$parent.$index; },
|
|
function indexChanged(newIdx, oldIdx) {
|
|
if (newIdx === oldIdx) {
|
|
return;
|
|
}
|
|
gridCtrl.invalidateTiles();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function GridTileCaptionDirective() {
|
|
return {
|
|
template: '<figcaption ng-transclude></figcaption>',
|
|
transclude: true
|
|
};
|
|
}
|
|
|
|
})(window, window.angular);
|