Use grid layout to implement form container in sao

Closes #11966
This commit is contained in:
Nicolas Évrard
2023-10-02 12:54:03 +02:00
parent fa5ae37c46
commit 583d911bc8
3 changed files with 233 additions and 177 deletions
+1
View File
@@ -1,3 +1,4 @@
* Use grid layout for form container
* Support scanning code
* Reset search offset when domain has changed
* Add a canonicalize function for domains
+59 -14
View File
@@ -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;
&.hidden {
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,
@@ -795,6 +787,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 {
@@ -911,6 +946,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) {
+173 -163
View File
@@ -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();
}
},
@@ -381,15 +384,25 @@ function eval_pyson(value){
})
.done(() => {
var record = this.record;
var j;
var j, prm;
var promesses = [];
for (j in this.state_widgets) {
var state_widget = this.state_widgets[j];
state_widget.set_state(record);
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() {
@@ -522,25 +535,25 @@ function eval_pyson(value){
init: function(col=4) {
if (col < 0) col = 0;
this.col = col;
this.el = jQuery('<table/>', {
'class': 'form-container responsive responsive-noheader'
this.el = jQuery('<div/>', {
'class': 'form-container'
});
this.body = jQuery('<tbody/>').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('<tr/>'));
},
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;
@@ -549,198 +562,191 @@ function eval_pyson(value){
if (xfill === undefined) xfill = 1;
var xexpand = attributes.xexpand;
if (xexpand === undefined) xexpand = 1;
var row = this.row();
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();
}
// CSS grid elements are 1-indexed
if ((this.col > 0) && ((this._col + colspan) > (this.col + 1))) {
this._col = 1;
this._row += 1;
}
var el;
if (widget) {
el = widget.el;
}
var cell = jQuery('<td/>', {
'colspan': colspan,
'class': widget ? widget.class_ || '' : ''
var cell = jQuery('<div/>', {
'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;
}
});
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]);
}
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]));
}
}
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 (!jQuery.isEmptyObject(xexpands)) {
has_expand = true;
if (!found) {
this._xexpand.add(
Math.round((span[0] + span[span.length - 1]) / 2));
}
}
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');
}
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 {
cell.css('width', '');
this._grid_cols.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();
}
}
i += colspan;
}
}
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 = 1; i <= col; i++) {
this._grid_cols.push("min-content");
}
}
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 {
this._grid_rows.push('min-content');
}
}
} 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;
}
}
if (empty) {
e.addClass('hidden');
} else {
e.removeClass('hidden');
}
return empty;
};
for (i = 0; i < grid_cols.length; i++) {
col = cols[i];
if (col.every(is_empty)) {
grid_cols[i] = "0px";
}
}
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(" "));
}
});
@@ -1049,6 +1055,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];
@@ -1068,7 +1075,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': [
@@ -1078,9 +1085,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],
@@ -1089,8 +1097,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) {