diff --git a/addons/account/static/src/js/reconciliation/reconciliation_action.js b/addons/account/static/src/js/reconciliation/reconciliation_action.js
index b047ed8cdaf..9cfeb3798ca 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_action.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_action.js
@@ -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: {
diff --git a/addons/base_import/static/src/js/import_action.js b/addons/base_import/static/src/js/import_action.js
index 11b81a6373a..03c258868a1 100644
--- a/addons/base_import/static/src/js/import_action.js
+++ b/addons/base_import/static/src/js/import_action.js
@@ -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'},
diff --git a/addons/hr_attendance/static/src/js/greeting_message.js b/addons/hr_attendance/static/src/js/greeting_message.js
index ac6e3f86c73..406a0c88017 100644
--- a/addons/hr_attendance/static/src/js/greeting_message.js
+++ b/addons/hr_attendance/static/src/js/greeting_message.js
@@ -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: {
diff --git a/addons/hr_attendance/static/src/js/kiosk_confirm.js b/addons/hr_attendance/static/src/js/kiosk_confirm.js
index 2f55edd42c1..fc976c20b2a 100644
--- a/addons/hr_attendance/static/src/js/kiosk_confirm.js
+++ b/addons/hr_attendance/static/src/js/kiosk_confirm.js
@@ -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 () {
diff --git a/addons/hr_attendance/static/src/js/kiosk_mode.js b/addons/hr_attendance/static/src/js/kiosk_mode.js
index d561f380c45..59bd0c3f71e 100644
--- a/addons/hr_attendance/static/src/js/kiosk_mode.js
+++ b/addons/hr_attendance/static/src/js/kiosk_mode.js
@@ -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'); },
},
diff --git a/addons/hr_attendance/static/src/js/my_attendances.js b/addons/hr_attendance/static/src/js/my_attendances.js
index d8f68a3c884..4a19b2b9b28 100644
--- a/addons/hr_attendance/static/src/js/my_attendances.js
+++ b/addons/hr_attendance/static/src/js/my_attendances.js
@@ -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");
diff --git a/addons/iap/static/src/js/iap_credit.js b/addons/iap/static/src/js/iap_credit.js
index 37cddd72aa8..baabd7bd980 100644
--- a/addons/iap/static/src/js/iap_credit.js
+++ b/addons/iap/static/src/js/iap_credit.js
@@ -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",
diff --git a/addons/mail/static/src/js/discuss.js b/addons/mail/static/src/js/discuss.js
index 8d682ddf8ef..f70cec0116c 100644
--- a/addons/mail/static/src/js/discuss.js
+++ b/addons/mail/static/src/js/discuss.js
@@ -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',
diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js
index 5b1f759b319..dc407c78aa9 100644
--- a/addons/point_of_sale/static/src/js/chrome.js
+++ b/addons/point_of_sale/static/src/js/chrome.js
@@ -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;
diff --git a/addons/sale_timesheet/static/src/js/timesheet_plan.js b/addons/sale_timesheet/static/src/js/timesheet_plan.js
index 5140f7e3bc3..0a0048d9720 100644
--- a/addons/sale_timesheet/static/src/js/timesheet_plan.js
+++ b/addons/sale_timesheet/static/src/js/timesheet_plan.js
@@ -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',
diff --git a/addons/stock/static/src/js/stock_traceability_report_backend.js b/addons/stock/static/src/js/stock_traceability_report_backend.js
index 553c155e94a..a58c9ac95c1 100644
--- a/addons/stock/static/src/js/stock_traceability_report_backend.js
+++ b/addons/stock/static/src/js/stock_traceability_report_backend.js
@@ -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;
diff --git a/addons/web/static/src/js/apps.js b/addons/web/static/src/js/apps.js
index fa513372269..c49e4741093 100644
--- a/addons/web/static/src/js/apps.js
+++ b/addons/web/static/src/js/apps.js
@@ -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',
diff --git a/addons/web/static/src/js/chrome/abstract_action.js b/addons/web/static/src/js/chrome/abstract_action.js
new file mode 100644
index 00000000000..eb305b3a0d0
--- /dev/null
+++ b/addons/web/static/src/js/chrome/abstract_action.js
@@ -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;
+
+});
diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js
index 1651e5d72d9..e213b55189d 100644
--- a/addons/web/static/src/js/chrome/action_manager.js
+++ b/addons/web/static/src/js/chrome/action_manager.js
@@ -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
diff --git a/addons/web/static/src/js/report/client_action.js b/addons/web/static/src/js/report/client_action.js
index 2fd2ccd44b3..7041913c286 100644
--- a/addons/web/static/src/js/report/client_action.js
+++ b/addons/web/static/src/js/report/client_action.js
@@ -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',
diff --git a/addons/web/static/src/js/views/abstract_controller.js b/addons/web/static/src/js/views/abstract_controller.js
index f1356f72a81..995a5102c37 100644
--- a/addons/web/static/src/js/views/abstract_controller.js
+++ b/addons/web/static/src/js/views/abstract_controller.js
@@ -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
diff --git a/addons/web/static/src/js/widgets/change_password.js b/addons/web/static/src/js/widgets/change_password.js
index b71ae4aaf4b..c05fa30408b 100644
--- a/addons/web/static/src/js/widgets/change_password.js
+++ b/addons/web/static/src/js/widgets/change_password.js
@@ -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",
/**
diff --git a/addons/web/static/tests/chrome/action_manager_tests.js b/addons/web/static/tests/chrome/action_manager_tests.js
index 5d237b9de92..4c4f6f9e338 100644
--- a/addons/web/static/tests/chrome/action_manager_tests.js
+++ b/addons/web/static/tests/chrome/action_manager_tests.js
@@ -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');
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml
index 9e713c51b36..f520c13864a 100644
--- a/addons/web/views/webclient_templates.xml
+++ b/addons/web/views/webclient_templates.xml
@@ -149,6 +149,7 @@
+
diff --git a/addons/web_settings_dashboard/static/src/js/dashboard.js b/addons/web_settings_dashboard/static/src/js/dashboard.js
index 8a4af041b20..1cdd7e55178 100644
--- a/addons/web_settings_dashboard/static/src/js/dashboard.js
+++ b/addons/web_settings_dashboard/static/src/js/dashboard.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(){
diff --git a/addons/website/static/src/js/backend/dashboard.js b/addons/website/static/src/js/backend/dashboard.js
index 6a4d7c7a609..ad45199ce27 100644
--- a/addons/website/static/src/js/backend/dashboard.js
+++ b/addons/website/static/src/js/backend/dashboard.js
@@ -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'