[IMP] web: reduce shift when switching form mode

This commit does 4 things in order to reduce the shift when switching
mode in form view:

1. modifies the render function of many2one and x2many radio
   fields to render them the same in edit mode and read mode.
2. removes margins in inner form groups.
3. sets a minimum height on rows to align them.
4. empty fields are now visible. (as a blank line)

task 2330101
This commit is contained in:
Michael Mattiello (mcm)
2021-02-02 12:40:22 +00:00
parent 785b304876
commit 288b24cbdf
9 changed files with 141 additions and 34 deletions
@@ -501,6 +501,8 @@ var FieldMany2One = AbstractField.extend({
_renderEdit: function () {
var value = this.m2o_value;
this.$('.o_field_many2one_extra').html(this._renderValueLines(false));
// this is a stupid hack necessary to support the always_reload flag.
// the field value has been reread by the basic model. We use it to
// display the full address of a partner, separated by \n. This is
@@ -516,15 +518,24 @@ var FieldMany2One = AbstractField.extend({
}
this._updateExternalButton();
},
/**
* @private
* @param {boolean} needFirstLine
* @returns {string} escaped html of value lines
*/
_renderValueLines: function (needFirstLine) {
const escapedValue = _.escape((this.m2o_value || "").trim());
const lines = escapedValue.split('\n');
if (!needFirstLine) {
lines.shift();
}
return lines.map((line) => `<span>${line}</span>`).join('<br/>');
},
/**
* @private
*/
_renderReadonly: function () {
var escapedValue = _.escape((this.m2o_value || "").trim());
var value = escapedValue.split('\n').map(function (line) {
return '<span>' + line + '</span>';
}).join('<br/>');
this.$el.html(value);
this.$el.html(this._renderValueLines(true));
if (!this.noOpen && this.value) {
this.$el.attr('href', _.str.sprintf('#id=%s&model=%s', this.value.res_id, this.field.relation));
this.$el.addClass('o_form_uri');
@@ -2923,7 +2934,7 @@ var FieldSelection = AbstractField.extend({
this._super.apply(this, arguments);
if (!this.attrs.modifiersValue.invisible && this.mode !== 'readonly') {
this._setValues();
this._renderEdit();
this._render();
}
},
@@ -3017,7 +3028,7 @@ var FieldRadio = FieldSelection.extend({
description: _lt("Radio"),
template: null,
className: 'o_field_radio',
tagName: 'span',
tagName: 'div',
specialData: "_fetchSpecialMany2ones",
supportedFieldTypes: ['selection', 'many2one'],
events: _.extend({}, AbstractField.prototype.events, {
@@ -3028,10 +3039,7 @@ var FieldRadio = FieldSelection.extend({
*/
init: function () {
this._super.apply(this, arguments);
if (this.mode === 'edit') {
this.tagName = 'div';
this.className += this.nodeOptions.horizontal ? ' o_horizontal' : ' o_vertical';
}
this.className += this.nodeOptions.horizontal ? ' o_horizontal' : ' o_vertical';
this.unique_id = _.uniqueId("radio");
this._setValues();
},
@@ -3077,7 +3085,7 @@ var FieldRadio = FieldSelection.extend({
* @private
* @override
*/
_renderEdit: function () {
_render: function () {
var self = this;
var currentValue;
if (this.field.type === 'many2one') {
@@ -3095,6 +3103,7 @@ var FieldRadio = FieldSelection.extend({
index: index,
name: self.unique_id,
value: value,
disabled: self.mode !== 'edit',
}));
});
},
@@ -1618,16 +1618,20 @@ var BasicModel = AbstractModel.extend({
}
var rel_data = _.pick(data, 'id', 'display_name');
const viewType = options.viewType || record.viewType;
const fieldInfo = record.fieldsInfo[viewType][fieldName] || {};
const fieldOptions = fieldInfo.options || {};
// the reference field doesn't store its co-model in its field metadata
// but directly in the data (as the co-model isn't fixed)
var def;
if (rel_data.display_name === undefined) {
if (rel_data.display_name === undefined || fieldOptions.always_reload) {
// TODO: refactor this to use _fetchNameGet
def = this._rpc({
model: coModel,
method: 'name_get',
args: [data.id],
context: record.context,
context: this._getContext(record, { fieldName, viewType }),
})
.then(function (result) {
rel_data.display_name = result[0][1];
+17 -2
View File
@@ -12,8 +12,8 @@
}
// Empty
.o_field_empty {
display: none!important;
.o_field_empty:empty {
min-height: $font-size-base * $line-height-base;
}
// Numbers
@@ -87,6 +87,21 @@
}
}
// Many2One
&.o_field_many2one {
flex-direction: column;
.o_field_many2one_selection {
display: flex;
width: 100%;
}
.o_external_button {
padding-top: 0;
padding-bottom: 0;
}
}
// Many2OneAvatar
&.o_field_many2one_avatar {
> img.o_m2o_avatar {
@@ -22,6 +22,7 @@
// Many2one
&.o_field_many2one .o_external_button {
flex: 0 0 auto;
padding: 0;
margin-left: 2px;
font-size: 19px;
+19 -1
View File
@@ -486,6 +486,12 @@
.o_priority > .o_priority_star {
font-size: inherit;
}
> h1 {
min-height: 55px;
}
> h2 {
min-height: 43px;
}
}
// Avatar
@@ -507,6 +513,10 @@
width: 100%;
margin: 10px 0;
.o_group {
margin: 0;
}
// o_group contains nested groups
@for $i from 1 through $o-form-group-cols {
.o_group_col_#{$i} {
@@ -554,7 +564,7 @@
// it does not really matter
// Makes extra buttons (e.g. m2o external button) overflow on the
// right padding of the parent element
> .o_input_dropdown {
.o_input_dropdown {
flex: 1 0 auto;
}
}
@@ -605,6 +615,14 @@
margin-bottom: 0px;
}
}
td.o_td_label .o_form_label {
min-height: 33px;
}
td:not(.o_field_cell) .o_form_uri > span:first-child {
display: inline-block;
padding: 1px 0;
margin-bottom: 1px;
}
// Translate icon
span.o_field_translate {
+1 -1
View File
@@ -275,7 +275,7 @@
}
> .o_field_widget {
width: 100%;
> .o_external_button {
.o_external_button {
padding: 0;
border: none;
background-color: inherit;
+13 -10
View File
@@ -1235,7 +1235,7 @@
</t>
<t t-name="FieldRadio.button">
<div class="custom-control custom-radio o_radio_item" aria-atomic="true">
<input type="radio" class="custom-control-input o_radio_input" t-att-checked="checked ? true : undefined"
<input type="radio" class="custom-control-input o_radio_input" t-att-checked="checked ? true : undefined" t-att-disabled="disabled ? true : undefined"
t-att-name="name" t-att-data-value="value[0]" t-att-data-index="index" t-att-id="id"/>
<label class="custom-control-label o_form_label" t-att-for="id"><t t-esc="value[1]"/></label>
</div>
@@ -1253,16 +1253,19 @@
<span t-if="widget.noOpen"/>
</t>
<div t-if="widget.mode === 'edit'" class="o_field_widget o_field_many2one" aria-atomic="true">
<div class="o_input_dropdown">
<input type="text" class="o_input"
t-att-barcode_events="widget.nodeOptions.barcode_events"
t-att-tabindex="widget.attrs.tabindex"
t-att-autofocus="widget.attrs.autofocus"
t-att-placeholder="widget.attrs.placeholder"
t-att-id="widget.idForLabel"/>
<a role="button" class="o_dropdown_button" draggable="false"/>
<div class="o_field_many2one_selection">
<div class="o_input_dropdown">
<input type="text" class="o_input"
t-att-barcode_events="widget.nodeOptions.barcode_events"
t-att-tabindex="widget.attrs.tabindex"
t-att-autofocus="widget.attrs.autofocus"
t-att-placeholder="widget.attrs.placeholder"
t-att-id="widget.idForLabel"/>
<a role="button" class="o_dropdown_button" draggable="false"/>
</div>
<button type="button" t-if="!widget.noOpen" class="fa fa-external-link btn btn-secondary o_external_button" tabindex="-1" draggable="false" aria-label="External link" title="External link"/>
</div>
<button type="button" t-if="!widget.noOpen" class="fa fa-external-link btn btn-secondary o_external_button" tabindex="-1" draggable="false" aria-label="External link" title="External link"/>
<div class="o_field_many2one_extra"/>
</div>
</t>
@@ -328,7 +328,7 @@ QUnit.module('fields', {}, function () {
});
QUnit.test('many2ones in form views with show_address', async function (assert) {
assert.expect(4);
assert.expect(6);
var form = await createView({
View: FormView,
model: 'partner',
@@ -360,6 +360,11 @@ QUnit.module('fields', {}, function () {
assert.strictEqual(form.$('a.o_form_uri').html(), '<span>aaa</span><br><span>Street</span><br><span>City ZIP</span>',
"input should have a multi-line content in readonly due to show_address");
await testUtils.form.clickEdit(form);
assert.strictEqual(form.$('input.o_input').val(), 'aaa');
assert.strictEqual(form.$('.o_field_many2one_extra').html(),
'<span>Street</span><br><span>City ZIP</span>');
assert.containsOnce(form, 'button.o_external_button:visible',
"should have an open record button");
@@ -374,6 +379,57 @@ QUnit.module('fields', {}, function () {
form.destroy();
});
QUnit.test('many2one show_address in edit', async function (assert) {
assert.expect(6);
const addresses = {
"aaa": "\nAAA\nRecord",
"first record": "\nFirst\nRecord",
"second record": "\nSecond\nRecord",
};
const form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch: `
<form><sheet><group>
<field name="trululu" context="{'show_address': 1}" options="{'always_reload': True}"/>
</group></sheet></form>
`,
mockRPC: function (route, args) {
if (args.method === 'name_get') {
return this._super(route, args).then(function (result) {
result[0][1] += addresses[result[0][1]];
return result;
});
}
return this._super(route, args);
},
res_id: 1,
});
await testUtils.form.clickEdit(form);
assert.strictEqual(form.$('input').val(), 'aaa');
assert.strictEqual(form.$('.o_field_many2one_extra').html(),
'<span>AAA</span><br><span>Record</span>');
await testUtils.fields.editInput(form.$('input'), 'first record');
await testUtils.fields.many2one.clickHighlightedItem('trululu');
assert.strictEqual(form.$('input').val(), 'first record');
assert.strictEqual(form.$('.o_field_many2one_extra').html(),
'<span>First</span><br><span>Record</span>');
await testUtils.fields.editInput(form.$('input'), 'second record');
await testUtils.fields.many2one.clickHighlightedItem('trululu');
assert.strictEqual(form.$('input').val(), 'second record');
assert.strictEqual(form.$('.o_field_many2one_extra').html(),
'<span>Second</span><br><span>Record</span>');
form.destroy();
});
QUnit.test('show_address works in a view embedded in a view of another type', async function (assert) {
assert.expect(1);
@@ -2172,7 +2172,7 @@ QUnit.module('relational_fields', {
});
QUnit.test('fieldradio widget with numerical keys encoded as strings', async function (assert) {
assert.expect(5);
assert.expect(7);
this.data.partner.fields.selection = {
type: 'selection',
@@ -2197,8 +2197,8 @@ QUnit.module('relational_fields', {
});
assert.strictEqual(form.$('.o_field_widget').text(), '',
"field should be unset");
assert.strictEqual(form.$('.o_field_widget').text().trim().split(/\s+/g).join(','), 'Red,Black');
assert.containsNone(form, '.o_radio_input:checked', "no value should be checked");
await testUtils.form.clickEdit(form);
@@ -2209,8 +2209,9 @@ QUnit.module('relational_fields', {
await testUtils.form.clickSave(form);
assert.strictEqual(form.$('.o_field_widget').text(), 'Black',
"value should be 'Black'");
assert.strictEqual(form.$('.o_field_widget').text().trim().split(/\s+/g).join(','), 'Red,Black');
assert.containsOnce(form, '.o_radio_input[data-index=1]:checked',
"'Black' should be checked");
await testUtils.form.clickEdit(form);