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