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);
});