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