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