Skip to content

Commit

Permalink
fix: consider all headers and footers when calculating column width (#…
Browse files Browse the repository at this point in the history
…2199)

* fix: consider all headers and footers when calculating column width

backporting vaadin/web-components#2964

* fix tests

* ie specific workaround

* fix: consider all headers and footers when calculating column width

backporting vaadin/web-components#2964

* fix tests

* ie specific workaround

Co-authored-by: Hadi Amiri <hadi@vaadin.com>
  • Loading branch information
Farhad and hdamr authored Dec 20, 2021
1 parent 6c54c08 commit bec0993
Show file tree
Hide file tree
Showing 3 changed files with 307 additions and 26 deletions.
14 changes: 14 additions & 0 deletions src/vaadin-grid-drag-and-drop-mixin.html
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,20 @@

/** @private */
__getViewportRows() {
// Workaround an IE11 bug where the `getBoundingClientRect` for header/footer
// might return incorrect values
if (this._ie) {
this.$.header.style.outline = '0px solid transparent';
const scrollerRect = this.$.scroller.getBoundingClientRect();
const headerBottom = Math.max(this.$.header.getBoundingClientRect().bottom, scrollerRect.top);
const footerTop = Math.min(this.$.footer.getBoundingClientRect().top, scrollerRect.bottom);

return Array.from(this.$.items.children).filter((row) => {
const rowRect = row.getBoundingClientRect();
return rowRect.bottom > headerBottom && rowRect.top < footerTop;
});
}

const headerBottom = this.$.header.getBoundingClientRect().bottom;
const footerTop = this.$.footer.getBoundingClientRect().top;
return Array.from(this.$.items.children)
Expand Down
85 changes: 59 additions & 26 deletions src/vaadin-grid.html
Original file line number Diff line number Diff line change
Expand Up @@ -467,37 +467,70 @@
}
}

/** @private */
__getIntrinsicWidth(col) {
const initialWidth = col.width;
const initialFlexGrow = col.flexGrow;

col.width = 'auto';
col.flexGrow = 0;

// Note: _allCells only contains cells which are currently rendered in DOM
const width = col._allCells
.reduce((width, cell) => {
// Add 1px buffer to the offset width to avoid too narrow columns (sub-pixel rendering)
return Math.max(width, cell.offsetWidth + 1);
}, 0);

col.flexGrow = initialFlexGrow;
col.width = initialWidth;

return width;
}

/** @private */
__getDistributedWidth(col, innerColumn) {
if (col == null || col === this) {
return 0;
}

const columnWidth = Math.max(this.__getIntrinsicWidth(col), this.__getDistributedWidth(col.parentElement, col));

// we're processing a regular grid-column and not a grid-column-group
if (!innerColumn) {
return columnWidth;
}

// At the end, the width of each vaadin-grid-column-group is determined by the sum of the width of its children.
// Here we determine how much space the vaadin-grid-column-group actually needs to render properly and then we distribute that space
// to its children, so when we actually do the summation it will be rendered properly.
// Check out vaadin-grid-column-group:_updateFlexAndWidth
const columnGroup = col;
const columnGroupWidth = columnWidth;
const sumOfWidthOfAllChildColumns = columnGroup._visibleChildColumns
.map((col) => this.__getIntrinsicWidth(col))
.reduce((sum, curr) => sum + curr, 0);

const extraNecessarySpaceForGridColumnGroup = Math.max(0, columnGroupWidth - sumOfWidthOfAllChildColumns);

// The distribution of the extra necessary space is done according to the intrinsic width of each child column.
// Lets say we need 100 pixels of extra space for the grid-column-group to render properly
// it has two grid-column children, |100px|300px| in total 400px
// the first column gets 25px of the additional space (100/400)*100 = 25
// the second column gets the 75px of the additional space (300/400)*100 = 75
const proportionOfExtraSpace = this.__getIntrinsicWidth(innerColumn) / sumOfWidthOfAllChildColumns;
const shareOfInnerColumnFromNecessaryExtraSpace = proportionOfExtraSpace * extraNecessarySpaceForGridColumnGroup;

return this.__getIntrinsicWidth(innerColumn) + shareOfInnerColumnFromNecessaryExtraSpace;
}

