pull/3114/merge
Jasmin Solanki 2025-04-24 12:04:39 +00:00 committed by GitHub
commit 01971faf49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1044 additions and 0 deletions

View File

@ -0,0 +1,97 @@
==============================
Web Remember Tree Column Width
==============================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:ec03536e8e88c5e3dbb117d391ba923b60e328998f72c81cee3b41c8ecaea9c2
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
: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/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-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=18.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
Remember the tree columns' widths across sessions, and after filtering,
grouping, or reordering.
**Table of contents**
.. contents::
:local:
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:%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.
Credits
=======
Authors
-------
* Vauxoo
Contributors
------------
- Francisco Javier Luna Vázquez <fluna@vauxoo.com>
- 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>
Maintainers
-----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
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.
.. |maintainer-frahikLV| image:: https://github.com/frahikLV.png?size=40px
:target: https://github.com/frahikLV
:alt: frahikLV
.. |maintainer-luisg123v| image:: https://github.com/luisg123v.png?size=40px
:target: https://github.com/luisg123v
:alt: luisg123v
.. |maintainer-cuongnmtm| image:: https://github.com/cuongnmtm.png?size=40px
:target: https://github.com/cuongnmtm
:alt: cuongnmtm
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/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

@ -0,0 +1,24 @@
{
"name": "Web Remember Tree Column Width",
"summary": "Remember the tree columns' widths across sessions.",
"author": "Vauxoo, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/web",
"license": "LGPL-3",
"category": "Extra Tools",
"version": "18.0.1.0.0",
"maintainers": [
"frahikLV",
"luisg123v",
"cuongnmtm",
],
"depends": [
"web",
],
"data": [],
"assets": {
"web.assets_backend": [
"web_remember_tree_column_width/static/src/**/*",
],
},
"installable": True,
}

View File

@ -0,0 +1,13 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"

View File

@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@ -0,0 +1,5 @@
- Francisco Javier Luna Vázquez \<<fluna@vauxoo.com>\>
- 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

@ -0,0 +1,2 @@
Remember the tree columns' widths across sessions, and after filtering,
grouping, or reordering.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1,432 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Web Remember Tree Column Width</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
: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.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
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 }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="web-remember-tree-column-width">
<h1 class="title">Web Remember Tree Column Width</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! 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/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>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-1">Bug Tracker</a></li>
<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="#maintainers" id="toc-entry-5">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-1">Bug Tracker</a></h1>
<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:%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">
<h1><a class="toc-backref" href="#toc-entry-2">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-3">Authors</a></h2>
<ul class="simple">
<li>Vauxoo</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-4">Contributors</a></h2>
<ul class="simple">
<li>Francisco Javier Luna Vázquez &lt;<a class="reference external" href="mailto:fluna&#64;vauxoo.com">fluna&#64;vauxoo.com</a>&gt;</li>
<li>Tomás Álvarez &lt;<a class="reference external" href="mailto:tomas&#64;vauxoo.com">tomas&#64;vauxoo.com</a>&gt;</li>
<li><a class="reference external" href="https://komit-consulting.com/">Komit</a>:<ul>
<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>
<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-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>
<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/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>
</div>
</body>
</html>

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

@ -0,0 +1,21 @@
import {ListRenderer} from "@web/views/list/list_renderer";
import {useMagicColumnWidths} from "./column_width_hook.esm";
import {patch} from "@web/core/utils/patch";
patch(ListRenderer.prototype, {
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,
};
});
},
});

View File

@ -0,0 +1,4 @@
th.o_column_sortable {
max-width: none !important;
min-width: 0 !important;
}