diff --git a/sao/CHANGELOG b/sao/CHANGELOG index 416c42cff02..42bc3b5e5e6 100644 --- a/sao/CHANGELOG +++ b/sao/CHANGELOG @@ -1,3 +1,4 @@ +* Use grid layout for form container * Don't reset date/datetime/time to None when it is invalid Version 6.8.2 - 2023-09-06 diff --git a/sao/src/sao.less b/sao/src/sao.less index 44a78df70d5..da970284f74 100644 --- a/sao/src/sao.less +++ b/sao/src/sao.less @@ -629,25 +629,17 @@ img.icon { .form { width: 100%; .form-container, .form-hcontainer, .form-vcontainer { - display: inline-table; + display: grid; } - table.form-container > tbody > tr > td { + .form-item { + display: flex; padding: 2px; - } - table.form-hcontainer > tbody > tr > td { - padding-top: 0px; - padding-bottom: 0px; - > .btn { - margin-top: 2px; - margin-bottom: 2px; + &.form-empty { + padding: 0px; } } - table.form-vcontainer > tbody > tr > td { - padding-left: 0px; - padding-right: 0px; - } - td.form-label { + .form-label { white-space: pre; } .form-char, .form-password, .form-integer, .form-float, .form-date, @@ -796,6 +788,49 @@ img.icon { vertical-align: middle; } } + + .xexpand { + width: 100%; + } + + .xfill { + justify-content: stretch; + } + + .yexpand { + height: 100%; + } + + .xalign-start { + justify-self: start; + justify-content: start; + } + + .xalign-center { + justify-self: center; + justify-content: center; + } + + .xalign-end { + justify-self: end; + justify-content: end; + } + + .yalign-start { + align-items: start; + } + + .yalign-center { + align-items: center; + } + + .yalign-end { + align-items: end; + } + + .yfill { + align-self: stretch; + } } .form-binary, .editabletree-binary { @@ -912,6 +947,16 @@ input.column-boolean { .form-link { display: none; } + + .form { + .form-container, .form-hcontainer, .form-vcontainer { + display: block; + } + } + + .xalign-center, .xalign-end { + text-align: start; + } } @media screen and (max-width: @screen-xs-max) { diff --git a/sao/src/view/form.js b/sao/src/view/form.js index f53456d76f9..207d04ec85f 100644 --- a/sao/src/view/form.js +++ b/sao/src/view/form.js @@ -46,6 +46,9 @@ function eval_pyson(value){ this.parse(child); } if (container) { + if (container instanceof Sao.View.Form.Container) { + container.setup_grid_template(); + } this._containers.pop(); } }, @@ -359,18 +362,24 @@ function eval_pyson(value){ widget.display(); } } - }) - .done(() => { - var record = this.record; - var j; - for (j in this.state_widgets) { + var promesses = []; + for (const j in this.state_widgets) { var state_widget = this.state_widgets[j]; - state_widget.set_state(record); + var prm = state_widget.set_state(record); + if (prm) { + promesses.push(prm); + } } - for (j in this.containers) { - var container = this.containers[j]; - container.resize(); + for (const container of this.containers) { + container.set_grid_template(); } + // re-set the grid templates for the StateWidget that are + // asynchronous + jQuery.when.apply(jQuery, promesses).done(() => { + for (const container of this.containers) { + container.set_grid_template(); + } + }); }); }, set_value: function() { @@ -492,25 +501,25 @@ function eval_pyson(value){ init: function(col=4) { if (col < 0) col = 0; this.col = col; - this.el = jQuery('', { - 'class': 'form-container responsive responsive-noheader' + this.el = jQuery('
', { + 'class': 'form-container' }); - this.body = jQuery('
').appendTo(this.el); if (this.col <= 0) { this.el.addClass('form-hcontainer'); } else if (this.col == 1) { this.el.addClass('form-vcontainer'); } - this.add_row(); + this._col = 1; + this._row = 1; + this._xexpand = new Set(); + this._colspans = []; + this._yexpand = new Set(); + this._grid_cols = []; + this._grid_rows = []; }, add_row: function() { - this.body.append(jQuery('')); - }, - rows: function() { - return this.body.children('tr'); - }, - row: function() { - return this.rows().last(); + this._col = 1; + this._row += 1; }, add: function(widget, attributes) { var colspan = attributes.colspan; @@ -519,198 +528,192 @@ function eval_pyson(value){ if (xfill === undefined) xfill = 1; var xexpand = attributes.xexpand; if (xexpand === undefined) xexpand = 1; - var row = this.row(); + + // CSS grid elements are 1-indexed if (this.col > 0) { - var len = 0; - row.children().map(function(i, e) { - len += Number(jQuery(e).attr('colspan') || 1); - }); - if (len + colspan > this.col) { - this.add_row(); - row = this.row(); + if (colspan > this.col) { + colspan = this.col; + } + if ((this._col + colspan) > (this.col + 1)) { + this._col = 1; + this._row += 1; } } + var el; if (widget) { el = widget.el; } - var cell = jQuery('
', { - 'colspan': colspan, - 'class': widget ? widget.class_ || '' : '' + var cell = jQuery('
', { + 'class': 'form-item ' + (widget ? widget.class_ || '' : ''), }).append(el); - row.append(cell); + cell.css('grid-column', `${this._col} / ${this._col + colspan}`); + cell.css('grid-row', `${this._row} / ${this._row + 1}`); + this.el.append(cell); if (!widget) { + this._col += colspan; return; - } - - if (attributes.yexpand) { - cell.css('height', '100%'); + } else { + if (xexpand && (colspan == 1)) { + this._xexpand.add(this._col); + } else if (xexpand) { + var newspan = []; + for (var i=this._col; i < this._col + colspan; i++) { + newspan.push(i); + } + this._colspans.push(newspan); + } + if (attributes.yexpand) { + this._yexpand.add(this._row); + } + this._col += colspan; } if (attributes.xalign !== undefined) { var xalign; if (attributes.xalign == 0.5) { - if (xexpand) { - xalign = 'start'; - } else { - xalign = 'center'; - } + xalign = 'center'; } else { xalign = attributes.xalign <= 0.5? 'start': 'end'; } - cell.css('text-align', xalign); + cell.addClass(`xalign-${xalign}`); + } else { + cell.addClass('xalign-start'); } if (xexpand) { cell.addClass('xexpand'); - cell.css('width', '100%'); } if (xfill) { cell.addClass('xfill'); if (xexpand) { - el.css('width', '100%'); + el.addClass('xexpand'); } } if (attributes.yalign !== undefined) { var yalign; if (attributes.yalign == 0.5) { - yalign = 'middle'; + yalign = 'center'; } else { - yalign = attributes.yalign <= 0.5? 'top': 'bottom'; + yalign = attributes.yalign <= 0.5? 'start': 'end'; + } + cell.addClass(`yalign-${yalign}`); + } + + if (attributes.yfill) { + cell.addClass('yfill'); + if (attributes.yexpand) { + el.addClass('yexpand'); } - cell.css('vertical-align', yalign); } if (attributes.help) { widget.el.attr('title', attributes.help); } }, - resize: function() { - var rows = this.rows().toArray(); - var widths = []; - var col = this.col; - var has_expand = false; - - var parent_max_width = 0.75; - this.el.parents('td').each(function() { - var width = this.style.width; - if (width.endsWith('%')) { - parent_max_width *= parseFloat(width.slice(0, -1), 10) / 100; + setup_grid_template: function() { + for (const span of this._colspans) { + var found = false; + for (const col of span) { + if (this._xexpand.has(col)) { + found = true; + break; + } } - }); + if (!found) { + this._xexpand.add( + Math.round((span[0] + span[span.length - 1]) / 2)); + } + } - var get_xexpands = function(row) { - row = jQuery(row); - var xexpands = []; - let i = 0; - row.children().map(function() { - var cell = jQuery(this); - var colspan = Math.min(Number(cell.attr('colspan')), col || 1); - if (cell.hasClass('xexpand') && - (!jQuery.isEmptyObject(cell.children())) && - (cell.children(':not(.tooltip)').css('display') != 'none')) { - xexpands.push([cell, i]); + var i; + var col = this.col <= 0 ? this._col : this.col; + if (this._xexpand.size) { + for (i = 1; i <= col; i++) { + if (this._xexpand.has(i)) { + this._grid_cols.push(`minmax(min-content, ${col}fr)`); + } else { + this._grid_cols.push('min-content'); } - i += colspan; - }); - return xexpands; - }; - // Sort rows to compute first the most constraining row - // which are the one with the more xexpand cells - // and with the less colspan - rows.sort(function(a, b) { - a = get_xexpands(a); - b = get_xexpands(b); - if (a.length == b.length) { - var reduce = function(previous, current) { - var cell = current[0]; - var colspan = Math.min( - Number(cell.attr('colspan')), col || 1); - return previous + colspan; - }; - return a.reduce(reduce, 0) - b.reduce(reduce, 0); - } else { - return b.length - a.length; } - }); - for (let row of rows) { - row = jQuery(row); - var xexpands = get_xexpands(row); - const width = 100 / xexpands.length; - for (const e of xexpands) { - var cell = e[0]; - let i = e[1]; - const colspan = Math.min( - Number(cell.attr('colspan')), col || 1); - var current_width = 0; - for (let j = 0; j < colspan; j++) { - current_width += widths[i + j] || 0; - } - for (let j = 0; j < colspan; j++) { - if (!current_width) { - widths[i + j] = width / colspan; - } else if (current_width > width) { - // Split proprotionally the difference over all cells - // following their current width - var diff = current_width - width; - if (widths[i + j]) { - widths[i + j] -= (diff / - (current_width / widths[i + j])); - } - } - } + } else { + for (i = 1; i <= col; i++) { + this._grid_cols.push("min-content"); } - if (!jQuery.isEmptyObject(xexpands)) { - has_expand = true; - } - } - for (let row of rows) { - row = jQuery(row); - let i = 0; - for (let cell of row.children()) { - cell = jQuery(cell); - const colspan = Math.min( - Number(cell.attr('colspan')), col || 1); - if (cell.hasClass('xexpand') && - (cell.children(':not(.tooltip)').css('display') != - 'none')) { - let width = 0; - for (let j = 0; j < colspan; j++) { - width += widths[i + j] || 0; - } - cell.css('width', width + '%'); - if (0 < width) { - cell.css( - 'max-width', - (width * parent_max_width) + 'vw'); - } + } + + if (this._yexpand.size) { + for (i = 1; i <= this._row; i++) { + if (this._yexpand.has(i)) { + this._grid_rows.push( + `minmax(min-content, ${this._row}fr)`); } else { - cell.css('width', ''); + this._grid_rows.push('min-content'); } - // show/hide when container is horizontal or vertical - // to not show padding - if (cell.children().css('display') == 'none') { - cell.css('visibility', 'collapse'); - if (col <= 1) { - cell.hide(); - } - } else { - cell.css('visibility', 'visible'); - if (col <= 1) { - cell.show(); - } + } + } else { + for (i = 1; i <= this._row; i++) { + this._grid_rows.push("min-content"); + } + } + }, + set_grid_template: function() { + var i; + var grid_cols = this._grid_cols.slice(); + var grid_rows = this._grid_rows.slice(); + var cols = []; + var rows = []; + for (i = 0; i < grid_cols.length; i++) { + cols.push([]); + } + for (i = 0; i < grid_rows.length; i++) { + rows.push([]); + } + var col_start, col_end, row_start, row_end; + for (var child of this.el.children()) { + child = jQuery(child); + col_start = parseInt( + child.css('grid-column-start'), 10); + col_end = parseInt(child.css('grid-column-end'), 10); + row_start = parseInt(child.css('grid-row-start'), 10); + row_end = parseInt(child.css('grid-row-end'), 10); + + for (i = col_start; i < col_end; i++) { + cols[i - 1].push(child); + } + for (i = row_start; i < row_end; i++) { + rows[i - 1].push(child); + } + } + var row, col; + var is_empty = function(e) { + var empty = true; + for (const child of e.children(':not(.tooltip)')) { + if (jQuery(child).css('display') != 'none') { + empty = false; + break; } - i += colspan; + } + e.toggleClass('form-empty', empty); + return empty; + }; + for (i = 0; i < grid_cols.length; i++) { + col = cols[i]; + if (col.every(is_empty)) { + grid_cols[i] = "0px"; } } - if (has_expand && - (!this.el.closest('td').length || - this.el.closest('td').hasClass('xexpand'))) { - this.el.css('width', '100%'); - } else { - this.el.css('width', ''); + for (i = 0; i < grid_rows.length; i++) { + row = rows[i]; + if (row.every(is_empty)) { + grid_rows[i] = "0px"; + } } + this.el.css( + 'grid-template-columns', grid_cols.join(" ")); + this.el.css( + 'grid-template-rows', grid_rows.join(" ")); } }); @@ -1019,6 +1022,7 @@ function eval_pyson(value){ domain = d[1]; return [name, decoder.decode(domain)]; }); + const promesses = []; var counter; if (record && record.links_counts[this.action_id]) { counter = record.links_counts[this.action_id]; @@ -1038,7 +1042,7 @@ function eval_pyson(value){ if (tab_domains.length) { tab_domains.map(function(d, i) { var tab_domain = d[1]; - Sao.rpc({ + const prm = Sao.rpc({ 'method': ( 'model.' + action.res_model + '.search_count'), 'params': [ @@ -1049,9 +1053,10 @@ function eval_pyson(value){ value, i, current, counter, action.name, tab_domains); }); + promesses.push(prm); }, this); } else { - Sao.rpc({ + const prm = Sao.rpc({ 'method': ( 'model.' + action.res_model + '.search_count'), 'params': [domain, 0, 100, context], @@ -1061,8 +1066,10 @@ function eval_pyson(value){ value, 0, current, counter, action.name, tab_domains); }); + promesses.push(prm); } } + return jQuery.when.apply(jQuery, promesses); }, _set_count: function(value, idx, current, counter, name, domains) { if (current != this._current) { @@ -3765,7 +3772,7 @@ function eval_pyson(value){ to_display_prm.done(function() { for (var j in to_display.view.containers) { var container = widget.view.containers[j]; - container.resize(); + container.set_grid_template(); } to_display.display(to_display.record, to_display.field); });