/**
* @param {!Array<!GridColumnElement>} cols the columns to auto size based on their content width
* @private
*/
_recalculateColumnWidths(cols) {
// Note: The `cols.forEach()` loops below could be implemented as a single loop but this has been
// split for performance reasons to batch these similar actions [write/read] together to avoid
// unnecessary layout trashing.

// [write] Set automatic width for all cells (breaks column alignment)
cols.forEach(col => {
col.width = 'auto';
col._origFlexGrow = col.flexGrow;
col.flexGrow = 0;
});
// [read] Measure max cell width in each column
cols.forEach(col => {
col._currentWidth = 0;
// Note: _allCells only contains cells which are currently rendered in DOM
col._allCells.forEach(c => {
// Add 1px buffer to the offset width to avoid too narrow columns (sub-pixel rendering)
const cellWidth = c.offsetWidth + 1;
col._currentWidth = Math.max(col._currentWidth, cellWidth);
});
});
// [write] Set column widths to fit widest measured content
cols.forEach(col => {
col.width = `${col._currentWidth}px`;
col.flexGrow = col._origFlexGrow;
col._currentWidth = undefined;
col._origFlexGrow = undefined;
cols.forEach((col) => {
col.width = `${this.__getDistributedWidth(col)}px`;
});
}

Expand Down
234 changes: 234 additions & 0 deletions test/column-auto-width.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

<link rel="import" href="helpers.html">
<link rel="import" href="../vaadin-grid.html">
<link rel="import" href="../vaadin-grid-column-group.html">
</head>

<body>
Expand All @@ -36,6 +37,110 @@
</template>
</test-fixture>

<test-fixture id="column-group-1">
<template>
<vaadin-grid style="width: 200px;">
<vaadin-grid-column-group header="a lengthy header that should change the width of the column">
<vaadin-grid-column auto-width path="a" header="small header"></vaadin-grid-column>
</vaadin-grid-column-group>
</vaadin-grid>
</template>
</test-fixture>

<test-fixture id="column-group-2">
<template>
<vaadin-grid style="width: 200px;">
<vaadin-grid-column-group header="a lengthy header that should change the width of the column">
<vaadin-grid-column auto-width path="a" header="small header"></vaadin-grid-column>
<vaadin-grid-column auto-width path="b" header="small header"></vaadin-grid-column>
</vaadin-grid-column-group>
</vaadin-grid>
</template>
</test-fixture>

<test-fixture id="column-group-3">
<template>
<vaadin-grid style="width: 200px;">
<vaadin-grid-column-group header="HeaderHeaderHeaderHeaderHeaderHeaderHeaderHeaderHeaderHeader">
<vaadin-grid-column auto-width path="first"></vaadin-grid-column>
<vaadin-grid-column auto-width path="last"></vaadin-grid-column>
</vaadin-grid-column-group>
</vaadin-grid>
</template>
</test-fixture>

<test-fixture id="column-group-4">
<template>
<vaadin-grid style="width: 200px;">
<vaadin-grid-column-group header="HeaderHeaderHeaderHeaderHeaderHeaderHeaderHeaderHeaderHeader">
<vaadin-grid-column auto-width flex-grow="3" path="first"></vaadin-grid-column>
<vaadin-grid-column auto-width path="last"></vaadin-grid-column>
</vaadin-grid-column-group>
</vaadin-grid>
</template>
</test-fixture>

<test-fixture id="column-group-5">
<template>
<vaadin-grid style="width: 200px;">
<vaadin-grid-column-group header="a lengthy header that should change the width of the column">
<vaadin-grid-column auto-width path="a" header="header"></vaadin-grid-column>
<vaadin-grid-column auto-width path="b" header="headerheader"></vaadin-grid-column>
</vaadin-grid-column-group>
</vaadin-grid>
</template>
</test-fixture>

<test-fixture id="column-group-6">
<template>
<vaadin-grid style="width: 200px;">
<vaadin-grid-column-group header="a lengthy header, greater than immediate column-group">
<vaadin-grid-column-group header="immediate column-group">
<vaadin-grid-column auto-width path="a" header="header"></vaadin-grid-column>
</vaadin-grid-column-group>
</vaadin-grid-column-group>
</vaadin-grid>
</template>
</test-fixture>

<test-fixture id="column-group-7">
<template>
<vaadin-grid style="width: 200px;">
<vaadin-grid-column-group header="small header">
<vaadin-grid-column auto-width path="a" header="a lengthy header that should change the width of the column"></vaadin-grid-column>
</vaadin-grid-column-group>
</vaadin-grid>
</template>
</test-fixture>

<test-fixture id="column-group-8">
<template>
<vaadin-grid style="width: 200px;">
<vaadin-grid-column-group>
<vaadin-grid-column auto-width path="a" header="header"></vaadin-grid-column>
</vaadin-grid-column-group>
</vaadin-grid>
</template>
</test-fixture>

<test-fixture id="column-group-9">
<template>
<vaadin-grid style="width: 200px;">
<vaadin-grid-column-group>
<vaadin-grid-column auto-width path="a" header="header"></vaadin-grid-column>
</vaadin-grid-column-group>
</vaadin-grid>
</template>
</test-fixture>

<test-fixture id="column-group-10">
<template>
<vaadin-grid style="width: 200px;">
<vaadin-grid-column auto-width path="a" header="header"></vaadin-grid-column>
</vaadin-grid>
</template>
</test-fixture>

<script>
describe('column auto-width', function() {
let grid;
Expand Down Expand Up @@ -220,6 +325,135 @@
expect(grid._recalculateColumnWidths.called).to.be.true;
});
});

