Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update grid list #739

Merged
merged 9 commits into from
Jul 19, 2017
362 changes: 124 additions & 238 deletions addon/components/paper-grid-list.js

Large diffs are not rendered by default.

135 changes: 99 additions & 36 deletions addon/components/paper-grid-tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

});
3 changes: 3 additions & 0 deletions addon/templates/components/paper-grid-list.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{yield (hash
tile=(component 'paper-grid-tile')
)}}
4 changes: 3 additions & 1 deletion addon/templates/components/paper-grid-tile.hbs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<figure>
{{yield}}
{{yield (hash
footer=(component 'paper-grid-tile-footer')
)}}
</figure>
96 changes: 13 additions & 83 deletions addon/utils/grid-layout.js
Original file line number Diff line number Diff line change
@@ -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( <styles> ). 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);
}

/*
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions tests/dummy/app/controllers/demo/grid-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
}

Expand Down
2 changes: 1 addition & 1 deletion tests/dummy/app/templates/application.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Expand Down
Loading