diff --git a/addon/components/paper-grid-list.js b/addon/components/paper-grid-list.js index 4fd24ef17..ec0000753 100644 --- a/addon/components/paper-grid-list.js +++ b/addon/components/paper-grid-list.js @@ -47,9 +47,11 @@ export default Component.extend(ParentMixin, { this._installMediaListener(); }, - didUpdateAttrs() { + didUpdate() { this._super(...arguments); - this.updateGrid(); + + // Debounces until the next run loop + run.debounce(this, this.updateGrid, 0); }, willDestroyElement() { @@ -66,13 +68,14 @@ export default Component.extend(ParentMixin, { // 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); + // Trigger initial grid calculations + this._mediaDidChange(mediaName, mediaList.matches); mediaList.addListener(this[listenerName]); } @@ -88,23 +91,23 @@ export default Component.extend(ParentMixin, { _mediaDidChange(mediaName, matches) { this.set(mediaName, matches); - run.debounce(this, this._updateCurrentMedia, 50); + + // Debounces until the next run loop + run.debounce(this, this._updateCurrentMedia, 0); }, _updateCurrentMedia() { let mediaPriorities = this.get('constants.MEDIA_PRIORITY'); - let currentMedia = mediaPriorities.filter((mediaName) => { - return this.get(mediaName); - }); + let currentMedia = mediaPriorities.filter((mediaName) => this.get(mediaName)); this.set('currentMedia', currentMedia); this.updateGrid(); }, + // Updates styles and triggers onUpdate callbacks updateGrid() { this.$().css(this._gridStyle()); - this.get('tiles').forEach((tile) => { - tile.$().css(tile._tileStyle()); - }); + this.get('tiles').forEach((tile) => tile.updateTile()); + this.sendAction('onUpdate'); }, _gridStyle() { @@ -135,7 +138,8 @@ export default Component.extend(ParentMixin, { break; } case 'fit': { - // noop, as the height is user set + // rowHeight is container height + style.height = '100%'; break; } } @@ -145,16 +149,22 @@ export default Component.extend(ParentMixin, { // Calculates tile positions _setTileLayout() { - let tiles = this.get('tiles'); + let tiles = this.orderedTiles(); let layoutInfo = gridLayout(this.get('currentCols'), tiles); - tiles.forEach((tile, i) => { - tile.set('position', layoutInfo.positions[i]); - }); + tiles.forEach((tile, i) => tile.set('position', layoutInfo.positions[i])); this.set('rowCount', layoutInfo.rowCount); }, + // Sorts tiles by their order in the dom + orderedTiles() { + let domTiles = this.$('md-grid-tile').toArray(); + return this.get('tiles').sort((a, b) => { + return domTiles.indexOf(a.get('element')) > domTiles.indexOf(b.get('element')) ? 1 : -1; + }); + }, + // Parses attribute string and returns hash of media sizes _extractResponsiveSizes(string, regex = mediaRegex) { let matches = {}; diff --git a/addon/components/paper-grid-tile.js b/addon/components/paper-grid-tile.js index 192951ccd..5663d84e9 100644 --- a/addon/components/paper-grid-tile.js +++ b/addon/components/paper-grid-tile.js @@ -33,12 +33,15 @@ export default Component.extend(ChildMixin, { didUpdateAttrs() { this._super(...arguments); - this.updateTile(); + let gridList = this.get('gridList'); + + // Debounces until the next run loop + run.debounce(gridList, gridList.updateGrid, 0); }, updateTile() { - let gridList = this.get('gridList'); - run.debounce(gridList, gridList.updateGrid, 50); + this.$().css(this._tileStyle()); + this.sendAction('onUpdate'); }, colspanMedia: computed('colspan', function() { diff --git a/tests/dummy/app/templates/demo/grid-list.hbs b/tests/dummy/app/templates/demo/grid-list.hbs index a9466aa89..6db7b300e 100644 --- a/tests/dummy/app/templates/demo/grid-list.hbs +++ b/tests/dummy/app/templates/demo/grid-list.hbs @@ -80,7 +80,7 @@

Responsive and Animated Usage

{{! BEGIN-SNIPPET grid-list.responsive }} - {{#paper-content class="md-whiteframe-z1 grid-list-demo-responsiveTiles" layout-padding=''}} + {{#paper-content class="md-whiteframe-z1 grid-list-demo-responsiveTiles"}} {{#paper-grid-list cols="3 md-8 gt-md-12" diff --git a/tests/integration/components/paper-grid-list-test.js b/tests/integration/components/paper-grid-list-test.js index 21e9b6c5d..41f76acde 100644 --- a/tests/integration/components/paper-grid-list-test.js +++ b/tests/integration/components/paper-grid-list-test.js @@ -1,28 +1,69 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; -import { render, settled } from '@ember/test-helpers'; +import { render, find, findAll, waitUntil } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; +import { run } from '@ember/runloop'; +import { A } from "@ember/array" + +function getStyle(selector, property) { + let el = find(selector); + let style = getComputedStyle(el); + return style.getPropertyValue(property); +} + +function tilePosition(selector) { + let left = getStyle(selector, 'left'); + + switch (left) { + case '0px': + return 1; + case '50px': + return 2; + case '100px': + return 3; + case '150px': + return 4; + default: + return 'grid sizing wrong: grid not ready yet?'; + } +} + +function tileRow(selector) { + let marginTop = getStyle(selector, 'margin-top'); + + switch (marginTop) { + case '0px': + return 1; + case '150.25px': + return 2; + case '300.5px': + return 3; + default: + return 'grid sizing wrong: grid not ready yet?'; + } +} + +function createTiles() { + return A(['ONE', 'TWO', 'THREE']); +} module('Integration | Component | paper grid list', function(hooks) { setupRenderingTest(hooks); test('it renders tiles with tag name', async function(assert) { assert.expect(1); - await render(hbs` {{#paper-grid-list cols="1" rowHeight="4:3" as |grid|}} {{#grid.tile}} {{/grid.tile}} {{/paper-grid-list}} `); - return settled().then(() => { - assert.equal(this.$('md-grid-tile').length, 1); - }); + + assert.equal(this.$('md-grid-tile').length, 1); }); test('it renders tiles with footer', async function(assert) { assert.expect(1); - await render(hbs` {{#paper-grid-list cols="1" rowHeight="4:3" as |grid|}} {{#grid.tile as |tile|}} @@ -31,8 +72,243 @@ module('Integration | Component | paper grid list', function(hooks) { {{/grid.tile}} {{/paper-grid-list}} `); - return settled().then(() => { - assert.equal(this.$('md-grid-tile-footer').length, 1); - }); + + assert.equal(findAll('md-grid-tile-footer').length, 1); + }); + + test('it applies a gutter', async function(assert) { + assert.expect(1); + this.set('tiles', createTiles()); + + await render(hbs` +
+ {{#paper-grid-list gutter="20px" cols="3" rowHeight="4:3" as |grid|}} + {{#each tiles as |item|}} + {{#grid.tile class=item as |tile|}} + {{item}} + {{/grid.tile}} + {{/each}} + {{/paper-grid-list}} +
+ `); + + assert.equal(getStyle('.TWO', 'left'), '72.9844px'); + }); + + test('it applies a fixed row height', async function(assert) { + assert.expect(1); + this.set('tiles', createTiles()); + + await render(hbs` +
+ {{#paper-grid-list cols="3" rowHeight="75px" as |grid|}} + {{#each tiles as |item|}} + {{#grid.tile class=item as |tile|}} + {{item}} + {{/grid.tile}} + {{/each}} + {{/paper-grid-list}} +
+ `); + + assert.equal(getStyle('.TWO', 'height'), '75px'); + }); + + test('it applies a row height ratio', async function(assert) { + assert.expect(2); + this.set('tiles', createTiles()); + + await render(hbs` +
+ {{#paper-grid-list cols="2" rowHeight="2:1" as |grid|}} + {{#each tiles as |item|}} + {{#grid.tile class=item as |tile|}} + {{item}} + {{/grid.tile}} + {{/each}} + {{/paper-grid-list}} +
+ `); + + assert.equal(getStyle('.TWO', 'height'), '49.5px'); + assert.equal(getStyle('.TWO', 'width'), '99.5px'); + }); + + test('it applies a row height fit', async function(assert) { + assert.expect(1); + this.set('tiles', createTiles()); + + await render(hbs` +
+ {{#paper-grid-list cols="1" rowHeight="fit" as |grid|}} + {{#each tiles as |item|}} + {{#grid.tile class=item as |tile|}} + {{item}} + {{/grid.tile}} + {{/each}} + {{/paper-grid-list}} +
+ `); + + assert.equal(getStyle('.TWO', 'height').substr(0, 2), '39'); + }); + + test('it applies tile colspan', async function(assert) { + assert.expect(1); + this.set('tiles', createTiles()); + + await render(hbs` +
+ {{#paper-grid-list cols="3" rowHeight="4:3" as |grid|}} + {{#grid.tile colspan="3" class="COLSPAN" as |tile|}} + COLSPAN + {{/grid.tile}} + {{#each tiles as |item|}} + {{#grid.tile class=item as |tile|}} + {{item}} + {{/grid.tile}} + {{/each}} + {{/paper-grid-list}} +
+ `); + + assert.equal(getStyle('.COLSPAN', 'width'), '199px'); + }); + + test('it applies tile rowspan', async function(assert) { + assert.expect(2); + this.set('tiles', createTiles()); + + await render(hbs` +
+ {{#paper-grid-list cols="1" rowHeight="4:3" as |grid|}} + {{#grid.tile rowspan="2" class="ROWSPAN" as |tile|}} + ROWSPAN + {{/grid.tile}} + {{#each tiles as |item|}} + {{#grid.tile class=item as |tile|}} + {{item}} + {{/grid.tile}} + {{/each}} + {{/paper-grid-list}} +
+ `); + + assert.equal(getStyle('.ONE', 'height'), '149.25px'); + assert.equal(getStyle('.ROWSPAN', 'height'), '299.5px'); + }); + + test('it recalculates when cols changes', async function(assert) { + assert.expect(6); + this.set('tiles', createTiles()); + this.set('cols', 1); + + await render(hbs` +
+ {{#paper-grid-list cols=cols rowHeight="4:3" as |grid|}} + {{#each tiles as |item|}} + {{#grid.tile class=item as |tile|}} + {{item}} + {{/grid.tile}} + {{/each}} + {{/paper-grid-list}} +
+ `); + + assert.equal(tileRow('.ONE'), 1); + assert.equal(tileRow('.TWO'), 2); + assert.equal(tileRow('.THREE'), 3); + + this.set('cols', 3); + await waitUntil(() => find('.THREE')); + + assert.equal(tileRow('.ONE'), 1, 'ONE'); + assert.equal(tileRow('.TWO'), 1, 'TWO'); + assert.equal(tileRow('.THREE'), 1, 'THREE'); + }); + + test('it recalculates when tile is added', async function(assert) { + assert.expect(7); + this.set('tiles', createTiles()); + + await render(hbs` +
+ {{#paper-grid-list cols="4" rowHeight="4:3" as |grid|}} + {{#each tiles as |item|}} + {{#grid.tile class=item as |tile|}} + {{item}} + {{/grid.tile}} + {{/each}} + {{/paper-grid-list}} +
+ `); + + assert.equal(tilePosition('.ONE'), 1); + assert.equal(tilePosition('.TWO'), 2); + assert.equal(tilePosition('.THREE'), 3); + + run(() => this.get('tiles').insertAt(2, 'FOUR')); + await waitUntil(() => find('.FOUR')); + + assert.equal(tilePosition('.ONE'), 1, 'ONE'); + assert.equal(tilePosition('.TWO'), 2, 'TWO'); + assert.equal(tilePosition('.FOUR'), 3, 'FOUR'); + assert.equal(tilePosition('.THREE'), 4, 'THREE'); + }); + + test('it recalculates when tile is removed', async function(assert) { + assert.expect(6); + this.set('tiles', createTiles()); + + await render(hbs` +
+ {{#paper-grid-list cols="4" rowHeight="4:3" as |grid|}} + {{#each tiles as |item|}} + {{#grid.tile class=item as |tile|}} + {{item}} + {{/grid.tile}} + {{/each}} + {{/paper-grid-list}} +
+ `); + + assert.equal(tilePosition('.ONE'), 1); + assert.equal(tilePosition('.TWO'), 2); + assert.equal(tilePosition('.THREE'), 3); + + run(() => this.get('tiles').removeAt(1)); + await waitUntil(() => !find('.TWO')); + + assert.equal(find('.TWO'), null); + assert.equal(tilePosition('.ONE'), 1); + assert.equal(tilePosition('.THREE'), 2); + }); + + test('it reorders tiles when dom order changes', async function(assert) { + assert.expect(6); + this.set('tiles', createTiles()); + + await render(hbs` +
+ {{#paper-grid-list cols="4" rowHeight="4:3" as |grid|}} + {{#each tiles as |item|}} + {{#grid.tile class=item as |tile|}} + {{item}} + {{/grid.tile}} + {{/each}} + {{/paper-grid-list}} +
+ `); + + assert.equal(tilePosition('.ONE'), 1); + assert.equal(tilePosition('.TWO'), 2); + assert.equal(tilePosition('.THREE'), 3); + + run(() => this.get('tiles').reverseObjects()); + await waitUntil(() => find('.TWO')); + + assert.equal(tilePosition('.ONE'), 3); + assert.equal(tilePosition('.TWO'), 2); + assert.equal(tilePosition('.THREE'), 1); }); });