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 @@ +