[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:
@@ -501,6 +501,8 @@ var FieldMany2One = AbstractField.extend({
|
|||||||
_renderEdit: function () {
|
_renderEdit: function () {
|
||||||
var value = this.m2o_value;
|
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.
|
// 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
|
// 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
|
// display the full address of a partner, separated by \n. This is
|
||||||
@@ -516,15 +518,24 @@ var FieldMany2One = AbstractField.extend({
|
|||||||
}
|
}
|
||||||
this._updateExternalButton();
|
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
|
* @private
|
||||||
*/
|
*/
|
||||||
_renderReadonly: function () {
|
_renderReadonly: function () {
|
||||||
var escapedValue = _.escape((this.m2o_value || "").trim());
|
this.$el.html(this._renderValueLines(true));
|
||||||
var value = escapedValue.split('\n').map(function (line) {
|
|
||||||
return '<span>' + line + '</span>';
|
|
||||||
}).join('<br/>');
|
|
||||||
this.$el.html(value);
|
|
||||||
if (!this.noOpen && this.value) {
|
if (!this.noOpen && this.value) {
|
||||||
this.$el.attr('href', _.str.sprintf('#id=%s&model=%s', this.value.res_id, this.field.relation));
|
this.$el.attr('href', _.str.sprintf('#id=%s&model=%s', this.value.res_id, this.field.relation));
|
||||||
this.$el.addClass('o_form_uri');
|
this.$el.addClass('o_form_uri');
|
||||||
@@ -2923,7 +2934,7 @@ var FieldSelection = AbstractField.extend({
|
|||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
if (!this.attrs.modifiersValue.invisible && this.mode !== 'readonly') {
|
if (!this.attrs.modifiersValue.invisible && this.mode !== 'readonly') {
|
||||||
this._setValues();
|
this._setValues();
|
||||||
this._renderEdit();
|
this._render();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -3017,7 +3028,7 @@ var FieldRadio = FieldSelection.extend({
|
|||||||
description: _lt("Radio"),
|
description: _lt("Radio"),
|
||||||
template: null,
|
template: null,
|
||||||
className: 'o_field_radio',
|
className: 'o_field_radio',
|
||||||
tagName: 'span',
|
tagName: 'div',
|
||||||
specialData: "_fetchSpecialMany2ones",
|
specialData: "_fetchSpecialMany2ones",
|
||||||
supportedFieldTypes: ['selection', 'many2one'],
|
supportedFieldTypes: ['selection', 'many2one'],
|
||||||
events: _.extend({}, AbstractField.prototype.events, {
|
events: _.extend({}, AbstractField.prototype.events, {
|
||||||
@@ -3028,10 +3039,7 @@ var FieldRadio = FieldSelection.extend({
|
|||||||
*/
|
*/
|
||||||
init: function () {
|
init: function () {
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
if (this.mode === 'edit') {
|
this.className += this.nodeOptions.horizontal ? ' o_horizontal' : ' o_vertical';
|
||||||
this.tagName = 'div';
|
|
||||||
this.className += this.nodeOptions.horizontal ? ' o_horizontal' : ' o_vertical';
|
|
||||||
}
|
|
||||||
this.unique_id = _.uniqueId("radio");
|
this.unique_id = _.uniqueId("radio");
|
||||||
this._setValues();
|
this._setValues();
|
||||||
},
|
},
|
||||||
@@ -3077,7 +3085,7 @@ var FieldRadio = FieldSelection.extend({
|
|||||||
* @private
|
* @private
|
||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
_renderEdit: function () {
|
_render: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
var currentValue;
|
var currentValue;
|
||||||
if (this.field.type === 'many2one') {
|
if (this.field.type === 'many2one') {
|
||||||
@@ -3095,6 +3103,7 @@ var FieldRadio = FieldSelection.extend({
|
|||||||
index: index,
|
index: index,
|
||||||
name: self.unique_id,
|
name: self.unique_id,
|
||||||
value: value,
|
value: value,
|
||||||
|
disabled: self.mode !== 'edit',
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1618,16 +1618,20 @@ var BasicModel = AbstractModel.extend({
|
|||||||
}
|
}
|
||||||
var rel_data = _.pick(data, 'id', 'display_name');
|
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
|
// 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)
|
// but directly in the data (as the co-model isn't fixed)
|
||||||
var def;
|
var def;
|
||||||
if (rel_data.display_name === undefined) {
|
if (rel_data.display_name === undefined || fieldOptions.always_reload) {
|
||||||
// TODO: refactor this to use _fetchNameGet
|
// TODO: refactor this to use _fetchNameGet
|
||||||
def = this._rpc({
|
def = this._rpc({
|
||||||
model: coModel,
|
model: coModel,
|
||||||
method: 'name_get',
|
method: 'name_get',
|
||||||
args: [data.id],
|
args: [data.id],
|
||||||
context: record.context,
|
context: this._getContext(record, { fieldName, viewType }),
|
||||||
})
|
})
|
||||||
.then(function (result) {
|
.then(function (result) {
|
||||||
rel_data.display_name = result[0][1];
|
rel_data.display_name = result[0][1];
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Empty
|
// Empty
|
||||||
.o_field_empty {
|
.o_field_empty:empty {
|
||||||
display: none!important;
|
min-height: $font-size-base * $line-height-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Numbers
|
// 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
|
// Many2OneAvatar
|
||||||
&.o_field_many2one_avatar {
|
&.o_field_many2one_avatar {
|
||||||
> img.o_m2o_avatar {
|
> img.o_m2o_avatar {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
// Many2one
|
// Many2one
|
||||||
&.o_field_many2one .o_external_button {
|
&.o_field_many2one .o_external_button {
|
||||||
|
flex: 0 0 auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
font-size: 19px;
|
font-size: 19px;
|
||||||
|
|||||||
@@ -486,6 +486,12 @@
|
|||||||
.o_priority > .o_priority_star {
|
.o_priority > .o_priority_star {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
|
> h1 {
|
||||||
|
min-height: 55px;
|
||||||
|
}
|
||||||
|
> h2 {
|
||||||
|
min-height: 43px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avatar
|
// Avatar
|
||||||
@@ -507,6 +513,10 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
|
|
||||||
|
.o_group {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
// o_group contains nested groups
|
// o_group contains nested groups
|
||||||
@for $i from 1 through $o-form-group-cols {
|
@for $i from 1 through $o-form-group-cols {
|
||||||
.o_group_col_#{$i} {
|
.o_group_col_#{$i} {
|
||||||
@@ -554,7 +564,7 @@
|
|||||||
// it does not really matter
|
// it does not really matter
|
||||||
// Makes extra buttons (e.g. m2o external button) overflow on the
|
// Makes extra buttons (e.g. m2o external button) overflow on the
|
||||||
// right padding of the parent element
|
// right padding of the parent element
|
||||||
> .o_input_dropdown {
|
.o_input_dropdown {
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -605,6 +615,14 @@
|
|||||||
margin-bottom: 0px;
|
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
|
// Translate icon
|
||||||
span.o_field_translate {
|
span.o_field_translate {
|
||||||
|
|||||||
@@ -275,7 +275,7 @@
|
|||||||
}
|
}
|
||||||
> .o_field_widget {
|
> .o_field_widget {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
> .o_external_button {
|
.o_external_button {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
|
|||||||
@@ -1235,7 +1235,7 @@
|
|||||||
</t>
|
</t>
|
||||||
<t t-name="FieldRadio.button">
|
<t t-name="FieldRadio.button">
|
||||||
<div class="custom-control custom-radio o_radio_item" aria-atomic="true">
|
<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"/>
|
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>
|
<label class="custom-control-label o_form_label" t-att-for="id"><t t-esc="value[1]"/></label>
|
||||||
</div>
|
</div>
|
||||||
@@ -1253,16 +1253,19 @@
|
|||||||
<span t-if="widget.noOpen"/>
|
<span t-if="widget.noOpen"/>
|
||||||
</t>
|
</t>
|
||||||
<div t-if="widget.mode === 'edit'" class="o_field_widget o_field_many2one" aria-atomic="true">
|
<div t-if="widget.mode === 'edit'" class="o_field_widget o_field_many2one" aria-atomic="true">
|
||||||
<div class="o_input_dropdown">
|
<div class="o_field_many2one_selection">
|
||||||
<input type="text" class="o_input"
|
<div class="o_input_dropdown">
|
||||||
t-att-barcode_events="widget.nodeOptions.barcode_events"
|
<input type="text" class="o_input"
|
||||||
t-att-tabindex="widget.attrs.tabindex"
|
t-att-barcode_events="widget.nodeOptions.barcode_events"
|
||||||
t-att-autofocus="widget.attrs.autofocus"
|
t-att-tabindex="widget.attrs.tabindex"
|
||||||
t-att-placeholder="widget.attrs.placeholder"
|
t-att-autofocus="widget.attrs.autofocus"
|
||||||
t-att-id="widget.idForLabel"/>
|
t-att-placeholder="widget.attrs.placeholder"
|
||||||
<a role="button" class="o_dropdown_button" draggable="false"/>
|
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>
|
</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>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
|
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ QUnit.module('fields', {}, function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test('many2ones in form views with show_address', async function (assert) {
|
QUnit.test('many2ones in form views with show_address', async function (assert) {
|
||||||
assert.expect(4);
|
assert.expect(6);
|
||||||
var form = await createView({
|
var form = await createView({
|
||||||
View: FormView,
|
View: FormView,
|
||||||
model: 'partner',
|
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>',
|
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");
|
"input should have a multi-line content in readonly due to show_address");
|
||||||
await testUtils.form.clickEdit(form);
|
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',
|
assert.containsOnce(form, 'button.o_external_button:visible',
|
||||||
"should have an open record button");
|
"should have an open record button");
|
||||||
|
|
||||||
@@ -374,6 +379,57 @@ QUnit.module('fields', {}, function () {
|
|||||||
form.destroy();
|
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) {
|
QUnit.test('show_address works in a view embedded in a view of another type', async function (assert) {
|
||||||
assert.expect(1);
|
assert.expect(1);
|
||||||
|
|
||||||
|
|||||||
@@ -2172,7 +2172,7 @@ QUnit.module('relational_fields', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
QUnit.test('fieldradio widget with numerical keys encoded as strings', async function (assert) {
|
QUnit.test('fieldradio widget with numerical keys encoded as strings', async function (assert) {
|
||||||
assert.expect(5);
|
assert.expect(7);
|
||||||
|
|
||||||
this.data.partner.fields.selection = {
|
this.data.partner.fields.selection = {
|
||||||
type: 'selection',
|
type: 'selection',
|
||||||
@@ -2197,8 +2197,8 @@ QUnit.module('relational_fields', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
assert.strictEqual(form.$('.o_field_widget').text(), '',
|
assert.strictEqual(form.$('.o_field_widget').text().trim().split(/\s+/g).join(','), 'Red,Black');
|
||||||
"field should be unset");
|
assert.containsNone(form, '.o_radio_input:checked', "no value should be checked");
|
||||||
|
|
||||||
await testUtils.form.clickEdit(form);
|
await testUtils.form.clickEdit(form);
|
||||||
|
|
||||||
@@ -2209,8 +2209,9 @@ QUnit.module('relational_fields', {
|
|||||||
|
|
||||||
await testUtils.form.clickSave(form);
|
await testUtils.form.clickSave(form);
|
||||||
|
|
||||||
assert.strictEqual(form.$('.o_field_widget').text(), 'Black',
|
assert.strictEqual(form.$('.o_field_widget').text().trim().split(/\s+/g).join(','), 'Red,Black');
|
||||||
"value should be 'Black'");
|
assert.containsOnce(form, '.o_radio_input[data-index=1]:checked',
|
||||||
|
"'Black' should be checked");
|
||||||
|
|
||||||
await testUtils.form.clickEdit(form);
|
await testUtils.form.clickEdit(form);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user