From 4bf700140b70d27a8ea599e995bbf8038d1ed376 Mon Sep 17 00:00:00 2001 From: Joseph Caburnay Date: Tue, 3 Jan 2023 15:09:31 +0000 Subject: [PATCH] [FIX] web: list column resize issue **Issue:** - Try resizing a column. - Bug: After releasing the resize, the column being resized is also reordered. - This results to automatic resizing of the columns, not respecting the current resize made by the user. **Solution:** 1. Keep the size of the columns after reordering. Better UX because any resize made by the user won't change when reordering the list (accidentally or purposely). 2. During resize, we track state variables - `resizing` and `preventReorder`. - When resize starts, `resizing` is set to `true`. - When the mouse pointer is released (mouseup), and before the `mouseup` event reaches the `window`'s `mouseup` listener, the column title handles the `mouseup` first, setting another state variable `preventReorder` to `true`. - `mouseup` will eventually reached `window` which stops the resize, setting `resizing` to false. - Then, click event happens on the column header. And since the `preventSort` is true, it will return early but resets the `preventReorder` to `false`. - If the click event happened to be in the resizer span element, we should not prevent it from bubbling so that `preventReorder` is properly reset. **TASK-ID:** 3109501 closes odoo/odoo#109518 X-original-commit: 7a71a0974164277ac7fd382391684b078b71bf7d Signed-off-by: Aaron Bohy (aab) --- .../static/src/views/list/list_renderer.js | 26 +++++++++ .../static/src/views/list/list_renderer.xml | 2 +- .../web/static/tests/views/list_view_tests.js | 54 +++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/views/list/list_renderer.js b/addons/web/static/src/views/list/list_renderer.js index 555e8618e2c..f5531e2fc71 100644 --- a/addons/web/static/src/views/list/list_renderer.js +++ b/addons/web/static/src/views/list/list_renderer.js @@ -76,6 +76,18 @@ export class ListRenderer extends Component { this.longTouchTimer = null; this.touchStartMs = 0; + /** + * When resizing, it's possible that the pointer is not above the resize + * handle (by some few pixel difference). During this scenario, click event + * will be triggered on the column title which will reorder the column. + * Column resize that triggers a reorder is not a good UX and we prevent this + * using the following state variables: `resizing` and `preventReorder` which + * are set during the column's click (onClickSortColumn), mouseup + * (onColumnTitleMouseUp) and onStartResize events. + */ + this.resizing = false; + this.preventReorder = false; + this.creates = this.props.archInfo.creates.length ? this.props.archInfo.creates : [{ type: "create", string: this.env._t("Add a line") }]; @@ -863,6 +875,10 @@ export class ListRenderer extends Component { } onClickSortColumn(column) { + if (this.preventReorder) { + this.preventReorder = false; + return; + } if (this.props.list.editedRecord || this.props.list.model.useSampleModel) { return; } @@ -870,6 +886,8 @@ export class ListRenderer extends Component { const list = this.props.list; if (this.isSortable(column)) { list.sortBy(fieldName); + // don't resize column when reordering. + this.keepColumnWidths = true; } } @@ -1684,6 +1702,12 @@ export class ListRenderer extends Component { } } + onColumnTitleMouseUp() { + if (this.resizing) { + this.preventReorder = true; + } + } + /** * Handles the resize feature on the column headers * @@ -1691,6 +1715,7 @@ export class ListRenderer extends Component { * @param {MouseEvent} ev */ onStartResize(ev) { + this.resizing = true; const table = this.tableRef.el; const th = ev.target.closest("th"); const handler = th.querySelector(".o_resize"); @@ -1733,6 +1758,7 @@ export class ListRenderer extends Component { // Mouse or keyboard events : stop resize const stopResize = (ev) => { + this.resizing = false; // Ignores the 'left mouse button down' event as it used to start resizing if (ev.type === "mousedown" && ev.which === 1) { return; diff --git a/addons/web/static/src/views/list/list_renderer.xml b/addons/web/static/src/views/list/list_renderer.xml index d843463bb86..299cdedf7d2 100644 --- a/addons/web/static/src/views/list/list_renderer.xml +++ b/addons/web/static/src/views/list/list_renderer.xml @@ -24,6 +24,7 @@ t-att-class="getColumnClass(column) + ' opacity-trigger-hover'" t-on-mouseenter="ev => this.onHoverSortColumn(ev, column)" t-on-mouseleave="ev => this.onHoverSortColumn(ev, column)" + t-on-mouseup="onColumnTitleMouseUp" t-on-click="() => this.onClickSortColumn(column)" t-on-keydown.synthetic="(ev) => this.onCellKeydown(ev)" t-att-data-tooltip-template="isDebugMode ? 'web.FieldTooltip' : false" t-att-data-tooltip-info="isDebugMode ? makeTooltip(column) : false" tabindex="-1"> @@ -37,7 +38,6 @@ diff --git a/addons/web/static/tests/views/list_view_tests.js b/addons/web/static/tests/views/list_view_tests.js index 7e801c416c4..8d8c2ed3efb 100644 --- a/addons/web/static/tests/views/list_view_tests.js +++ b/addons/web/static/tests/views/list_view_tests.js @@ -23,6 +23,7 @@ import { clickOpenedDropdownItem, clickOpenM2ODropdown, clickSave, + drag, dragAndDrop, editInput, editSelect, @@ -14869,6 +14870,59 @@ QUnit.module("Views", (hooks) => { ); }); + QUnit.test("list: column: resize, reorder, resize again", async function (assert) { + serverData.models.foo.fields.foo.sortable = true; + serverData.models.foo.fields.int_field.sortable = true; + await makeView({ + type: "list", + resModel: "foo", + serverData, + arch: ` + + + + `, + }); + + // pointer doesn't perfectly match the resized th. + const PIXEL_TOLERANCE = 3; + const assertAlmostEqual = (v1, v2) => Math.abs(v1 - v2) <= PIXEL_TOLERANCE; + + // 1. Resize column foo to middle of column int_field. + const originalWidths = [...target.querySelectorAll(".o_list_table th")].map( + (th) => th.offsetWidth + ); + const th2 = target.querySelector("th:nth-child(2)"); + const th3 = target.querySelector("th:nth-child(3)"); + const resizeHandle = th2.querySelector(".o_resize"); + + await dragAndDrop(resizeHandle, th3); + + const widthsAfterResize = [...target.querySelectorAll(".o_list_table th")].map( + (th) => th.offsetWidth + ); + + assert.strictEqual(widthsAfterResize[0], originalWidths[0]); + assertAlmostEqual(widthsAfterResize[1], originalWidths[1] + originalWidths[2] / 2); + + // 2. Reorder column foo. + await click(th2); + const widthsAfterReorder = [...target.querySelectorAll(".o_list_table th")].map( + (th) => th.offsetWidth + ); + + assert.strictEqual(widthsAfterResize[0], widthsAfterReorder[0]); + assert.strictEqual(widthsAfterResize[1], widthsAfterReorder[1]); + + // 3. Resize again, this time check sizes while dragging and after drop. + const drop = drag(resizeHandle, th3); + assertAlmostEqual(th2.offsetWidth, widthsAfterReorder[1] + widthsAfterReorder[2] / 2); + + drop(); + await nextTick(); + assertAlmostEqual(th2.offsetWidth, widthsAfterReorder[1] + widthsAfterReorder[2] / 2); + }); + QUnit.test("editable list: resize column headers", async function (assert) { await makeView({ type: "list",