[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 () {
|
||||
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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -275,7 +275,7 @@
|
||||
}
|
||||
> .o_field_widget {
|
||||
width: 100%;
|
||||
> .o_external_button {
|
||||
.o_external_button {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background-color: inherit;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user