[FIX] web: Form stat button stays correctly disabled

A stat button set as disabled was used to just show some statistics
without an action on click. However, on tasks executed by the Form,
all the buttons would be temporarly set as disabled then set back to
enabled. This would lose the original state of the button and make them
all clickable.

The way it worked before v14.5 was most likely because the action
manager would not throw an error on an action with those "invalid"
arguments.

Also, a button without a type and name will now be set the disabled
attribute automatically.

closes odoo/odoo#78839

Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Simon Genin (ges)
2021-10-27 21:53:57 +00:00
parent 88d0c49604
commit 5f5e699baa
2 changed files with 114 additions and 14 deletions
@@ -52,6 +52,9 @@ var FormRenderer = BasicRenderer.extend({
// display them (e.g. in Studio, in "show invisible" mode). This flag
// allows to disable this optimization.
this.renderInvisible = false;
// Keeps track of buttons that are disabled momentarily and need to be renabled.
// Needed to compare with buttons that have to stay disabled all the time.
this.manuallyDisabledButtons = new Set();
},
/**
* @override
@@ -182,16 +185,24 @@ var FormRenderer = BasicRenderer.extend({
*
*/
disableButtons: function () {
this.$('.o_statusbar_buttons button, .oe_button_box button')
.attr('disabled', true);
const allButtons = this.$el[0].querySelectorAll('.o_statusbar_buttons button, .oe_button_box button');
for (const button of allButtons) {
if (!button.getAttribute("disabled")) {
this.manuallyDisabledButtons.add(button)
button.setAttribute("disabled", true)
}
}
},
/**
* Enable statusbar buttons and stat buttons so they can be clicked again
*
*/
enableButtons: function () {
this.$('.o_statusbar_buttons button, .oe_button_box button')
.removeAttr('disabled');
const allButtons = this.$el[0].querySelectorAll('.o_statusbar_buttons button, .oe_button_box button');
this.manuallyDisabledButtons.forEach((button) => {
button.removeAttribute("disabled");
});
this.manuallyDisabledButtons.clear();
},
/**
* Put the focus on the last activated widget.
@@ -814,11 +825,20 @@ var FormRenderer = BasicRenderer.extend({
var $button = viewUtils.renderButtonFromNode(node, {
extraClass: 'oe_stat_button',
});
// If there is no type nor name, it will not bind a click listener and will set the button as disabled
const buttonDoesStartAnAction = node.attrs.type || node.attrs.name;
if (!buttonDoesStartAnAction) {
$button[0].setAttribute("disabled", true);
}
$button.append(_.map(node.children, this._renderNode.bind(this)));
if (node.attrs.help) {
this._addButtonTooltip(node, $button);
}
this._addOnClickAction($button, node);
if (buttonDoesStartAnAction) {
this._addOnClickAction($button, node);
}
this._handleAttributes($button, node);
this._registerModifiers(node, this.state, $button);
return $button;
@@ -1109,6 +1129,7 @@ var FormRenderer = BasicRenderer.extend({
return Promise.all(defs).then(() => this.__renderView()).then(function () {
self._postProcessLabels();
self._updateView($form.contents());
self.manuallyDisabledButtons.clear();
if (self.state.res_id in self.alertFields) {
self.displayTranslationAlert();
}
@@ -1114,7 +1114,7 @@ QUnit.module('Views', {
});
QUnit.test('rendering stat buttons', async function (assert) {
QUnit.test('rendering stat buttons with action', async function (assert) {
assert.expect(3);
var form = await createView({
@@ -1123,11 +1123,11 @@ QUnit.module('Views', {
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<div name="button_box">' +
'<button class="oe_stat_button">' +
'<div name="button_box" class="oe_button_box">' +
'<button class="oe_stat_button" >' +
'<field name="int_field"/>' +
'</button>' +
'<button class="oe_stat_button" attrs=\'{"invisible": [["bar", "=", true]]}\'>' +
'<button class="oe_stat_button" name="some_action" type="action" attrs=\'{"invisible": [["bar", "=", true]]}\'>' +
'<field name="bar"/>' +
'</button>' +
'</div>' +
@@ -1147,7 +1147,86 @@ QUnit.module('Views', {
count++;
});
await testUtils.dom.click('.oe_stat_button');
assert.strictEqual(count, 1, "should have triggered a execute action");
assert.strictEqual(count, 0, "should have triggered an execute_action");
form.destroy();
});
QUnit.test('rendering stat buttons without action', async function (assert) {
assert.expect(4);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<div name="button_box" class="oe_button_box">' +
'<button class="oe_stat_button">' +
'<field name="int_field"/>' +
'</button>' +
'<button class="oe_stat_button" attrs=\'{"invisible": [["bar", "=", true]]}\'>' +
'<field name="bar"/>' +
'</button>' +
'</div>' +
'<group>' +
'<field name="foo"/>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 2,
});
assert.containsN(form, 'button.oe_stat_button', 2);
assert.containsOnce(form, 'button.oe_stat_button.o_invisible_modifier');
assert.containsN(form, 'button.oe_stat_button:disabled', 2);
var count = 0;
await testUtils.mock.intercept(form, "execute_action", function () {
count++;
});
await testUtils.dom.click('.oe_stat_button');
assert.strictEqual(count, 0, "should have not triggered an execute_action");
form.destroy();
});
QUnit.test('readonly stat buttons stays disabled', async function (assert) {
assert.expect(4);
var form = await createView({
View: FormView,
model: 'partner',
data: this.data,
arch:'<form string="Partners">' +
'<sheet>' +
'<div name="button_box" class="oe_button_box">' +
'<button class="oe_stat_button">' +
'<field name="int_field"/>' +
'</button>' +
'<button class="oe_stat_button" type="action" name="some_action">' +
'<field name="bar"/>' +
'</button>' +
'</div>' +
'<group>' +
'<button type="action" name="action_to_perform">Run an action</button>' +
'</group>' +
'</sheet>' +
'</form>',
res_id: 2,
});
var count = 0;
await testUtils.mock.intercept(form, "execute_action", function (event) {
if (event.data.action_data.name == "action_to_perform") {
assert.containsN(form, 'button.oe_stat_button[disabled]', 2, "While performing the action, both buttons should be disabled.");
event.data.on_success();
}
});
assert.containsN(form, 'button.oe_stat_button', 2);
assert.containsN(form, 'button.oe_stat_button[disabled]', 1);
await testUtils.dom.click('button[name=action_to_perform]');
assert.containsN(form, 'button.oe_stat_button[disabled]', 1, "After performing the action, only one button should be disabled.");
form.destroy();
});
@@ -2741,7 +2820,7 @@ QUnit.module('Views', {
arch:'<form string="Partners">' +
'<sheet>' +
'<div name="button_box">' +
'<button class="oe_stat_button">' +
'<button class="oe_stat_button" name="some_action" type="action">' +
'<field name="bar"/>' +
'</button>' +
'</div>' +
@@ -7137,7 +7216,7 @@ QUnit.module('Views', {
'</header>' +
'<sheet>' +
'<div name="button_box" class="oe_button_box">' +
'<button class="oe_stat_button">' +
'<button class="oe_stat_button" name="some_action" type="action">' +
'<field name="bar"/>' +
'</button>' +
'</div>' +
@@ -7201,7 +7280,7 @@ QUnit.module('Views', {
'</header>' +
'<sheet>' +
'<div name="button_box" class="oe_button_box">' +
'<button class="oe_stat_button">' +
'<button class="oe_stat_button" name="some_action" type="action">' +
'<field name="bar"/>' +
'</button>' +
'</div>' +
@@ -7314,7 +7393,7 @@ QUnit.module('Views', {
'partner,false,form': '<form>' +
'<sheet>' +
'<div name="button_box" class="oe_button_box">' +
'<button class="oe_stat_button">' +
'<button class="oe_stat_button" name="some_action" type="action">' +
'<field name="bar"/>' +
'</button>' +
'</div>' +