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); + }); +});