gridList.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764
  1. /*!
  2. * Angular Material Design
  3. * https://github.com/angular/material
  4. * @license MIT
  5. * v0.11.4
  6. */
  7. goog.provide('ng.material.components.gridList');
  8. goog.require('ng.material.core');
  9. /**
  10. * @ngdoc module
  11. * @name material.components.gridList
  12. */
  13. angular.module('material.components.gridList', ['material.core'])
  14. .directive('mdGridList', GridListDirective)
  15. .directive('mdGridTile', GridTileDirective)
  16. .directive('mdGridTileFooter', GridTileCaptionDirective)
  17. .directive('mdGridTileHeader', GridTileCaptionDirective)
  18. .factory('$mdGridLayout', GridLayoutFactory);
  19. /**
  20. * @ngdoc directive
  21. * @name mdGridList
  22. * @module material.components.gridList
  23. * @restrict E
  24. * @description
  25. * Grid lists are an alternative to standard list views. Grid lists are distinct
  26. * from grids used for layouts and other visual presentations.
  27. *
  28. * A grid list is best suited to presenting a homogenous data type, typically
  29. * images, and is optimized for visual comprehension and differentiating between
  30. * like data types.
  31. *
  32. * A grid list is a continuous element consisting of tessellated, regular
  33. * subdivisions called cells that contain tiles (`md-grid-tile`).
  34. *
  35. * <img src="//material-design.storage.googleapis.com/publish/v_2/material_ext_publish/0Bx4BSt6jniD7OVlEaXZ5YmU1Xzg/components_grids_usage2.png"
  36. * style="width: 300px; height: auto; margin-right: 16px;" alt="Concept of grid explained visually">
  37. * <img src="//material-design.storage.googleapis.com/publish/v_2/material_ext_publish/0Bx4BSt6jniD7VGhsOE5idWlJWXM/components_grids_usage3.png"
  38. * style="width: 300px; height: auto;" alt="Grid concepts legend">
  39. *
  40. * Cells are arrayed vertically and horizontally within the grid.
  41. *
  42. * Tiles hold content and can span one or more cells vertically or horizontally.
  43. *
  44. * ### Responsive Attributes
  45. *
  46. * The `md-grid-list` directive supports "responsive" attributes, which allow
  47. * different `md-cols`, `md-gutter` and `md-row-height` values depending on the
  48. * currently matching media query (as defined in `$mdConstant.MEDIA`).
  49. *
  50. * In order to set a responsive attribute, first define the fallback value with
  51. * the standard attribute name, then add additional attributes with the
  52. * following convention: `{base-attribute-name}-{media-query-name}="{value}"`
  53. * (ie. `md-cols-lg="8"`)
  54. *
  55. * @param {number} md-cols Number of columns in the grid.
  56. * @param {string} md-row-height One of
  57. * <ul>
  58. * <li>CSS length - Fixed height rows (eg. `8px` or `1rem`)</li>
  59. * <li>`{width}:{height}` - Ratio of width to height (eg.
  60. * `md-row-height="16:9"`)</li>
  61. * <li>`"fit"` - Height will be determined by subdividing the available
  62. * height by the number of rows</li>
  63. * </ul>
  64. * @param {string=} md-gutter The amount of space between tiles in CSS units
  65. * (default 1px)
  66. * @param {expression=} md-on-layout Expression to evaluate after layout. Event
  67. * object is available as `$event`, and contains performance information.
  68. *
  69. * @usage
  70. * Basic:
  71. * <hljs lang="html">
  72. * <md-grid-list md-cols="5" md-gutter="1em" md-row-height="4:3">
  73. * <md-grid-tile></md-grid-tile>
  74. * </md-grid-list>
  75. * </hljs>
  76. *
  77. * Fixed-height rows:
  78. * <hljs lang="html">
  79. * <md-grid-list md-cols="4" md-row-height="200px" ...>
  80. * <md-grid-tile></md-grid-tile>
  81. * </md-grid-list>
  82. * </hljs>
  83. *
  84. * Fit rows:
  85. * <hljs lang="html">
  86. * <md-grid-list md-cols="4" md-row-height="fit" style="height: 400px;" ...>
  87. * <md-grid-tile></md-grid-tile>
  88. * </md-grid-list>
  89. * </hljs>
  90. *
  91. * Using responsive attributes:
  92. * <hljs lang="html">
  93. * <md-grid-list
  94. * md-cols-sm="2"
  95. * md-cols-md="4"
  96. * md-cols-lg="8"
  97. * md-cols-gt-lg="12"
  98. * ...>
  99. * <md-grid-tile></md-grid-tile>
  100. * </md-grid-list>
  101. * </hljs>
  102. */
  103. function GridListDirective($interpolate, $mdConstant, $mdGridLayout, $mdMedia) {
  104. return {
  105. restrict: 'E',
  106. controller: GridListController,
  107. scope: {
  108. mdOnLayout: '&'
  109. },
  110. link: postLink
  111. };
  112. function postLink(scope, element, attrs, ctrl) {
  113. // Apply semantics
  114. element.attr('role', 'list');
  115. // Provide the controller with a way to trigger layouts.
  116. ctrl.layoutDelegate = layoutDelegate;
  117. var invalidateLayout = angular.bind(ctrl, ctrl.invalidateLayout),
  118. unwatchAttrs = watchMedia();
  119. scope.$on('$destroy', unwatchMedia);
  120. /**
  121. * Watches for changes in media, invalidating layout as necessary.
  122. */
  123. function watchMedia() {
  124. for (var mediaName in $mdConstant.MEDIA) {
  125. $mdMedia(mediaName); // initialize
  126. $mdMedia.getQuery($mdConstant.MEDIA[mediaName])
  127. .addListener(invalidateLayout);
  128. }
  129. return $mdMedia.watchResponsiveAttributes(
  130. ['md-cols', 'md-row-height', 'md-gutter'], attrs, layoutIfMediaMatch);
  131. }
  132. function unwatchMedia() {
  133. ctrl.layoutDelegate = angular.noop;
  134. unwatchAttrs();
  135. for (var mediaName in $mdConstant.MEDIA) {
  136. $mdMedia.getQuery($mdConstant.MEDIA[mediaName])
  137. .removeListener(invalidateLayout);
  138. }
  139. }
  140. /**
  141. * Performs grid layout if the provided mediaName matches the currently
  142. * active media type.
  143. */
  144. function layoutIfMediaMatch(mediaName) {
  145. if (mediaName == null) {
  146. // TODO(shyndman): It would be nice to only layout if we have
  147. // instances of attributes using this media type
  148. ctrl.invalidateLayout();
  149. } else if ($mdMedia(mediaName)) {
  150. ctrl.invalidateLayout();
  151. }
  152. }
  153. var lastLayoutProps;
  154. /**
  155. * Invokes the layout engine, and uses its results to lay out our
  156. * tile elements.
  157. *
  158. * @param {boolean} tilesInvalidated Whether tiles have been
  159. * added/removed/moved since the last layout. This is to avoid situations
  160. * where tiles are replaced with properties identical to their removed
  161. * counterparts.
  162. */
  163. function layoutDelegate(tilesInvalidated) {
  164. var tiles = getTileElements();
  165. var props = {
  166. tileSpans: getTileSpans(tiles),
  167. colCount: getColumnCount(),
  168. rowMode: getRowMode(),
  169. rowHeight: getRowHeight(),
  170. gutter: getGutter()
  171. };
  172. if (!tilesInvalidated && angular.equals(props, lastLayoutProps)) {
  173. return;
  174. }
  175. var performance =
  176. $mdGridLayout(props.colCount, props.tileSpans, tiles)
  177. .map(function(tilePositions, rowCount) {
  178. return {
  179. grid: {
  180. element: element,
  181. style: getGridStyle(props.colCount, rowCount,
  182. props.gutter, props.rowMode, props.rowHeight)
  183. },
  184. tiles: tilePositions.map(function(ps, i) {
  185. return {
  186. element: angular.element(tiles[i]),
  187. style: getTileStyle(ps.position, ps.spans,
  188. props.colCount, rowCount,
  189. props.gutter, props.rowMode, props.rowHeight)
  190. }
  191. })
  192. }
  193. })
  194. .reflow()
  195. .performance();
  196. // Report layout
  197. scope.mdOnLayout({
  198. $event: {
  199. performance: performance
  200. }
  201. });
  202. lastLayoutProps = props;
  203. }
  204. // Use $interpolate to do some simple string interpolation as a convenience.
  205. var startSymbol = $interpolate.startSymbol();
  206. var endSymbol = $interpolate.endSymbol();
  207. // Returns an expression wrapped in the interpolator's start and end symbols.
  208. function expr(exprStr) {
  209. return startSymbol + exprStr + endSymbol;
  210. }
  211. // The amount of space a single 1x1 tile would take up (either width or height), used as
  212. // a basis for other calculations. This consists of taking the base size percent (as would be
  213. // if evenly dividing the size between cells), and then subtracting the size of one gutter.
  214. // However, since there are no gutters on the edges, each tile only uses a fration
  215. // (gutterShare = numGutters / numCells) of the gutter size. (Imagine having one gutter per
  216. // tile, and then breaking up the extra gutter on the edge evenly among the cells).
  217. var UNIT = $interpolate(expr('share') + '% - (' + expr('gutter') + ' * ' + expr('gutterShare') + ')');
  218. // The horizontal or vertical position of a tile, e.g., the 'top' or 'left' property value.
  219. // The position comes the size of a 1x1 tile plus gutter for each previous tile in the
  220. // row/column (offset).
  221. var POSITION = $interpolate('calc((' + expr('unit') + ' + ' + expr('gutter') + ') * ' + expr('offset') + ')');
  222. // The actual size of a tile, e.g., width or height, taking rowSpan or colSpan into account.
  223. // This is computed by multiplying the base unit by the rowSpan/colSpan, and then adding back
  224. // in the space that the gutter would normally have used (which was already accounted for in
  225. // the base unit calculation).
  226. var DIMENSION = $interpolate('calc((' + expr('unit') + ') * ' + expr('span') + ' + (' + expr('span') + ' - 1) * ' + expr('gutter') + ')');
  227. /**
  228. * Gets the styles applied to a tile element described by the given parameters.
  229. * @param {{row: number, col: number}} position The row and column indices of the tile.
  230. * @param {{row: number, col: number}} spans The rowSpan and colSpan of the tile.
  231. * @param {number} colCount The number of columns.
  232. * @param {number} rowCount The number of rows.
  233. * @param {string} gutter The amount of space between tiles. This will be something like
  234. * '5px' or '2em'.
  235. * @param {string} rowMode The row height mode. Can be one of:
  236. * 'fixed': all rows have a fixed size, given by rowHeight,
  237. * 'ratio': row height defined as a ratio to width, or
  238. * 'fit': fit to the grid-list element height, divinding evenly among rows.
  239. * @param {string|number} rowHeight The height of a row. This is only used for 'fixed' mode and
  240. * for 'ratio' mode. For 'ratio' mode, this is the *ratio* of width-to-height (e.g., 0.75).
  241. * @returns {Object} Map of CSS properties to be applied to the style element. Will define
  242. * values for top, left, width, height, marginTop, and paddingTop.
  243. */
  244. function getTileStyle(position, spans, colCount, rowCount, gutter, rowMode, rowHeight) {
  245. // TODO(shyndman): There are style caching opportunities here.
  246. // Percent of the available horizontal space that one column takes up.
  247. var hShare = (1 / colCount) * 100;
  248. // Fraction of the gutter size that each column takes up.
  249. var hGutterShare = (colCount - 1) / colCount;
  250. // Base horizontal size of a column.
  251. var hUnit = UNIT({share: hShare, gutterShare: hGutterShare, gutter: gutter});
  252. // The width and horizontal position of each tile is always calculated the same way, but the
  253. // height and vertical position depends on the rowMode.
  254. var style = {
  255. left: POSITION({ unit: hUnit, offset: position.col, gutter: gutter }),
  256. width: DIMENSION({ unit: hUnit, span: spans.col, gutter: gutter }),
  257. // resets
  258. paddingTop: '',
  259. marginTop: '',
  260. top: '',
  261. height: ''
  262. };
  263. switch (rowMode) {
  264. case 'fixed':
  265. // In fixed mode, simply use the given rowHeight.
  266. style.top = POSITION({ unit: rowHeight, offset: position.row, gutter: gutter });
  267. style.height = DIMENSION({ unit: rowHeight, span: spans.row, gutter: gutter });
  268. break;
  269. case 'ratio':
  270. // Percent of the available vertical space that one row takes up. Here, rowHeight holds
  271. // the ratio value. For example, if the width:height ratio is 4:3, rowHeight = 1.333.
  272. var vShare = hShare / rowHeight;
  273. // Base veritcal size of a row.
  274. var vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter });
  275. // padidngTop and marginTop are used to maintain the given aspect ratio, as
  276. // a percentage-based value for these properties is applied to the *width* of the
  277. // containing block. See http://www.w3.org/TR/CSS2/box.html#margin-properties
  278. style.paddingTop = DIMENSION({ unit: vUnit, span: spans.row, gutter: gutter});
  279. style.marginTop = POSITION({ unit: vUnit, offset: position.row, gutter: gutter });
  280. break;
  281. case 'fit':
  282. // Fraction of the gutter size that each column takes up.
  283. var vGutterShare = (rowCount - 1) / rowCount;
  284. // Percent of the available vertical space that one row takes up.
  285. var vShare = (1 / rowCount) * 100;
  286. // Base vertical size of a row.
  287. var vUnit = UNIT({share: vShare, gutterShare: vGutterShare, gutter: gutter});
  288. style.top = POSITION({unit: vUnit, offset: position.row, gutter: gutter});
  289. style.height = DIMENSION({unit: vUnit, span: spans.row, gutter: gutter});
  290. break;
  291. }
  292. return style;
  293. }
  294. function getGridStyle(colCount, rowCount, gutter, rowMode, rowHeight) {
  295. var style = {};
  296. switch(rowMode) {
  297. case 'fixed':
  298. style.height = DIMENSION({ unit: rowHeight, span: rowCount, gutter: gutter });
  299. style.paddingBottom = '';
  300. break;
  301. case 'ratio':
  302. // rowHeight is width / height
  303. var hGutterShare = colCount === 1 ? 0 : (colCount - 1) / colCount,
  304. hShare = (1 / colCount) * 100,
  305. vShare = hShare * (1 / rowHeight),
  306. vUnit = UNIT({ share: vShare, gutterShare: hGutterShare, gutter: gutter });
  307. style.height = '';
  308. style.paddingBottom = DIMENSION({ unit: vUnit, span: rowCount, gutter: gutter});
  309. break;
  310. case 'fit':
  311. // noop, as the height is user set
  312. break;
  313. }
  314. return style;
  315. }
  316. function getTileElements() {
  317. return [].filter.call(element.children(), function(ele) {
  318. return ele.tagName == 'MD-GRID-TILE' && !ele.$$mdDestroyed;
  319. });
  320. }
  321. /**
  322. * Gets an array of objects containing the rowspan and colspan for each tile.
  323. * @returns {Array<{row: number, col: number}>}
  324. */
  325. function getTileSpans(tileElements) {
  326. return [].map.call(tileElements, function(ele) {
  327. var ctrl = angular.element(ele).controller('mdGridTile');
  328. return {
  329. row: parseInt(
  330. $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-rowspan'), 10) || 1,
  331. col: parseInt(
  332. $mdMedia.getResponsiveAttribute(ctrl.$attrs, 'md-colspan'), 10) || 1
  333. };
  334. });
  335. }
  336. function getColumnCount() {
  337. var colCount = parseInt($mdMedia.getResponsiveAttribute(attrs, 'md-cols'), 10);
  338. if (isNaN(colCount)) {
  339. throw 'md-grid-list: md-cols attribute was not found, or contained a non-numeric value';
  340. }
  341. return colCount;
  342. }
  343. function getGutter() {
  344. return applyDefaultUnit($mdMedia.getResponsiveAttribute(attrs, 'md-gutter') || 1);
  345. }
  346. function getRowHeight() {
  347. var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height');
  348. switch (getRowMode()) {
  349. case 'fixed':
  350. return applyDefaultUnit(rowHeight);
  351. case 'ratio':
  352. var whRatio = rowHeight.split(':');
  353. return parseFloat(whRatio[0]) / parseFloat(whRatio[1]);
  354. case 'fit':
  355. return 0; // N/A
  356. }
  357. }
  358. function getRowMode() {
  359. var rowHeight = $mdMedia.getResponsiveAttribute(attrs, 'md-row-height');
  360. if (rowHeight == 'fit') {
  361. return 'fit';
  362. } else if (rowHeight.indexOf(':') !== -1) {
  363. return 'ratio';
  364. } else {
  365. return 'fixed';
  366. }
  367. }
  368. function applyDefaultUnit(val) {
  369. return /\D$/.test(val) ? val : val + 'px';
  370. }
  371. }
  372. }
  373. GridListDirective.$inject = ["$interpolate", "$mdConstant", "$mdGridLayout", "$mdMedia"];
  374. /* ngInject */
  375. function GridListController($mdUtil) {
  376. this.layoutInvalidated = false;
  377. this.tilesInvalidated = false;
  378. this.$timeout_ = $mdUtil.nextTick;
  379. this.layoutDelegate = angular.noop;
  380. }
  381. GridListController.$inject = ["$mdUtil"];
  382. GridListController.prototype = {
  383. invalidateTiles: function() {
  384. this.tilesInvalidated = true;
  385. this.invalidateLayout();
  386. },
  387. invalidateLayout: function() {
  388. if (this.layoutInvalidated) {
  389. return;
  390. }
  391. this.layoutInvalidated = true;
  392. this.$timeout_(angular.bind(this, this.layout));
  393. },
  394. layout: function() {
  395. try {
  396. this.layoutDelegate(this.tilesInvalidated);
  397. } finally {
  398. this.layoutInvalidated = false;
  399. this.tilesInvalidated = false;
  400. }
  401. }
  402. };
  403. /* ngInject */
  404. function GridLayoutFactory($mdUtil) {
  405. var defaultAnimator = GridTileAnimator;
  406. /**
  407. * Set the reflow animator callback
  408. */
  409. GridLayout.animateWith = function(customAnimator) {
  410. defaultAnimator = !angular.isFunction(customAnimator) ? GridTileAnimator : customAnimator;
  411. };
  412. return GridLayout;
  413. /**
  414. * Publish layout function
  415. */
  416. function GridLayout(colCount, tileSpans) {
  417. var self, layoutInfo, gridStyles, layoutTime, mapTime, reflowTime;
  418. layoutTime = $mdUtil.time(function() {
  419. layoutInfo = calculateGridFor(colCount, tileSpans);
  420. });
  421. return self = {
  422. /**
  423. * An array of objects describing each tile's position in the grid.
  424. */
  425. layoutInfo: function() {
  426. return layoutInfo;
  427. },
  428. /**
  429. * Maps grid positioning to an element and a set of styles using the
  430. * provided updateFn.
  431. */
  432. map: function(updateFn) {
  433. mapTime = $mdUtil.time(function() {
  434. var info = self.layoutInfo();
  435. gridStyles = updateFn(info.positioning, info.rowCount);
  436. });
  437. return self;
  438. },
  439. /**
  440. * Default animator simply sets the element.css( <styles> ). An alternate
  441. * animator can be provided as an argument. The function has the following
  442. * signature:
  443. *
  444. * function({grid: {element: JQLite, style: Object}, tiles: Array<{element: JQLite, style: Object}>)
  445. */
  446. reflow: function(animatorFn) {
  447. reflowTime = $mdUtil.time(function() {
  448. var animator = animatorFn || defaultAnimator;
  449. animator(gridStyles.grid, gridStyles.tiles);
  450. });
  451. return self;
  452. },
  453. /**
  454. * Timing for the most recent layout run.
  455. */
  456. performance: function() {
  457. return {
  458. tileCount: tileSpans.length,
  459. layoutTime: layoutTime,
  460. mapTime: mapTime,
  461. reflowTime: reflowTime,
  462. totalTime: layoutTime + mapTime + reflowTime
  463. };
  464. }
  465. };
  466. }
  467. /**
  468. * Default Gridlist animator simple sets the css for each element;
  469. * NOTE: any transitions effects must be manually set in the CSS.
  470. * e.g.
  471. *
  472. * md-grid-tile {
  473. * transition: all 700ms ease-out 50ms;
  474. * }
  475. *
  476. */
  477. function GridTileAnimator(grid, tiles) {
  478. grid.element.css(grid.style);
  479. tiles.forEach(function(t) {
  480. t.element.css(t.style);
  481. })
  482. }
  483. /**
  484. * Calculates the positions of tiles.
  485. *
  486. * The algorithm works as follows:
  487. * An Array<Number> with length colCount (spaceTracker) keeps track of
  488. * available tiling positions, where elements of value 0 represents an
  489. * empty position. Space for a tile is reserved by finding a sequence of
  490. * 0s with length <= than the tile's colspan. When such a space has been
  491. * found, the occupied tile positions are incremented by the tile's
  492. * rowspan value, as these positions have become unavailable for that
  493. * many rows.
  494. *
  495. * If the end of a row has been reached without finding space for the
  496. * tile, spaceTracker's elements are each decremented by 1 to a minimum
  497. * of 0. Rows are searched in this fashion until space is found.
  498. */
  499. function calculateGridFor(colCount, tileSpans) {
  500. var curCol = 0,
  501. curRow = 0,
  502. spaceTracker = newSpaceTracker();
  503. return {
  504. positioning: tileSpans.map(function(spans, i) {
  505. return {
  506. spans: spans,
  507. position: reserveSpace(spans, i)
  508. };
  509. }),
  510. rowCount: curRow + Math.max.apply(Math, spaceTracker)
  511. };
  512. function reserveSpace(spans, i) {
  513. if (spans.col > colCount) {
  514. throw 'md-grid-list: Tile at position ' + i + ' has a colspan ' +
  515. '(' + spans.col + ') that exceeds the column count ' +
  516. '(' + colCount + ')';
  517. }
  518. var start = 0,
  519. end = 0;
  520. // TODO(shyndman): This loop isn't strictly necessary if you can
  521. // determine the minimum number of rows before a space opens up. To do
  522. // this, recognize that you've iterated across an entire row looking for
  523. // space, and if so fast-forward by the minimum rowSpan count. Repeat
  524. // until the required space opens up.
  525. while (end - start < spans.col) {
  526. if (curCol >= colCount) {
  527. nextRow();
  528. continue;
  529. }
  530. start = spaceTracker.indexOf(0, curCol);
  531. if (start === -1 || (end = findEnd(start + 1)) === -1) {
  532. start = end = 0;
  533. nextRow();
  534. continue;
  535. }
  536. curCol = end + 1;
  537. }
  538. adjustRow(start, spans.col, spans.row);
  539. curCol = start + spans.col;
  540. return {
  541. col: start,
  542. row: curRow
  543. };
  544. }
  545. function nextRow() {
  546. curCol = 0;
  547. curRow++;
  548. adjustRow(0, colCount, -1); // Decrement row spans by one
  549. }
  550. function adjustRow(from, cols, by) {
  551. for (var i = from; i < from + cols; i++) {
  552. spaceTracker[i] = Math.max(spaceTracker[i] + by, 0);
  553. }
  554. }
  555. function findEnd(start) {
  556. var i;
  557. for (i = start; i < spaceTracker.length; i++) {
  558. if (spaceTracker[i] !== 0) {
  559. return i;
  560. }
  561. }
  562. if (i === spaceTracker.length) {
  563. return i;
  564. }
  565. }
  566. function newSpaceTracker() {
  567. var tracker = [];
  568. for (var i = 0; i < colCount; i++) {
  569. tracker.push(0);
  570. }
  571. return tracker;
  572. }
  573. }
  574. }
  575. GridLayoutFactory.$inject = ["$mdUtil"];
  576. /**
  577. * @ngdoc directive
  578. * @name mdGridTile
  579. * @module material.components.gridList
  580. * @restrict E
  581. * @description
  582. * Tiles contain the content of an `md-grid-list`. They span one or more grid
  583. * cells vertically or horizontally, and use `md-grid-tile-{footer,header}` to
  584. * display secondary content.
  585. *
  586. * ### Responsive Attributes
  587. *
  588. * The `md-grid-tile` directive supports "responsive" attributes, which allow
  589. * different `md-rowspan` and `md-colspan` values depending on the currently
  590. * matching media query (as defined in `$mdConstant.MEDIA`).
  591. *
  592. * In order to set a responsive attribute, first define the fallback value with
  593. * the standard attribute name, then add additional attributes with the
  594. * following convention: `{base-attribute-name}-{media-query-name}="{value}"`
  595. * (ie. `md-colspan-sm="4"`)
  596. *
  597. * @param {number=} md-colspan The number of columns to span (default 1). Cannot
  598. * exceed the number of columns in the grid. Supports interpolation.
  599. * @param {number=} md-rowspan The number of rows to span (default 1). Supports
  600. * interpolation.
  601. *
  602. * @usage
  603. * With header:
  604. * <hljs lang="html">
  605. * <md-grid-tile>
  606. * <md-grid-tile-header>
  607. * <h3>This is a header</h3>
  608. * </md-grid-tile-header>
  609. * </md-grid-tile>
  610. * </hljs>
  611. *
  612. * With footer:
  613. * <hljs lang="html">
  614. * <md-grid-tile>
  615. * <md-grid-tile-footer>
  616. * <h3>This is a footer</h3>
  617. * </md-grid-tile-footer>
  618. * </md-grid-tile>
  619. * </hljs>
  620. *
  621. * Spanning multiple rows/columns:
  622. * <hljs lang="html">
  623. * <md-grid-tile md-colspan="2" md-rowspan="3">
  624. * </md-grid-tile>
  625. * </hljs>
  626. *
  627. * Responsive attributes:
  628. * <hljs lang="html">
  629. * <md-grid-tile md-colspan="1" md-colspan-sm="3" md-colspan-md="5">
  630. * </md-grid-tile>
  631. * </hljs>
  632. */
  633. function GridTileDirective($mdMedia) {
  634. return {
  635. restrict: 'E',
  636. require: '^mdGridList',
  637. template: '<figure ng-transclude></figure>',
  638. transclude: true,
  639. scope: {},
  640. // Simple controller that exposes attributes to the grid directive
  641. controller: ["$attrs", function($attrs) {
  642. this.$attrs = $attrs;
  643. }],
  644. link: postLink
  645. };
  646. function postLink(scope, element, attrs, gridCtrl) {
  647. // Apply semantics
  648. element.attr('role', 'listitem');
  649. // If our colspan or rowspan changes, trigger a layout
  650. var unwatchAttrs = $mdMedia.watchResponsiveAttributes(['md-colspan', 'md-rowspan'],
  651. attrs, angular.bind(gridCtrl, gridCtrl.invalidateLayout));
  652. // Tile registration/deregistration
  653. gridCtrl.invalidateTiles();
  654. scope.$on('$destroy', function() {
  655. // Mark the tile as destroyed so it is no longer considered in layout,
  656. // even if the DOM element sticks around (like during a leave animation)
  657. element[0].$$mdDestroyed = true;
  658. unwatchAttrs();
  659. gridCtrl.invalidateLayout();
  660. });
  661. if (angular.isDefined(scope.$parent.$index)) {
  662. scope.$watch(function() { return scope.$parent.$index; },
  663. function indexChanged(newIdx, oldIdx) {
  664. if (newIdx === oldIdx) {
  665. return;
  666. }
  667. gridCtrl.invalidateTiles();
  668. });
  669. }
  670. }
  671. }
  672. GridTileDirective.$inject = ["$mdMedia"];
  673. function GridTileCaptionDirective() {
  674. return {
  675. template: '<figcaption ng-transclude></figcaption>',
  676. transclude: true
  677. };
  678. }
  679. ng.material.components.gridList = angular.module("material.components.gridList");