describe('column group', () => {
const num = (str) => parseInt(str, 10);

function expectColumnsWidthToBeOk(grid, ws, delta = 5) {
const columns = grid.querySelectorAll('vaadin-grid-column');

Array.from(columns).forEach((col, i) => {
const columnWidth = num(col.width);
expect(columnWidth).to.be.closeTo(ws[i], delta);
});
}

function createGrid(id, items = [{a: 'm', b: 'mm'}]) {
const grid = fixture(id);
grid.items = items;
flushGrid(grid);

return grid;
}

it('should consider column group when calculating column width', () => {
const grid = createGrid('column-group-1');
expectColumnsWidthToBeOk(grid, [420], 25);
});

it('should distribute the excess space to all columns', () => {
const grid = createGrid('column-group-2');
expectColumnsWidthToBeOk(grid, [217, 217], 20);
});

it('should distribute the extra necessary space to all columns regardless of flexGrow', () => {
const items = [{first: 'fff', last: 'lll'}];

const grid = createGrid('column-group-3', items);
const gridWithFlexGrow = createGrid('column-group-4', items);

const [columnA, columnB] = grid.querySelectorAll('vaadin-grid-column');
const [columnA2, columnB2] = gridWithFlexGrow.querySelectorAll('vaadin-grid-column');

expect(columnA.width).to.equal(columnA2.width);
expect(columnB.width).to.equal(columnB2.width);
});

it('should distribute the excess space to all columns according to their initial width', () => {
const grid = createGrid('column-group-5');
const [columnA, columnB] = grid.querySelectorAll('vaadin-grid-column');
expect(num(columnB.width)).to.be.greaterThan(num(columnA.width));
});

it('should consider all the parent vaadin-grid-column-groups when calculating the necessary width', () => {
const grid = createGrid('column-group-6');

expectColumnsWidthToBeOk(grid, [403], 30);
});

it('should consider vaadin-grid-column header when calculating column width', () => {
const grid = createGrid('column-group-7');

expectColumnsWidthToBeOk(grid, [420], 25);
});

it('should consider vaadin-grid-column-group footer when calculating column width', () => {
const grid = createGrid('column-group-8');

const columnGroup = document.querySelector('vaadin-grid-column-group');
const column = document.querySelector('vaadin-grid-column');

columnGroup.footerRenderer = (root) => {
const footer = document.createElement('footer');
footer.textContent = 'group footer';
footer.style.width = '300px';

root.appendChild(footer);
};

column.footerRenderer = (root) => {
const footer = document.createElement('footer');
footer.textContent = 'column footer';
root.appendChild(footer);
};

grid.recalculateColumnWidths();
flushGrid(grid);
expectColumnsWidthToBeOk(grid, [333]);
});

it('should consider vaadin-grid-column footer when calculating column width', () => {
const grid = createGrid('column-group-9');
const columnGroup = document.querySelector('vaadin-grid-column-group');
const column = document.querySelector('vaadin-grid-column');

columnGroup.footerRenderer = (root) => {
const footer = document.createElement('footer');
footer.textContent = 'group footer';
root.appendChild(footer);
};

column.footerRenderer = (root) => {
const footer = document.createElement('footer');
footer.textContent = 'footer';
footer.style.width = '300px';

root.appendChild(footer);
};

grid.recalculateColumnWidths();
flushGrid(grid);
expectColumnsWidthToBeOk(grid, [333]);
});

it('should not error when there is no vaadin-grid-column-group', () => {
const grid = createGrid('column-group-10');

const column = document.querySelector('vaadin-grid-column');

column.footerRenderer = (root) => {
const footer = document.createElement('footer');
footer.textContent = 'footer';
footer.style.width = '300px';

root.appendChild(footer);
};

grid.recalculateColumnWidths();
flushGrid(grid);
expectColumnsWidthToBeOk(grid, [333]);
});
});
</script>

</body>
Expand Down

0 comments on commit bec0993

Please # to comment.