[REF] web,*: introduce the concept of AbstractAction

Before this commit, a client action was simply a subclass of Widget.
This is quite simple, but not really enough for the ActionManager. The
ActionManager needs to handle client actions in a different way that
view actions. Sometimes, it needs to check if a method exists before
calling it.

With this commit, we introduce a new abstraction: the AbstractAction
class, which defines a common API for all actions managed by the web
client.

Note that the API proposed in this commit is just the beginning. We will
introduce other methods in the future, to manage the focus, or the
scroll position, for example.
This commit is contained in:
Géry Debongnie
2018-03-08 16:48:54 +01:00
parent 7ca93cd916
commit b17ba359db
21 changed files with 125 additions and 61 deletions
@@ -1,6 +1,7 @@
odoo.define('account.ReconciliationClientAction', function (require) {
"use strict";
var AbstractAction = require('web.AbstractAction');
var ReconciliationModel = require('account.ReconciliationModel');
var ReconciliationRenderer = require('account.ReconciliationRenderer');
var ControlPanelMixin = require('web.ControlPanelMixin');
@@ -11,7 +12,7 @@ var core = require('web.core');
/**
* Widget used as action for 'account.bank.statement' reconciliation
*/
var StatementAction = Widget.extend(ControlPanelMixin, {
var StatementAction = AbstractAction.extend(ControlPanelMixin, {
title: core._t('Bank reconciliation'),
template: 'reconciliation',
custom_events: {
@@ -1,11 +1,11 @@
odoo.define('base_import.import', function (require) {
"use strict";
var AbstractAction = require('web.AbstractAction');
var ControlPanelMixin = require('web.ControlPanelMixin');
var core = require('web.core');
var session = require('web.session');
var time = require('web.time');
var Widget = require('web.Widget');
var QWeb = core.qweb;
var _t = core._t;
@@ -44,7 +44,7 @@ function jsonp(form, attributes, callback) {
$(form).ajaxSubmit(attributes);
}
var DataImport = Widget.extend(ControlPanelMixin, {
var DataImport = AbstractAction.extend(ControlPanelMixin, {
template: 'ImportView',
opts: [
{name: 'encoding', label: _lt("Encoding:"), value: 'utf-8'},
@@ -1,13 +1,13 @@
odoo.define('hr_attendance.greeting_message', function (require) {
"use strict";
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
var Widget = require('web.Widget');
var _t = core._t;
var GreetingMessage = Widget.extend({
var GreetingMessage = AbstractAction.extend({
template: 'HrAttendanceGreetingMessage',
events: {
@@ -1,12 +1,12 @@
odoo.define('hr_attendance.kiosk_confirm', function (require) {
"use strict";
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
var Widget = require('web.Widget');
var QWeb = core.qweb;
var KioskConfirm = Widget.extend({
var KioskConfirm = AbstractAction.extend({
events: {
"click .o_hr_attendance_back_button": function () { this.do_action(this.next_action, {clear_breadcrumbs: true}); },
"click .o_hr_attendance_sign_in_out_icon": function () {
@@ -1,13 +1,14 @@
odoo.define('hr_attendance.kiosk_mode', function (require) {
"use strict";
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
var Widget = require('web.Widget');
var Session = require('web.session');
var QWeb = core.qweb;
var KioskMode = Widget.extend({
var KioskMode = AbstractAction.extend({
events: {
"click .o_hr_attendance_button_employees": function(){ this.do_action('hr_attendance.hr_employee_attendance_action_kanban'); },
},
@@ -1,14 +1,14 @@
odoo.define('hr_attendance.my_attendances', function (require) {
"use strict";
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
var Widget = require('web.Widget');
var QWeb = core.qweb;
var _t = core._t;
var MyAttendances = Widget.extend({
var MyAttendances = AbstractAction.extend({
events: {
"click .o_hr_attendance_sign_in_out_icon": function() {
this.$('.o_hr_attendance_sign_in_out_icon').attr("disabled", "disabled");
+2 -4
View File
@@ -1,13 +1,11 @@
odoo.define('iap.redirect_odoo_credit_widget', function(require) {
"use strict";
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
var framework = require('web.framework');
var Widget = require('web.Widget');
var QWeb = core.qweb;
var IapOdooCreditRedirect = Widget.extend({
var IapOdooCreditRedirect = AbstractAction.extend({
template: 'iap.redirect_to_odoo_credit',
events : {
"click .redirect_confirm" : "odoo_redirect",
+2 -2
View File
@@ -4,6 +4,7 @@ odoo.define('mail.chat_discuss', function (require) {
var ChatThread = require('mail.ChatThread');
var composer = require('mail.composer');
var AbstractAction = require('web.AbstractAction');
var config = require('web.config');
var ControlPanelMixin = require('web.ControlPanelMixin');
var core = require('web.core');
@@ -13,7 +14,6 @@ var dom = require('web.dom');
var pyeval = require('web.pyeval');
var SearchView = require('web.SearchView');
var session = require('web.session');
var Widget = require('web.Widget');
var QWeb = core.qweb;
var _t = core._t;
@@ -106,7 +106,7 @@ var PartnerInviteDialog = Dialog.extend({
},
});
var Discuss = Widget.extend(ControlPanelMixin, {
var Discuss = AbstractAction.extend(ControlPanelMixin, {
template: 'mail.discuss',
custom_events: {
search: '_onSearch',
+2 -1
View File
@@ -5,6 +5,7 @@ var PosBaseWidget = require('point_of_sale.BaseWidget');
var gui = require('point_of_sale.gui');
var keyboard = require('point_of_sale.keyboard');
var models = require('point_of_sale.models');
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
var ajax = require('web.ajax');
var CrashManager = require('web.CrashManager');
@@ -564,7 +565,7 @@ var ClientScreenWidget = PosBaseWidget.extend({
// - .gui which controls the switching between
// screens and the showing/closing of popups
var Chrome = PosBaseWidget.extend({
var Chrome = PosBaseWidget.extend(AbstractAction.prototype, {
template: 'Chrome',
init: function() {
var self = this;
@@ -1,16 +1,16 @@
odoo.define('sale_timesheet.ProjectPlan', function (require) {
'use strict';
var AbstractAction = require('web.AbstractAction');
var ControlPanelMixin = require('web.ControlPanelMixin');
var core = require('web.core');
var data = require('web.data');
var pyeval = require('web.pyeval');
var SearchView = require('web.SearchView');
var Widget = require('web.Widget');
var _t = core._t;
var ProjectPlan = Widget.extend(ControlPanelMixin, {
var ProjectPlan = AbstractAction.extend(ControlPanelMixin, {
events: {
"click a[type='action']": "_onClickAction",
"click .o_timesheet_plan_redirect": '_onRedirect',
@@ -1,8 +1,8 @@
odoo.define('stock.stock_report_generic', function (require) {
'use strict';
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
var Widget = require('web.Widget');
var ControlPanelMixin = require('web.ControlPanelMixin');
var session = require('web.session');
var ReportWidget = require('stock.ReportWidget');
@@ -11,7 +11,7 @@ var crash_manager = require('web.crash_manager');
var QWeb = core.qweb;
var stock_report_generic = Widget.extend(ControlPanelMixin, {
var stock_report_generic = AbstractAction.extend(ControlPanelMixin, {
// Stores all the parameters of the action.
init: function(parent, action) {
this.actionManager = parent;
+2 -2
View File
@@ -1,16 +1,16 @@
odoo.define('web.Apps', function (require) {
"use strict";
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
var framework = require('web.framework');
var session = require('web.session');
var Widget = require('web.Widget');
var _t = core._t;
var apps_client = null;
var Apps = Widget.extend({
var Apps = AbstractAction.extend({
template: 'EmptyComponent',
remote_action_tag: 'loempia.embed',
failback_action_id: 'base.open_module_tree',
@@ -0,0 +1,67 @@
odoo.define('web.AbstractAction', function (require) {
"use strict";
/**
* We define here the generic notion of Action (from the point of view of the
* web client). In short, an action is a widget which controls the main part
* of the screen (everything below the control panel).
*
* More precisely, the action manager is the component that coordinates a stack
* of actions. Whenever the user navigates in the interface, switches views,
* open different menus, the action manager creates/updates/destroys special
* widgets which are actions. These actions need to answer to a standardised
* API, which is the reason this AbstractAction class.
*
* In practice, most actions are view controllers (coming from a
* ir.action.act_window). However, some actions are 'client actions'. They
* also need to be an AbstractAction for a better cooperation with the action
* manager.
*
* @module web.AbstractAction
*/
var Widget = require('web.Widget');
var AbstractAction = Widget.extend({
/**
* Called each time the action is attached into the DOM.
*/
on_attach_callback: function () {},
/**
* Called each time the action is detached from the DOM.
*/
on_detach_callback: function () {},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* In some situations, we need confirmation from the controller that the
* current state can be destroyed without prejudice to the user. For
* example, if the user has edited a form, maybe we should ask him if we
* can discard all his changes when we switch to another action. In that
* case, the action manager will call this method. If the returned
* deferred is succesfully resolved, then we can destroy the current action,
* otherwise, we need to stop.
*
* @returns {Deferred} resolved if the action can be removed, rejected otherwise
*/
canBeRemoved: function () {
return $.when();
},
/**
* Renders the buttons to append, in most cases, to the control panel (in
* the bottom left corner). When the action is rendered in a dialog, those
* buttons might be moved to the dialog's footer.
*
* @param {jQuery Node} $node
*/
renderButtons: function ($node) {
},
});
return AbstractAction;
});
@@ -9,6 +9,7 @@ odoo.define('web.ActionManager', function (require) {
* coordinated.
*/
var AbstractAction = require('web.AbstractAction');
var Bus = require('web.Bus');
var concurrency = require('web.concurrency');
var Context = require('web.Context');
@@ -79,7 +80,7 @@ var ActionManager = Widget.extend({
on_attach_callback: function() {
this.isInDOM = true;
var currentController = this.getCurrentController();
if (currentController && currentController.widget.on_attach_callback) {
if (currentController) {
currentController.widget.on_attach_callback();
}
},
@@ -89,7 +90,7 @@ var ActionManager = Widget.extend({
on_detach_callback: function() {
this.isInDOM = false;
var currentController = this.getCurrentController();
if (currentController && currentController.widget.on_detach_callback) {
if (currentController) {
currentController.widget.on_detach_callback();
}
},
@@ -109,13 +110,8 @@ var ActionManager = Widget.extend({
*/
clearUncommittedChanges: function () {
var currentController = this.getCurrentController();
// AAB: with AbstractAction, the second part of the condition won't be
// necessary anymore, as there will be such a function it its API
if (currentController && currentController.widget.discardChanges) {
return currentController.widget.discardChanges(undefined, {
// AAB: get rid of this option when on_hashchange mechanism is improved
readonlyIfRealDiscard: true,
});
if (currentController) {
return currentController.widget.canBeRemoved();
}
return $.when();
},
@@ -405,11 +401,7 @@ var ActionManager = Widget.extend({
in_DOM: true,
callbacks: [{widget: dialog}],
});
// AAB: renderButtons will be a function of AbstractAction, so this
// test won't be necessary anymore
if (widget.renderButtons) {
widget.renderButtons(dialog.$footer);
}
widget.renderButtons(dialog.$footer);
self.currentDialogController = controller;
@@ -435,6 +427,9 @@ var ActionManager = Widget.extend({
console.error("Could not find client action " + action.tag, action);
return $.Deferred().reject();
}
if (!(ClientAction.prototype instanceof AbstractAction)) {
console.warn('The client action ' + action.tag + ' should be an instance of AbstractAction!');
}
if (!(ClientAction.prototype instanceof Widget)) {
// the client action might be a function, which is executed and
// whose returned value might be another action to execute
@@ -1,11 +1,11 @@
odoo.define('report.client_action', function (require) {
'use strict';
var AbstractAction = require('web.AbstractAction');
var config = require('web.config');
var core = require('web.core');
var ControlPanelMixin = require('web.ControlPanelMixin');
var session = require('web.session');
var Widget = require('web.Widget');
var utils = require('report.utils');
var QWeb = core.qweb;
@@ -17,7 +17,7 @@ var AUTHORIZED_MESSAGES = [
'report:do_action',
];
var ReportAction = Widget.extend(ControlPanelMixin, {
var ReportAction = AbstractAction.extend(ControlPanelMixin, {
template: 'report.client_action',
@@ -14,11 +14,11 @@ odoo.define('web.AbstractController', function (require) {
var ControlPanelMixin = require('web.ControlPanelMixin');
var core = require('web.core');
var Widget = require('web.Widget');
var AbstractAction = require('web.AbstractAction');
var QWeb = core.qweb;
var AbstractController = Widget.extend(ControlPanelMixin, {
var AbstractController = AbstractAction.extend(ControlPanelMixin, {
custom_events: {
open_record: '_onOpenRecord',
switch_view: '_onSwitchView',
@@ -107,6 +107,13 @@ var AbstractController = Widget.extend(ControlPanelMixin, {
// Public
//--------------------------------------------------------------------------
/**
* @override
*/
canBeRemoved: function () {
// AAB: get rid of this option when on_hashchange mechanism is improved
return this.discardChanges(undefined, {readonlyIfRealDiscard: true});
},
/**
* Discards the changes made on the record associated to the given ID, or
* all changes made by the current controller if no recordID is given. For
@@ -167,15 +174,6 @@ var AbstractController = Widget.extend(ControlPanelMixin, {
reload: function (params) {
return this.update(params || {});
},
/**
* Renders the buttons to append, in most cases, to the control panel (in
* the bottom left corner). When the view is rendered in a dialog, those
* buttons might be moved to the dialog's footer.
*
* @param {jQuery Node} $node
*/
renderButtons: function ($node) {
},
/**
* For views that require a pager, this method will be called to allow the
* controller to instantiate and render a pager. Note that in theory, the
@@ -6,14 +6,14 @@ odoo.define('web.ChangePassword', function (require) {
* allows the user to change his password.
*/
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
var Dialog = require('web.Dialog');
var Widget = require('web.Widget');
var web_client = require('web.web_client');
var _t = core._t;
var ChangePassword = Widget.extend({
var ChangePassword = AbstractAction.extend({
template: "ChangePassword",
/**
@@ -3,6 +3,7 @@ odoo.define('web.action_manager_tests', function (require) {
var ReportClientAction = require('report.client_action');
var AbstractAction = require('web.AbstractAction');
var ControlPanelMixin = require('web.ControlPanelMixin');
var core = require('web.core');
var ReportService = require('web.ReportService');
@@ -485,7 +486,7 @@ QUnit.module('ActionManager', {
QUnit.test('properly load client actions', function (assert) {
assert.expect(2);
var ClientAction = Widget.extend({
var ClientAction = AbstractAction.extend({
className: 'o_client_action_test',
start: function () {
this.$el.text('Hello World');
@@ -781,7 +782,7 @@ QUnit.module('ActionManager', {
QUnit.test('change a param of an ir.actions.client in the url', function (assert) {
assert.expect(7);
var ClientAction = Widget.extend(ControlPanelMixin, {
var ClientAction = AbstractAction.extend(ControlPanelMixin, {
className: 'o_client_action',
init: function (parent, action) {
this._super.apply(this, arguments);
@@ -1205,7 +1206,7 @@ QUnit.module('ActionManager', {
QUnit.test('can execute client actions from tag name', function (assert) {
assert.expect(3);
var ClientAction = Widget.extend({
var ClientAction = AbstractAction.extend({
className: 'o_client_action_test',
start: function () {
this.$el.text('Hello World');
@@ -1234,7 +1235,7 @@ QUnit.module('ActionManager', {
QUnit.test('client action with control panel', function (assert) {
assert.expect(4);
var ClientAction = Widget.extend(ControlPanelMixin, {
var ClientAction = AbstractAction.extend(ControlPanelMixin, {
className: 'o_client_action_test',
start: function () {
this.$el.text('Hello World');
@@ -1262,7 +1263,7 @@ QUnit.module('ActionManager', {
QUnit.test('state is pushed for client actions', function (assert) {
assert.expect(2);
var ClientAction = Widget.extend(ControlPanelMixin, {
var ClientAction = AbstractAction.extend(ControlPanelMixin, {
className: 'o_client_action_test',
start: function () {
this.$el.text('Hello World');
+1
View File
@@ -149,6 +149,7 @@
<script type="text/javascript" src="/web/static/src/js/core/domain.js"></script>
<script type="text/javascript" src="/web/static/src/js/core/pyeval.js"></script>
<script type="text/javascript" src="/web/static/src/js/chrome/abstract_action.js"></script>
<script type="text/javascript" src="/web/static/src/js/chrome/action_manager.js"></script>
<script type="text/javascript" src="/web/static/src/js/chrome/action_manager_act_window.js"></script>
<script type="text/javascript" src="/web/static/src/js/chrome/action_manager_report.js"/>
@@ -1,6 +1,7 @@
odoo.define('web_settings_dashboard', function (require) {
"use strict";
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
var framework = require('web.framework');
var Widget = require('web.Widget');
@@ -8,7 +9,7 @@ var Widget = require('web.Widget');
var QWeb = core.qweb;
var _t = core._t;
var Dashboard = Widget.extend({
var Dashboard = AbstractAction.extend({
template: 'DashboardMain',
init: function(){
@@ -1,6 +1,7 @@
odoo.define('website.backend.dashboard', function (require) {
'use strict';
var AbstractAction = require('web.AbstractAction');
var ajax = require('web.ajax');
var ControlPanelMixin = require('web.ControlPanelMixin');
var core = require('web.core');
@@ -8,12 +9,11 @@ var Dialog = require('web.Dialog');
var field_utils = require('web.field_utils');
var session = require('web.session');
var web_client = require('web.web_client');
var Widget = require('web.Widget');
var _t = core._t;
var QWeb = core.qweb;
var Dashboard = Widget.extend(ControlPanelMixin, {
var Dashboard = AbstractAction.extend(ControlPanelMixin, {
template: 'website.WebsiteDashboardMain',
cssLibs: [
'/web/static/lib/nvd3/nv.d3.css'