diff --git a/addon/components/paper-grid-list.js b/addon/components/paper-grid-list.js
index 313a19c20..53035a28f 100644
--- a/addon/components/paper-grid-list.js
+++ b/addon/components/paper-grid-list.js
@@ -2,18 +2,19 @@
* @module ember-paper
*/
import Ember from 'ember';
+import layout from '../templates/components/paper-grid-list';
+import { ParentMixin } from 'ember-composability-tools';
import gridLayout from '../utils/grid-layout';
-const { Component, inject, computed, A, run, get, isEqual } = Ember;
+const { Component, inject, computed, run } = Ember;
+
+const mediaRegex = /(^|\s)((?:print-)|(?:[a-z]{2}-){1,2})?(\d+)(?!\S)/g;
+const rowHeightRegex = /(^|\s)((?:print-)|(?:[a-z]{2}-){1,2})?(\d+(?:[a-z]{2,3}|%)?|\d+:\d+|fit)(?!\S)/g;
const unitCSS = (units) => {
return `${units.share}% - (${units.gutter} * ${units.gutterShare})`;
};
-const positionCSS = (positions) => {
- return `calc((${positions.unit} + ${positions.gutter}) * ${positions.offset})`;
-};
-
const dimensionCSS = (dimensions) => {
return `calc((${dimensions.unit}) * ${dimensions.span} + (${dimensions.span} - 1) * ${dimensions.gutter})`;
};
@@ -22,205 +23,96 @@ const media = (mediaName) => {
return ((mediaName.charAt(0) !== '(') ? (`(${mediaName})`) : mediaName);
};
+const mediaListenerName = (name) => {
+ return `${name.replace('-', '')}Listener`;
+};
+
/**
* @class PaperGridList
* @extends Ember.Component
*/
-export default Component.extend({
+export default Component.extend(ParentMixin, {
+ layout,
tagName: 'md-grid-list',
constants: inject.service(),
- layoutInvalidated: false,
- tilesInvalidated: false,
- lastLayoutProps: {},
- tiles: computed(function() {
- return A();
- }),
-
- _invalidateLayoutListener: computed(function() {
- return run.bind(this, () => {
- this.send('invalidateLayout');
- });
- }),
+ tiles: computed.alias('childComponents'),
didInsertElement() {
this._super(...arguments);
- this._watchMedia();
- this._watchResponsiveAttributes(['md-cols', 'md-row-height', 'md-gutter'], run.bind(this, this.layoutIfMediaMatch));
-
+ this._installMediaListener();
},
- willDestroyElement() {
+ didUpdateAttrs() {
this._super(...arguments);
- this._unwatchMedia();
- },
-
- registerGridTile(gridTile) {
- this.get('tiles').addObject(gridTile);
+ this.updateGrid();
},
- doLayout() {
- if (this.isDestroyed) {
- return;
- }
- try {
- let tilesInvalidated = this.get('tilesInvalidated');
- this._layoutDelegate(tilesInvalidated);
- } finally {
- this.setProperties({
- 'layoutInvalidated': false,
- 'tilesInvalidated': false
- });
- }
- },
-
- layoutIfMediaMatch(mediaName) {
- if (mediaName == null) {
- this.send('invalidateLayout');
- } else if (window.matchMedia(mediaName)) {
- this.send('invalidateLayout');
- }
+ willDestroyElement() {
+ this._super(...arguments);
+ this._uninstallMediaListener();
},
- _watchMedia() {
-
- let invalidateLayoutListener = this.get('_invalidateLayoutListener');
-
+ // Sets up a listener for each media query
+ _installMediaListener() {
for (let mediaName in this.get('constants.MEDIA')) {
let query = this.get('constants.MEDIA')[mediaName] || media(mediaName);
- window.matchMedia(query).addListener(invalidateLayoutListener);
- }
- },
-
- _watchResponsiveAttributes(attrNames, watchFn) {
- let checkObserverValues = (sender, key, mediaName) => {
- let oldValue = sender.get(`old${key}`);
- let newValue = sender.get(key);
+ let mediaList = window.matchMedia(query);
+ let listenerName = mediaListenerName(mediaName);
- if (oldValue !== newValue) {
- watchFn(mediaName);
- }
+ // Sets mediaList to a property so removeListener can access it
+ this.set(`${listenerName}List`, mediaList);
+ // Creates a function based on mediaName so that removeListener can remove it.
+ this.set(listenerName, run.bind(this, (result) => {
+ this._mediaDidChange(mediaName, result.matches);
+ }));
- };
+ // Calls '_mediaDidChange' once
+ this[listenerName](mediaList);
- attrNames.forEach((attrName) => {
- if (get(this, attrName)) {
- this.set(`old${attrName}`, get(this, attrName));
-
- let customObserver = run.bind(this, checkObserverValues, this, attrName);
-
- this.addObserver(attrName, customObserver);
- }
-
- for (let mediaName in this.get('constants.MEDIA')) {
- let normalizedName = `${attrName}-${mediaName}`;
- if (get(this, normalizedName)) {
- let customObserverNormalized = run.bind(this, checkObserverValues, this, normalizedName, mediaName);
- this.addObserver(normalizedName, customObserverNormalized);
- }
- }
-
- });
+ mediaList.addListener(this[listenerName]);
+ }
},
- _unwatchMedia() {
- let invalidateLayoutListener = this.get('_invalidateLayoutListener');
+ _uninstallMediaListener() {
for (let mediaName in this.get('constants.MEDIA')) {
- let query = this.get('constants.MEDIA')[mediaName] || media(mediaName);
- window.matchMedia(query).removeListener(invalidateLayoutListener);
+ let listenerName = mediaListenerName(mediaName);
+ let mediaList = this.get(`${listenerName}List`);
+ mediaList.removeListener(this[listenerName]);
}
},
- _getResponsiveAttribute(component, attrName) {
- let mediaPriorities = this.get('constants.MEDIA_PRIORITY');
- for (let i = 0; i < mediaPriorities.length; i++) {
- let mediaName = mediaPriorities[i];
- let query = this.get('constants.MEDIA')[mediaName] || media(mediaName);
-
- if (!window.matchMedia(query).matches) {
- continue;
- }
-
- let normalizedName = `${attrName}-${mediaName}`;
- if (get(component, normalizedName)) {
- return get(component, normalizedName);
- }
- }
-
- // fallback on unprefixed
- return get(component, attrName);
+ _mediaDidChange(mediaName, matches) {
+ this.set(mediaName, matches);
+ run.debounce(this, this._updateCurrentMedia, 50);
},
- _getTileStyle(position, spans, colCount, rowCount, gutter, rowMode, rowHeight) {
-
- // Percent of the available horizontal space that one column takes up.
- let hShare = (1 / colCount) * 100;
-
- // Fraction of the gutter size that each column takes up.
- let hGutterShare = (colCount - 1) / colCount;
-
- // Base horizontal size of a column.
- let hUnit = unitCSS({ share: hShare, gutterShare: hGutterShare, gutter });
-
- // The width and horizontal position of each tile is always calculated the same way, but the
- // height and vertical position depends on the rowMode.
- let style = {
- left: positionCSS({ unit: hUnit, offset: position.col, gutter }),
- width: dimensionCSS({ unit: hUnit, span: spans.col, gutter }),
- // resets
- paddingTop: '',
- marginTop: '',
- top: '',
- height: ''
- };
-
- let vShare, vUnit;
-
- switch (rowMode) {
- case 'fixed': {
- // In fixed mode, simply use the given rowHeight.
- style.top = positionCSS({ unit: rowHeight, offset: position.row, gutter });
- style.height = dimensionCSS({ unit: rowHeight, span: spans.row, gutter });
- break;
- }
- case 'ratio': {
- // Percent of the available vertical space that one row takes up. Here, rowHeight holds
- // the ratio value. For example, if the width:height ratio is 4:3, rowHeight = 1.333.
- vShare = hShare / rowHeight;
-
- // Base veritcal size of a row.
- vUnit = unitCSS({ share: vShare, gutterShare: hGutterShare, gutter });
-
- // padidngTop and marginTop are used to maintain the given aspect ratio, as
- // a percentage-based value for these properties is applied to the *width* of the
- // containing block. See http://www.w3.org/TR/CSS2/box.html#margin-properties
- style.paddingTop = dimensionCSS({ unit: vUnit, span: spans.row, gutter });
- style.marginTop = positionCSS({ unit: vUnit, offset: position.row, gutter });
- break;
- }
- case 'fit': {
- // Fraction of the gutter size that each column takes up.
- let vGutterShare = (rowCount - 1) / rowCount;
-
- // Percent of the available vertical space that one row takes up.
- vShare = (1 / rowCount) * 100;
-
- // Base vertical size of a row.
- vUnit = unitCSS({ share: vShare, gutterShare: vGutterShare, gutter });
-
- style.top = positionCSS({ unit: vUnit, offset: position.row, gutter });
- style.height = dimensionCSS({ unit: vUnit, span: spans.row, gutter });
- break;
- }
- }
-
- return style;
+ _updateCurrentMedia() {
+ let mediaPriorities = this.get('constants.MEDIA_PRIORITY');
+ let currentMedia = mediaPriorities.filter((mediaName) => {
+ return this.get(mediaName);
+ });
+ this.set('currentMedia', currentMedia);
+ this.updateGrid();
+ },
+ updateGrid() {
+ this.$().css(this._gridStyle());
+ this.get('tiles').forEach((tile) => {
+ tile.$().css(tile._tileStyle());
+ });
},
- _getGridStyle(colCount, rowCount, gutter, rowMode, rowHeight) {
+ _gridStyle() {
+ this._setTileLayout();
+
let style = {};
+ let colCount = this.get('currentCols');
+ let gutter = this.get('currentGutter');
+ let rowHeight = this.get('currentRowHeight');
+ let rowMode = this.get('currentRowMode');
+ let rowCount = this.get('rowCount');
switch (rowMode) {
case 'fixed': {
@@ -248,30 +140,74 @@ export default Component.extend({
return style;
},
- _getTileSpans(tileElements) {
- return [].map.call(tileElements, (ele) => {
- return {
- row: parseInt(this._getResponsiveAttribute(ele, 'md-rowspan'), 10) || 1,
- col: parseInt(this._getResponsiveAttribute(ele, 'md-colspan'), 10) || 1
- };
+ // Calculates tile positions
+ _setTileLayout() {
+ let tiles = this.get('tiles');
+ let layoutInfo = gridLayout(this.get('currentCols'), tiles);
+
+ tiles.forEach((tile, i) => {
+ tile.set('position', layoutInfo.positions[i]);
});
+
+ this.set('rowCount', layoutInfo.rowCount);
},
- _getColumnCount() {
- let colCount = parseInt(this._getResponsiveAttribute(this, 'md-cols'), 10);
- if (isNaN(colCount)) {
- throw 'md-grid-list: md-cols attribute was not found, or contained a non-numeric value';
+ // Parses attribute string and returns hash of media sizes
+ _extractResponsiveSizes(string, regex = mediaRegex) {
+ let matches = {};
+ let match;
+ while ((match = regex.exec(string))) {
+ if (match[2]) {
+ matches[match[2].slice(0, -1)] = match[3];
+ } else {
+ matches.base = match[3];
+ }
}
- return colCount;
+ return matches;
},
- _getGutter() {
- return this._applyDefaultUnit(this._getResponsiveAttribute(this, 'md-gutter') || 1);
+ // Gets attribute for current media
+ _getAttributeForMedia(sizes, currentMedia) {
+ for (let i = 0; i < currentMedia.length; i++) {
+ if (sizes[currentMedia[i]]) {
+ return sizes[currentMedia[i]];
+ }
+ }
+ return sizes.base;
},
- _getRowHeight() {
- let rowHeight = this._getResponsiveAttribute(this, 'md-row-height');
- switch (this._getRowMode()) {
+ colsMedia: computed('cols', function() {
+ let sizes = this._extractResponsiveSizes(this.get('cols'));
+ if (Object.keys(sizes).length === 0) {
+ throw new Error('md-grid-list: No valid cols found');
+ }
+ return sizes;
+ }),
+
+ currentCols: computed('colsMedia', 'currentMedia.[]', function() {
+ return this._getAttributeForMedia(this.get('colsMedia'), this.get('currentMedia')) || 1;
+ }),
+
+ gutterMedia: computed('gutter', function() {
+ return this._extractResponsiveSizes(this.get('gutter'), rowHeightRegex);
+ }),
+
+ currentGutter: computed('gutterMedia', 'currentMedia.[]', function() {
+ return this._applyDefaultUnit(this._getAttributeForMedia(this.get('gutterMedia'), this.get('currentMedia')) || 1);
+ }),
+
+ rowHeightMedia: computed('rowHeight', function() {
+ let rowHeights = this._extractResponsiveSizes(this.get('rowHeight'), rowHeightRegex);
+ if (Object.keys(rowHeights).length === 0) {
+ throw new Error('md-grid-list: No valid rowHeight found');
+ }
+ return rowHeights;
+ }),
+
+ currentRowHeight: computed('rowHeightMedia', 'currentMedia.[]', function() {
+ let rowHeight = this._getAttributeForMedia(this.get('rowHeightMedia'), this.get('currentMedia'));
+ this.set('currentRowMode', this._getRowMode(rowHeight));
+ switch (this._getRowMode(rowHeight)) {
case 'fixed': {
return this._applyDefaultUnit(rowHeight);
}
@@ -283,10 +219,9 @@ export default Component.extend({
return 0;
}
}
- },
+ }),
- _getRowMode() {
- let rowHeight = this._getResponsiveAttribute(this, 'md-row-height');
+ _getRowMode(rowHeight) {
if (rowHeight === 'fit') {
return 'fit';
} else if (rowHeight.indexOf(':') !== -1) {
@@ -296,57 +231,8 @@ export default Component.extend({
}
},
- _layoutDelegate(tilesInvalidated) {
- let tiles = this.get('tiles');
- let props = {
- tileSpans: this._getTileSpans(tiles),
- colCount: this._getColumnCount(),
- rowMode: this._getRowMode(),
- rowHeight: this._getRowHeight(),
- gutter: this._getGutter()
- };
-
- if (!tilesInvalidated && isEqual(props, this.get('lastLayoutProps'))) {
- return;
- }
-
- gridLayout(props.colCount, props.tileSpans, tiles)
- .map((tilePositions, rowCount) => {
- return {
- grid: {
- element: this.$(),
- style: this._getGridStyle(props.colCount, rowCount, props.gutter, props.rowMode, props.rowHeight)
- },
- tiles: tilePositions.map((ps, i) => {
- return {
- element: tiles[i].$(),
- style: this._getTileStyle(ps.position, ps.spans, props.colCount, rowCount, props.gutter, props.rowMode, props.rowHeight)
- };
- })
- };
- })
- .reflow();
-
- this.set('lastLayoutProps', props);
-
- },
-
_applyDefaultUnit(val) {
return /\D$/.test(val) ? val : `${val}px`;
- },
-
- actions: {
- invalidateTiles() {
- this.set('tilesInvalidated', true);
- this.send('invalidateLayout');
- },
-
- invalidateLayout() {
- if (this.get('layoutInvalidated') || this.get('isDestroyed') || this.get('isDestroying')) {
- return;
- }
- this.set('layoutInvalidated', true);
- run.next(this, this.doLayout);
- }
}
+
});
diff --git a/addon/components/paper-grid-tile.js b/addon/components/paper-grid-tile.js
index a62b52882..8cb6ad046 100644
--- a/addon/components/paper-grid-tile.js
+++ b/addon/components/paper-grid-tile.js
@@ -3,69 +3,132 @@
*/
import Ember from 'ember';
import layout from '../templates/components/paper-grid-tile';
-import PaperGridList from './paper-grid-list';
+import { ChildMixin } from 'ember-composability-tools';
-const { Component, computed, inject, get } = Ember;
+const { Component, computed, run } = Ember;
+
+const positionCSS = (positions) => {
+ return `calc((${positions.unit} + ${positions.gutter}) * ${positions.offset})`;
+};
+
+const dimensionCSS = (dimensions) => {
+ return `calc((${dimensions.unit}) * ${dimensions.span} + (${dimensions.span} - 1) * ${dimensions.gutter})`;
+};
+
+const unitCSS = (units) => {
+ return `${units.share}% - (${units.gutter} * ${units.gutterShare})`;
+};
/**
* @class PaperGridTile
* @extends Ember.Component
*/
-export default Component.extend({
+export default Component.extend(ChildMixin, {
layout,
tagName: 'md-grid-tile',
- constants: inject.service(),
+ gridList: computed.alias('parentComponent'),
- didInsertElement() {
+ didUpdateAttrs() {
this._super(...arguments);
-
- this.get('gridList').registerGridTile(this);
- this.get('gridList').send('invalidateTiles');
-
- this._watchResponsiveAttributes(['md-colspan', 'md-rowspan'], (mediaName) => {
- this.get('gridList').send('invalidateLayout', mediaName);
- });
+ this.updateTile();
},
- willDestroyElement() {
- this._super(...arguments);
-
- this.get('gridList').send('invalidateLayout');
+ updateTile() {
+ let gridList = this.get('gridList');
+ run.debounce(gridList, gridList.updateGrid, 50);
},
- gridList: computed(function() {
- return this.nearestOfType(PaperGridList);
+ colspanMedia: computed('colspan', function() {
+ return this.get('gridList')._extractResponsiveSizes(this.get('colspan'));
}),
- _watchResponsiveAttributes(attrNames, watchFn) {
+ currentColspan: computed('colspanMedia', 'gridList.currentMedia.[]', function() {
+ let colspan = this.get('gridList')._getAttributeForMedia(this.get('colspanMedia'), this.get('gridList.currentMedia'));
+ return parseInt(colspan, 10) || 1;
+ }),
- let checkObserverValues = (sender, key) => {
- let oldValue = this.get(`old${key}`);
- let newValue = sender.get(key);
+ rowspanMedia: computed('rowspan', function() {
+ return this.get('gridList')._extractResponsiveSizes(this.get('rowspan'));
+ }),
- if (oldValue !== newValue) {
- watchFn();
- }
+ currentRowspan: computed('rowspanMedia', 'gridList.currentMedia.[]', function() {
+ let rowspan = this.get('gridList')._getAttributeForMedia(this.get('rowspanMedia'), this.get('gridList.currentMedia'));
+ return parseInt(rowspan, 10) || 1;
+ }),
+
+ _tileStyle() {
+ let position = this.get('position');
+ let currentColspan = this.get('currentColspan');
+ let currentRowspan = this.get('currentRowspan');
+ let rowCount = this.get('gridList.rowCount');
+ let colCount = this.get('gridList.currentCols');
+ let gutter = this.get('gridList.currentGutter');
+ let rowMode = this.get('gridList.currentRowMode');
+ let rowHeight = this.get('gridList.currentRowHeight');
+
+ // Percent of the available horizontal space that one column takes up.
+ let hShare = (1 / colCount) * 100;
+
+ // Fraction of the gutter size that each column takes up.
+ let hGutterShare = (colCount - 1) / colCount;
+
+ // Base horizontal size of a column.
+ let hUnit = unitCSS({ share: hShare, gutterShare: hGutterShare, gutter });
+
+ // The width and horizontal position of each tile is always calculated the same way, but the
+ // height and vertical position depends on the rowMode.
+ let style = {
+ left: positionCSS({ unit: hUnit, offset: position.col, gutter }),
+ width: dimensionCSS({ unit: hUnit, span: currentColspan, gutter }),
+ // resets
+ paddingTop: '',
+ marginTop: '',
+ top: '',
+ height: ''
};
- attrNames.forEach((attrName) => {
- if (get(this, attrName)) {
- this.set(`old${attrName}`, get(this, attrName));
+ let vShare, vUnit;
- this.addObserver(attrName, checkObserverValues);
+ switch (rowMode) {
+ case 'fixed': {
+ // In fixed mode, simply use the given rowHeight.
+ style.top = positionCSS({ unit: rowHeight, offset: position.row, gutter });
+ style.height = dimensionCSS({ unit: rowHeight, span: currentRowspan, gutter });
+ break;
+ }
+ case 'ratio': {
+ // Percent of the available vertical space that one row takes up. Here, rowHeight holds
+ // the ratio value. For example, if the width:height ratio is 4:3, rowHeight = 1.333.
+ vShare = hShare / rowHeight;
+
+ // Base veritcal size of a row.
+ vUnit = unitCSS({ share: vShare, gutterShare: hGutterShare, gutter });
+
+ // paddingTop and marginTop are used to maintain the given aspect ratio, as
+ // a percentage-based value for these properties is applied to the *width* of the
+ // containing block. See http://www.w3.org/TR/CSS2/box.html#margin-properties
+ style.paddingTop = dimensionCSS({ unit: vUnit, span: currentRowspan, gutter });
+ style.marginTop = positionCSS({ unit: vUnit, offset: position.row, gutter });
+ break;
}
+ case 'fit': {
+ // Fraction of the gutter size that each column takes up.
+ let vGutterShare = (rowCount - 1) / rowCount;
+
+ // Percent of the available vertical space that one row takes up.
+ vShare = (1 / rowCount) * 100;
- for (let mediaName in this.get('constants.MEDIA')) {
- let normalizedName = `${attrName}-${mediaName}`;
- if (get(this, normalizedName)) {
- this.set(`old${normalizedName}`, get(this, normalizedName));
+ // Base vertical size of a row.
+ vUnit = unitCSS({ share: vShare, gutterShare: vGutterShare, gutter });
- this.addObserver(normalizedName, checkObserverValues);
- }
+ style.top = positionCSS({ unit: vUnit, offset: position.row, gutter });
+ style.height = dimensionCSS({ unit: vUnit, span: currentRowspan, gutter });
+ break;
}
+ }
- });
+ return style;
}
});
diff --git a/addon/templates/components/paper-grid-list.hbs b/addon/templates/components/paper-grid-list.hbs
new file mode 100644
index 000000000..63970837c
--- /dev/null
+++ b/addon/templates/components/paper-grid-list.hbs
@@ -0,0 +1,3 @@
+{{yield (hash
+ tile=(component 'paper-grid-tile')
+)}}
diff --git a/addon/templates/components/paper-grid-tile.hbs b/addon/templates/components/paper-grid-tile.hbs
index d4136f002..c365538f6 100644
--- a/addon/templates/components/paper-grid-tile.hbs
+++ b/addon/templates/components/paper-grid-tile.hbs
@@ -1,3 +1,5 @@
- {{yield}}
+ {{yield (hash
+ footer=(component 'paper-grid-tile-footer')
+ )}}
diff --git a/addon/utils/grid-layout.js b/addon/utils/grid-layout.js
index 307b6e4a4..3cff959b2 100644
--- a/addon/utils/grid-layout.js
+++ b/addon/utils/grid-layout.js
@@ -1,79 +1,12 @@
/**
* @module ember-paper
*/
-let defaultAnimator = GridTileAnimator;
/**
* Publish layout function
*/
-function GridLayout(colCount, tileSpans) {
- let self, layoutInfo, gridStyles, layoutTime, mapTime, reflowTime;
-
- layoutInfo = calculateGridfor(colCount, tileSpans);
-
- return self = {
-
- /*
- * An array of objects describing each tile's position in the grid.
- */
- layoutInfo() {
- return layoutInfo;
- },
-
- /*
- * Maps grid positioning to an element and a set of styles using the
- * provided updateFn.
- */
- map(updateFn) {
- let info = self.layoutInfo();
- gridStyles = updateFn(info.positioning, info.rowCount);
-
- return self;
- },
-
- /*
- * Default animator simply sets the element.css( ). An alternate
- * animator can be provided as an argument. The function has the following
- * signature:
- *
- * function({grid: {element: JQLite, style: Object}, tiles: Array<{element: JQLite, style: Object}>)
- */
- reflow(animatorFn) {
- let animator = animatorFn || defaultAnimator;
- animator(gridStyles.grid, gridStyles.tiles);
- return self;
- },
-
- /*
- * Timing for the most recent layout run.
- */
- performance() {
- return {
- tileCount: tileSpans.length,
- layoutTime,
- mapTime,
- reflowTime,
- totalTime: layoutTime + mapTime + reflowTime
- };
- }
- };
-}
-
-/*
- * Default Gridlist animator simple sets the css for each element;
- * NOTE: any transitions effects must be manually set in the CSS.
- * e.g.
- *
- * md-grid-tile {
- * transition: all 700ms ease-out 50ms;
- * }
- *
- */
-function GridTileAnimator(grid, tiles) {
- grid.element.css(grid.style);
- tiles.forEach(function(t) {
- t.element.css(t.style);
- });
+function GridLayout(colCount, tiles) {
+ return calculateGridfor(colCount, tiles);
}
/*
@@ -92,26 +25,23 @@ function GridTileAnimator(grid, tiles) {
* tile, spaceTracker's elements are each decremented by 1 to a minimum
* of 0. Rows are searched in this fashion until space is found.
*/
-function calculateGridfor(colCount, tileSpans) {
+function calculateGridfor(colCount, tiles) {
let curCol = 0;
let curRow = 0;
let spaceTracker = newSpaceTracker();
return {
- positioning: tileSpans.map(function(spans, i) {
- return {
- spans,
- position: reserveSpace(spans, i)
- };
+ positions: tiles.map(function(tile, i) {
+ return reserveSpace(tile, i);
}),
rowCount: curRow + Math.max(...spaceTracker)
};
- function reserveSpace(spans, i) {
- if (spans.col > colCount) {
- throw `md-grid-list: Tile at position ${i} has a colspan
- (${spans.col}) that exceeds the column count
- (${colCount})`;
+ function reserveSpace(tile, i) {
+ let colspan = tile.get('currentColspan');
+ let rowspan = tile.get('currentRowspan');
+ if (colspan > colCount) {
+ throw new Error(`md-grid-list: Tile at position ${i} has a colspan (${colspan}) that exceeds the column count (${colCount})`);
}
let start = 0;
@@ -122,7 +52,7 @@ function calculateGridfor(colCount, tileSpans) {
// this, recognize that you've iterated across an entire row looking for
// space, and if so fast-forward by the minimum rowSpan count. Repeat
// until the required space opens up.
- while (end - start < spans.col) {
+ while (end - start < colspan) {
if (curCol >= colCount) {
nextRow();
continue;
@@ -138,8 +68,8 @@ function calculateGridfor(colCount, tileSpans) {
curCol = end + 1;
}
- adjustRow(start, spans.col, spans.row);
- curCol = start + spans.col;
+ adjustRow(start, colspan, rowspan);
+ curCol = start + colspan;
return {
col: start,
diff --git a/tests/dummy/app/controllers/demo/grid-list.js b/tests/dummy/app/controllers/demo/grid-list.js
index 93f8b0006..8189324e9 100644
--- a/tests/dummy/app/controllers/demo/grid-list.js
+++ b/tests/dummy/app/controllers/demo/grid-list.js
@@ -68,11 +68,11 @@ function randomColor() {
function randomSpan() {
let r = Math.random();
if (r < 0.8) {
- return 1;
+ return 'gt-sm-1';
} else if (r < 0.9) {
- return 2;
+ return 'gt-sm-2';
} else {
- return 3;
+ return 'gt-sm-3';
}
}
diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs
index 27b9cf635..d8c29dfa8 100644
--- a/tests/dummy/app/templates/application.hbs
+++ b/tests/dummy/app/templates/application.hbs
@@ -24,7 +24,7 @@
{{#submenu-item active=(is-active "demo.chips" currentRouteName) onClick=(transition-to "demo.chips")}}Chips{{/submenu-item}}
{{#submenu-item active=(is-active "demo.dialog" currentRouteName) onClick=(transition-to "demo.dialog")}}Dialog{{/submenu-item}}
{{#submenu-item active=(is-active "demo.divider" currentRouteName) onClick=(transition-to "demo.divider")}}Divider{{/submenu-item}}
- {{#submenu-item active=(is-active "demo.grid-list" currentRouteName) onClick=(transition-to "demo.grid-list")}}Grid List {{paper-icon "warning" title="Not updated yet."}}{{/submenu-item}}
+ {{#submenu-item active=(is-active "demo.grid-list" currentRouteName) onClick=(transition-to "demo.grid-list")}}Grid List{{/submenu-item}}
{{#submenu-item active=(is-active "demo.icons" currentRouteName) onClick=(transition-to "demo.icons")}}Icons{{/submenu-item}}
{{#submenu-item active=(is-active "demo.input" currentRouteName) onClick=(transition-to "demo.input")}}Input{{/submenu-item}}
{{#submenu-item active=(is-active "demo.list" currentRouteName) onClick=(transition-to "demo.list")}}List{{/submenu-item}}
diff --git a/tests/dummy/app/templates/demo/grid-list.hbs b/tests/dummy/app/templates/demo/grid-list.hbs
index a6fd40310..a9466aa89 100644
--- a/tests/dummy/app/templates/demo/grid-list.hbs
+++ b/tests/dummy/app/templates/demo/grid-list.hbs
@@ -1,49 +1,50 @@
-{{page-toolbar pageTitle="Grid List" isDemo=true isWIP=true}}
+{{page-toolbar pageTitle="Grid List" isDemo=true}}
{{#doc-content}}
Basic Usage
{{! BEGIN-SNIPPET grid-list.basic-usage }}
{{#paper-content class="md-whiteframe-z1 grid-list-demo1"}}
- {{#paper-grid-list md-cols-sm="1" md-cols-md="2" md-cols-gt-md="6"
- md-row-height-gt-md="1:1" md-row-height="2:2"
- md-gutter="12px" md-gutter-gt-sm="8px"}}
-
- {{#paper-grid-tile class="gray" md-rowspan="3" md-colspan="2" md-colspan-sm="1"}}
- {{#paper-grid-tile-footer}}
- #1: (3r x 2c)
- {{/paper-grid-tile-footer}}
- {{/paper-grid-tile}}
-
- {{#paper-grid-tile class="green"}}
- {{#paper-grid-tile-footer}}
- #2: (1r x 1c)
- {{/paper-grid-tile-footer}}
- {{/paper-grid-tile}}
-
- {{#paper-grid-tile class="yellow"}}
- {{#paper-grid-tile-footer}}
- #3: (1r x 1c)
- {{/paper-grid-tile-footer}}
- {{/paper-grid-tile}}
-
- {{#paper-grid-tile class="blue" md-rowspan="2"}}
- {{#paper-grid-tile-footer}}
+ {{#paper-grid-list
+ cols="1 md-2 gt-md-6"
+ rowHeight="gt-md-1:1 2:2"
+ gutter="12px gt-sm-8px" as |grid|}}
+
+ {{#grid.tile class="gray" rowspan="2" colspan="sm-1 gt-sm-2" as |tile|}}
+ {{#tile.footer}}
+ #1: (3r x 2c)
+ {{/tile.footer}}
+ {{/grid.tile}}
+
+ {{#grid.tile class="green" as |tile|}}
+ {{#tile.footer}}
+ #2: (1r x 1c)
+ {{/tile.footer}}
+ {{/grid.tile}}
+
+ {{#grid.tile class="yellow" as |tile|}}
+ {{#tile.footer}}
+ #3: (1r x 1c)
+ {{/tile.footer}}
+ {{/grid.tile}}
+
+ {{#grid.tile class="blue" rowspan="2" as |tile|}}
+ {{#tile.footer}}
#4: (2r x 1c)
- {{/paper-grid-tile-footer}}
- {{/paper-grid-tile}}
+ {{/tile.footer}}
+ {{/grid.tile}}
- {{#paper-grid-tile class="red" md-rowspan="2" md-colspan="2" md-colspan-sm="1"}}
- {{#paper-grid-tile-footer}}
- #5: (2r x 2c)
- {{/paper-grid-tile-footer}}
- {{/paper-grid-tile}}
+ {{#grid.tile class="red" rowspan="2" colspan="sm-1 gt-sm-2" as |tile|}}
+ {{#tile.footer}}
+ #5: (2r x 2c)
+ {{/tile.footer}}
+ {{/grid.tile}}
- {{#paper-grid-tile class="green" md-rowspan="2"}}
- {{#paper-grid-tile-footer}}
- #6: (2r x 1c)
- {{/paper-grid-tile-footer}}
- {{/paper-grid-tile}}
+ {{#grid.tile class="green" rowspan="2" as |tile|}}
+ {{#tile.footer}}
+ #6: (2r x 1c)
+ {{/tile.footer}}
+ {{/grid.tile}}
{{/paper-grid-list}}
@@ -56,17 +57,18 @@
Dynamic Tiles
{{! BEGIN-SNIPPET grid-list.dynamic-tiles }}
{{#paper-content class="md-whiteframe-z1 grid-list-demo-dynamicTiles"}}
- {{#paper-grid-list md-cols-sm="1" md-cols-md="2" md-cols-gt-md="6"
- md-row-height-gt-md="1:1" md-row-height="4:3"
- md-gutter="8px" md-gutter-gt-sm="4px"}}
+ {{#paper-grid-list
+ cols="sm-2 md-4 gt-md-6"
+ rowHeight="4:3 gt-md-1:1"
+ gutter="8px gt-sm-4px" as |grid|}}
- {{#each tiles as |tile|}}
- {{#paper-grid-tile md-rowspan=tile.span.row md-colspan=tile.span.col md-colspan-sm="1" class=tile.background}}
+ {{#each tiles as |dynamicTile|}}
+ {{#grid.tile rowspan=dynamicTile.span.row colspan=(concat dynamicTile.span.col ' xs-1') class=dynamicTile.background as |tile|}}
- {{#paper-grid-tile-footer}}
- {{tile.title}}
- {{/paper-grid-tile-footer}}
- {{/paper-grid-tile}}
+ {{#tile.footer}}
+ {{dynamicTile.title}}
+ {{/tile.footer}}
+ {{/grid.tile}}
{{/each}}
{{/paper-grid-list}}
@@ -80,14 +82,15 @@
{{! BEGIN-SNIPPET grid-list.responsive }}
{{#paper-content class="md-whiteframe-z1 grid-list-demo-responsiveTiles" layout-padding=''}}
- {{#paper-grid-list md-cols-gt-md="12" md-cols-sm="3" md-cols-md="8"
- md-row-height-gt-md="1:1" md-row-height="4:3"
- md-gutter-gt-md="16px" md-gutter-gt-sm="8px" md-gutter="4px"}}
+ {{#paper-grid-list
+ cols="3 md-8 gt-md-12"
+ rowHeight="4:3 gt-md-1:1"
+ gutter="4px md-8px gt-md-16px" as |grid|}}
{{#each colorTiles as |tile|}}
- {{#paper-grid-tile md-colspan-gt-sm=tile.colspan md-rowspan-gt-sm=tile.rowspan class=tile.style}}
- {{/paper-grid-tile}}
+ {{#grid.tile colspan=tile.colspan rowspan=tile.rowspan class=tile.style}}
+ {{/grid.tile}}
{{/each}}
{{/paper-grid-list}}
diff --git a/tests/integration/components/paper-grid-list-test.js b/tests/integration/components/paper-grid-list-test.js
new file mode 100644
index 000000000..341a745ef
--- /dev/null
+++ b/tests/integration/components/paper-grid-list-test.js
@@ -0,0 +1,37 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+import wait from 'ember-test-helpers/wait';
+
+moduleForComponent('paper-grid-list', 'Integration | Component | paper grid list', {
+ integration: true
+});
+
+test('it renders tiles with tag name', function(assert) {
+ assert.expect(1);
+
+ this.render(hbs`
+ {{#paper-grid-list cols="1" rowHeight="4:3" as |grid|}}
+ {{#grid.tile}}
+ {{/grid.tile}}
+ {{/paper-grid-list}}
+ `);
+ return wait().then(() => {
+ assert.equal(this.$('md-grid-tile').length, 1);
+ });
+});
+
+test('it renders tiles with footer', function(assert) {
+ assert.expect(1);
+
+ this.render(hbs`
+ {{#paper-grid-list cols="1" rowHeight="4:3" as |grid|}}
+ {{#grid.tile as |tile|}}
+ {{#tile.footer}}
+ {{/tile.footer}}
+ {{/grid.tile}}
+ {{/paper-grid-list}}
+ `);
+ return wait().then(() => {
+ assert.equal(this.$('md-grid-tile-footer').length, 1);
+ });
+});