arnaucube
arnaucube/socketioMEANseedPublic Self-hosted repository
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);