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