[MIG] web_remember_tree_column_width: Migration to 18.0

pull/3114/head
JasminSForgeFlow 2025-03-07 17:14:27 +05:30
parent 4d3ad36222
commit e84ac770c6
7 changed files with 478 additions and 101 deletions

View File

@ -17,13 +17,13 @@ Web Remember Tree Column Width
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github
:target: https://github.com/OCA/web/tree/17.0/web_remember_tree_column_width
:target: https://github.com/OCA/web/tree/18.0/web_remember_tree_column_width
:alt: OCA/web
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/web-17-0/web-17-0-web_remember_tree_column_width
:target: https://translation.odoo-community.org/projects/web-18-0/web-18-0-web_remember_tree_column_width
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=17.0
:target: https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=18.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
@ -42,7 +42,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_remember_tree_column_width%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_remember_tree_column_width%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
@ -63,15 +63,7 @@ Contributors
- Cuong Nguyen Mtm <cuong.nmtm@komit-consulting.com>
Other credits
-------------
- Vauxoo
The migration of this module from 15.0 to 16.0 was financially supported
by:
- Komit (https://komit-consulting.com/)
- Jasmin Solanki <jasmin.solanki@forgeflow.com>
Maintainers
-----------
@ -100,6 +92,6 @@ Current `maintainers <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-frahikLV| |maintainer-luisg123v| |maintainer-cuongnmtm|
This module is part of the `OCA/web <https://github.com/OCA/web/tree/17.0/web_remember_tree_column_width>`_ project on GitHub.
This module is part of the `OCA/web <https://github.com/OCA/web/tree/18.0/web_remember_tree_column_width>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -5,7 +5,7 @@
"website": "https://github.com/OCA/web",
"license": "LGPL-3",
"category": "Extra Tools",
"version": "17.0.1.0.0",
"version": "18.0.1.0.0",
"maintainers": [
"frahikLV",
"luisg123v",

View File

@ -2,3 +2,4 @@
- Tomás Álvarez \<<tomas@vauxoo.com>\>
- [Komit](https://komit-consulting.com/):
- Cuong Nguyen Mtm \<<cuong.nmtm@komit-consulting.com>\>
- Jasmin Solanki \<<jasmin.solanki@forgeflow.com>\>

View File

@ -1,6 +0,0 @@
- Vauxoo
The migration of this module from 15.0 to 16.0 was financially supported
by:
- Komit (<https://komit-consulting.com/>)

View File

@ -8,10 +8,11 @@
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
@ -274,7 +275,7 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
@ -300,7 +301,7 @@ span.option {
span.pre {
white-space: pre }
span.problematic {
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
@ -368,7 +369,7 @@ ul.auto-toc {
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:ec03536e8e88c5e3dbb117d391ba923b60e328998f72c81cee3b41c8ecaea9c2
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/17.0/web_remember_tree_column_width"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/web-17-0/web-17-0-web_remember_tree_column_width"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/18.0/web_remember_tree_column_width"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/web-18-0/web-18-0-web_remember_tree_column_width"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>Remember the tree columns widths across sessions, and after filtering,
grouping, or reordering.</p>
<p><strong>Table of contents</strong></p>
@ -378,8 +379,7 @@ grouping, or reordering.</p>
<li><a class="reference internal" href="#credits" id="toc-entry-2">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-3">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-4">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="toc-entry-5">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-6">Maintainers</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-5">Maintainers</a></li>
</ul>
</li>
</ul>
@ -389,7 +389,7 @@ grouping, or reordering.</p>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_remember_tree_column_width%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_remember_tree_column_width%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
@ -409,29 +409,21 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
<li>Cuong Nguyen Mtm &lt;<a class="reference external" href="mailto:cuong.nmtm&#64;komit-consulting.com">cuong.nmtm&#64;komit-consulting.com</a>&gt;</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="other-credits">
<h2><a class="toc-backref" href="#toc-entry-5">Other credits</a></h2>
<ul class="simple">
<li>Vauxoo</li>
</ul>
<p>The migration of this module from 15.0 to 16.0 was financially supported
by:</p>
<ul class="simple">
<li>Komit (<a class="reference external" href="https://komit-consulting.com/">https://komit-consulting.com/</a>)</li>
<li>Jasmin Solanki &lt;<a class="reference external" href="mailto:jasmin.solanki&#64;forgeflow.com">jasmin.solanki&#64;forgeflow.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h2>
<h2><a class="toc-backref" href="#toc-entry-5">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainers</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/frahikLV"><img alt="frahikLV" src="https://github.com/frahikLV.png?size=40px" /></a> <a class="reference external image-reference" href="https://github.com/luisg123v"><img alt="luisg123v" src="https://github.com/luisg123v.png?size=40px" /></a> <a class="reference external image-reference" href="https://github.com/cuongnmtm"><img alt="cuongnmtm" src="https://github.com/cuongnmtm.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/17.0/web_remember_tree_column_width">OCA/web</a> project on GitHub.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/18.0/web_remember_tree_column_width">OCA/web</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>

View File

@ -0,0 +1,443 @@
/* eslint init-declarations: 0 */
/* eslint no-undef: 0 */
/* eslint no-use-before-define: 0 */
// Copy file web/static/src/views/list/column_width_hook.js and added custom code for remeber width of column
import {useDebounced} from "@web/core/utils/timing";
import {browser} from "@web/core/browser/browser";
import {useComponent, useEffect, useExternalListener} from "@odoo/owl";
// Hardcoded widths
const DEFAULT_MIN_WIDTH = 80;
const SELECTOR_WIDTH = 20;
const OPEN_FORM_VIEW_BUTTON_WIDTH = 54;
const DELETE_BUTTON_WIDTH = 12;
const FIELD_WIDTHS = {
boolean: [20, 100], // [minWidth, maxWidth]
char: [80], // Only minWidth, no maxWidth
date: 80, // MinWidth = maxWidth
datetime: 145,
float: 93,
integer: 71,
many2many: [80],
many2one_reference: [80],
many2one: [80],
monetary: 105,
one2many: [80],
reference: [80],
selection: [80],
text: [80, 1200],
};
/**
* Compute ideal widths based on the rules described on top of this file.
*
* @params {Element} table
* @params {Object} state
* @params {Number} allowedWidth
* @params {Number[]} startingWidths
* @returns {Number[]}
*/
function computeWidths(table, state, allowedWidth, startingWidths) {
let _columnWidths;
const headers = [...table.querySelectorAll("thead th")];
const columns = state.columns;
// Starting point: compute widths
if (startingWidths) {
_columnWidths = startingWidths.slice();
} else if (state.isEmpty) {
// Table is empty => uniform distribution as starting point
_columnWidths = headers.map(() => allowedWidth / headers.length);
} else {
// Table contains records => let the browser compute ideal widths
// Set table layout auto and remove inline style
table.style.tableLayout = "auto";
headers.forEach((th) => {
th.style.width = null;
});
// Toggle a className used to remove style that could interfere with the ideal width
// computation algorithm (e.g. prevent text fields from being wrapped during the
// computation, to prevent them from being completely crushed)
table.classList.add("o_list_computing_widths");
_columnWidths = headers.map((th) => th.getBoundingClientRect().width);
table.classList.remove("o_list_computing_widths");
}
// Force columns to comply with their min and max widths
if (state.hasSelectors) {
_columnWidths[0] = SELECTOR_WIDTH;
}
if (state.hasOpenFormViewColumn) {
const index = _columnWidths.length - (state.hasActionsColumn ? 2 : 1);
_columnWidths[index] = OPEN_FORM_VIEW_BUTTON_WIDTH;
}
if (state.hasActionsColumn) {
_columnWidths[_columnWidths.length - 1] = DELETE_BUTTON_WIDTH;
}
const columnWidthSpecs = getWidthSpecs(columns);
const columnOffset = state.hasSelectors ? 1 : 0;
for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
const thIndex = columnIndex + columnOffset;
const {minWidth, maxWidth} = columnWidthSpecs[columnIndex];
if (_columnWidths[thIndex] < minWidth) {
_columnWidths[thIndex] = minWidth;
} else if (maxWidth && _columnWidths[thIndex] > maxWidth) {
_columnWidths[thIndex] = maxWidth;
}
}
// Expand/shrink columns for the table to fill 100% of available space
const totalWidth = _columnWidths.reduce((tot, width) => tot + width, 0);
let diff = totalWidth - allowedWidth;
if (diff >= 1) {
// Case 1: table overflows its parent => shrink some columns
const shrinkableColumns = [];
let totalAvailableSpace = 0; // Total space we can gain by shrinking columns
for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
const thIndex = columnIndex + columnOffset;
const {minWidth, canShrink} = columnWidthSpecs[columnIndex];
if (_columnWidths[thIndex] > minWidth && canShrink) {
shrinkableColumns.push({thIndex, minWidth});
totalAvailableSpace += _columnWidths[thIndex] - minWidth;
}
}
if (diff > totalAvailableSpace) {
// We can't find enough space => set all columns to their min width, and there'll be an
// horizontal scrollbar
for (const {thIndex, minWidth} of shrinkableColumns) {
_columnWidths[thIndex] = minWidth;
}
} else {
// There's enough available space among shrinkable columns => shrink them uniformly
let remainingColumnsToShrink = shrinkableColumns.length;
while (diff >= 1) {
const colDiff = diff / remainingColumnsToShrink;
for (const {thIndex, minWidth} of shrinkableColumns) {
const currentWidth = _columnWidths[thIndex];
if (currentWidth === minWidth) {
continue;
}
const newWidth = Math.max(currentWidth - colDiff, minWidth);
diff -= currentWidth - newWidth;
_columnWidths[thIndex] = newWidth;
if (newWidth === minWidth) {
remainingColumnsToShrink--;
}
}
}
}
} else if (diff <= -1) {
// Case 2: table is narrower than its parent => expand some columns
diff = -diff; // For better readability
const expandableColumns = [];
for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
const thIndex = columnIndex + columnOffset;
const maxWidth = columnWidthSpecs[columnIndex].maxWidth;
if (!maxWidth || _columnWidths[thIndex] < maxWidth) {
expandableColumns.push({thIndex, maxWidth});
}
}
// Expand all expandable columns uniformly (i.e. at most, expand columns with a maxWidth
// to their maxWidth)
let remainingExpandableColumns = expandableColumns.length;
while (diff >= 1 && remainingExpandableColumns > 0) {
const colDiff = diff / remainingExpandableColumns;
for (const {thIndex, maxWidth} of expandableColumns) {
const currentWidth = _columnWidths[thIndex];
const newWidth = Math.min(
currentWidth + colDiff,
maxWidth || Number.MAX_VALUE
);
diff -= newWidth - currentWidth;
_columnWidths[thIndex] = newWidth;
if (newWidth === maxWidth) {
remainingExpandableColumns--;
}
}
}
if (diff >= 1) {
// All columns have a maxWidth and have been expanded to their max => expand them more
for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
const thIndex = columnIndex + columnOffset;
_columnWidths[thIndex] += diff / columns.length;
}
}
}
return _columnWidths;
}
/**
* Returns for each column its minimal and (if any) maximal widths.
*
* @param {Object[]} columns
* @returns {Object[]} each entry in this array has a minWidth and optionally a maxWidth key
*/
function getWidthSpecs(columns) {
return columns.map((column) => {
let minWidth;
let maxWidth;
if (column.attrs && column.attrs.width) {
minWidth = maxWidth = parseInt(column.attrs.width.split("px")[0]);
} else {
let width;
if (column.type === "field") {
if (column.field.listViewWidth) {
width = column.field.listViewWidth;
if (typeof width === "function") {
width = width({
type: column.fieldType,
hasLabel: column.hasLabel,
});
}
} else {
width = FIELD_WIDTHS[column.widget || column.fieldType];
}
} else if (column.type === "widget") {
width = column.widget.listViewWidth;
}
if (width) {
minWidth = Array.isArray(width) ? width[0] : width;
maxWidth = Array.isArray(width) ? width[1] : width;
} else {
minWidth = DEFAULT_MIN_WIDTH;
}
}
return {minWidth, maxWidth, canShrink: column.type === "field"};
});
}
/**
* Given an html element, returns the sum of its left and right padding.
*
* @param {HTMLElement} el
* @returns {Number}
*/
function getHorizontalPadding(el) {
const {paddingLeft, paddingRight} = getComputedStyle(el);
return parseFloat(paddingLeft) + parseFloat(paddingRight);
}
export function useMagicColumnWidths(tableRef, getState) {
const renderer = useComponent();
let columnWidths = null;
let allowedWidth = 0;
let hasAlwaysBeenEmpty = true;
let parentWidthFixed = false;
let hash;
let _resizing = false;
/**
* Apply the column widths in the DOM. If necessary, compute them first (e.g. if they haven't
* been computed yet, or if columns have changed).
*
* Note: the following code manipulates the DOM directly to avoid having to wait for a
* render + patch which would occur on the next frame and cause flickering.
*/
function forceColumnWidths() {
const table = tableRef.el;
const headers = [...table.querySelectorAll("thead th")];
const state = getState();
const resModel = state.model.config.resModel;
// Generate a hash to be able to detect when the columns change
const columns = state.columns;
// The last part of the hash is there to detect that static columns changed (typically, the
// selector column, which isn't displayed on small screens)
const nextHash = `${columns.map((column) => column.id).join("/")}/${headers.length}`;
if (nextHash !== hash) {
hash = nextHash;
resetWidths();
}
// If the table has always been empty until now, and it now contains records, we want to
// recompute the widths based on the records (typical case: we removed a filter).
// Exception: we were in an empty editable list, and we just added a first record.
if (hasAlwaysBeenEmpty && !state.isEmpty) {
hasAlwaysBeenEmpty = false;
const rows = table.querySelectorAll(".o_data_row");
if (rows.length !== 1 || !rows[0].classList.contains("o_selected_row")) {
resetWidths();
}
}
const parentPadding = getHorizontalPadding(table.parentNode);
const cellPaddings = headers.map((th) => getHorizontalPadding(th));
const totalCellPadding = cellPaddings.reduce(
(total, padding) => padding + total,
0
);
const nextAllowedWidth =
table.parentNode.clientWidth - parentPadding - totalCellPadding;
const allowedWidthDiff = Math.abs(allowedWidth - nextAllowedWidth);
allowedWidth = nextAllowedWidth;
// When a vertical scrollbar appears/disappears, it may (depending on the browser/os) change
// the available width. When it does, we want to keep the current widths, but tweak them a
// little bit s.t. the table fits in the new available space.
if (!columnWidths || allowedWidthDiff > 0) {
columnWidths = computeWidths(table, state, allowedWidth, columnWidths);
}
// Custom code to get width from browser storage and update in list
// custom code start here
headers.forEach((el, elIndex) => {
const fieldName =
(state.columns[elIndex] && state.columns[elIndex].name) || "";
if (
!el.classList.contains("o_list_button") &&
fieldName &&
resModel &&
browser.localStorage
) {
const storedWidth = browser.localStorage.getItem(
`odoo.columnWidth.${resModel}.${fieldName}`
);
if (storedWidth) {
columnWidths[elIndex + 1] = parseInt(storedWidth, 10);
}
}
});
// Custom code end here
// Set the computed widths in the DOM.
table.style.tableLayout = "fixed";
headers.forEach((th, index) => {
th.style.width = `${Math.floor(columnWidths[index] + cellPaddings[index])}px`;
});
}
/**
* Resets the widths. After next patch, ideal widths will be recomputed.
*/
function resetWidths() {
columnWidths = null;
// Unset widths that might have been set on the table by resizing a column
tableRef.el.style.width = null;
if (parentWidthFixed) {
tableRef.el.parentElement.style.width = null;
}
}
/**
* Handles the resize feature on the column headers
*
* @private
* @param {MouseEvent} ev
*/
function onStartResize(ev) {
_resizing = true;
const table = tableRef.el;
const th = ev.target.closest("th");
const handler = th.querySelector(".o_resize");
table.style.width = `${Math.floor(table.getBoundingClientRect().width)}px`;
const thPosition = [...th.parentNode.children].indexOf(th);
const resizingColumnElements = [...table.getElementsByTagName("tr")]
.filter((tr) => tr.children.length === th.parentNode.children.length)
.map((tr) => tr.children[thPosition]);
const initialX = ev.clientX;
const initialWidth = th.getBoundingClientRect().width;
const initialTableWidth = table.getBoundingClientRect().width;
const resizeStoppingEvents = ["keydown", "pointerdown", "pointerup"];
// Fix the width so that if the resize overflows, it doesn't affect the layout of the parent
if (!table.parentElement.style.width) {
parentWidthFixed = true;
table.parentElement.style.width = `${Math.floor(
table.parentElement.getBoundingClientRect().width
)}px`;
}
// Apply classes to table and selected column
table.classList.add("o_resizing");
for (const el of resizingColumnElements) {
el.classList.add("o_column_resizing");
handler.classList.add("bg-primary", "opacity-100");
handler.classList.remove("bg-black-25", "opacity-50-hover");
}
// Mousemove event : resize header
const resizeHeader = (ev) => {
ev.preventDefault();
ev.stopPropagation();
const delta = ev.clientX - initialX;
const newWidth = Math.max(10, initialWidth + delta);
const tableDelta = newWidth - initialWidth;
th.style.width = `${Math.floor(newWidth)}px`;
table.style.width = `${Math.floor(initialTableWidth + tableDelta)}px`;
};
window.addEventListener("pointermove", resizeHeader);
// Mouse or keyboard events : stop resize
const stopResize = (ev) => {
_resizing = false;
// Store current column widths to freeze them
const headers = [...table.querySelectorAll("thead th")];
columnWidths = headers.map((th) => {
return th.getBoundingClientRect().width - getHorizontalPadding(th);
});
// Ignores the 'left mouse button down' event as it used to start resizing
if (ev.type === "pointerdown" && ev.button === 0) {
return;
}
ev.preventDefault();
ev.stopPropagation();
table.classList.remove("o_resizing");
for (const el of resizingColumnElements) {
el.classList.remove("o_column_resizing");
handler.classList.remove("bg-primary", "opacity-100");
handler.classList.add("bg-black-25", "opacity-50-hover");
}
window.removeEventListener("pointermove", resizeHeader);
for (const eventType of resizeStoppingEvents) {
window.removeEventListener(eventType, stopResize);
}
// Custom code to set width to browser storage
// custom code start here
const th = ev.target.closest("th");
const fieldName = th.dataset.name;
const resModel = this.props.list.model.config.resModel;
if (resModel && fieldName && browser.localStorage) {
browser.localStorage.setItem(
"odoo.columnWidth." + resModel + "." + fieldName,
parseInt((th.style.width || "0").replace("px", ""), 10) || 0
);
}
// Custom code end here
// We remove the focus to make sure that the there is no focus inside
// the tr. If that is the case, there is some css to darken the whole
// thead, and it looks quite weird with the small css hover effect.
document.activeElement.blur();
};
// We have to listen to several events to properly stop the resizing function. Those are:
// - pointerdown (e.g. pressing right click)
// - pointerup : logical flow of the resizing feature (drag & drop)
// - keydown : (e.g. pressing 'Alt' + 'Tab' or 'Windows' key)
for (const eventType of resizeStoppingEvents) {
window.addEventListener(eventType, stopResize);
}
}
// Side effects
if (renderer.constructor.useMagicColumnWidths) {
useEffect(forceColumnWidths);
const debouncedResizeCallback = useDebounced(() => {
resetWidths();
forceColumnWidths();
}, 200);
useExternalListener(window, "resize", debouncedResizeCallback);
}
// API
return {
get resizing() {
return _resizing;
},
onStartResize,
};
}

View File

@ -1,66 +1,21 @@
/** @odoo-module **/
import {ListRenderer} from "@web/views/list/list_renderer";
import {browser} from "@web/core/browser/browser";
import {useMagicColumnWidths} from "./column_width_hook.esm";
import {patch} from "@web/core/utils/patch";
patch(ListRenderer.prototype, {
/**
* @override
*/
computeColumnWidthsFromContent(allowedWidth) {
const columnWidths = super.computeColumnWidthsFromContent(allowedWidth);
const table = this.tableRef.el;
const thElements = [...table.querySelectorAll("thead th")];
thElements.forEach((el, elIndex) => {
const fieldName = $(el).data("name");
if (
!el.classList.contains("o_list_button") &&
this.props.list.resModel &&
fieldName &&
browser.localStorage
) {
const storedWidth = browser.localStorage.getItem(
`odoo.columnWidth.${this.props.list.resModel}.${fieldName}`
);
if (storedWidth) {
columnWidths[elIndex] = parseInt(storedWidth, 10);
}
}
setup() {
super.setup();
this.columnWidths = useMagicColumnWidths(this.tableRef, () => {
return {
columns: this.columns,
isEmpty:
!this.props.list.records.length ||
this.props.list.model.useSampleModel,
hasSelectors: this.hasSelectors,
hasOpenFormViewColumn: this.hasOpenFormViewColumn,
hasActionsColumn: this.hasActionsColumn,
model: this.props.list.model,
};
});
return columnWidths;
},
/**
* @override
*/
onStartResize(ev) {
super.onStartResize(ev);
const resizeStoppingEvents = ["keydown", "mousedown", "mouseup"];
const $th = $(ev.target.closest("th"));
if (!$th || !$th.is("th")) {
return;
}
const saveWidth = (saveWidthEv) => {
if (saveWidthEv.type === "mousedown" && saveWidthEv.which === 1) {
return;
}
ev.preventDefault();
ev.stopPropagation();
const fieldName = $th.length ? $th.data("name") : undefined;
if (this.props.list.resModel && fieldName && browser.localStorage) {
browser.localStorage.setItem(
"odoo.columnWidth." + this.props.list.resModel + "." + fieldName,
parseInt(($th[0].style.width || "0").replace("px", ""), 10) || 0
);
}
for (const eventType of resizeStoppingEvents) {
browser.removeEventListener(eventType, saveWidth);
}
document.activeElement.blur();
};
for (const eventType of resizeStoppingEvents) {
browser.addEventListener(eventType, saveWidth);
}
},
});