diff --git a/addons/project/__manifest__.py b/addons/project/__manifest__.py
index ce0dc6d20dd..f4186e705e9 100644
--- a/addons/project/__manifest__.py
+++ b/addons/project/__manifest__.py
@@ -164,7 +164,6 @@
'web/static/src/legacy/js/fields/relational_fields.js',
'web/static/src/legacy/js/fields/field_registry.js',
'web/static/src/legacy/js/fields/field_utils.js',
- 'web/static/src/legacy/js/views/sample_server.js',
'web/static/src/legacy/js/views/abstract_model.js',
'web/static/src/legacy/js/views/basic/basic_model.js',
'web/static/src/legacy/js/views/action_model.js',
diff --git a/addons/spreadsheet/static/tests/charts/model/link_chart_plugin_test.js b/addons/spreadsheet/static/tests/charts/model/link_chart_plugin_test.js
index fac5a488c44..e07f74f5c00 100644
--- a/addons/spreadsheet/static/tests/charts/model/link_chart_plugin_test.js
+++ b/addons/spreadsheet/static/tests/charts/model/link_chart_plugin_test.js
@@ -7,8 +7,6 @@ import { makeTestEnv } from "@web/../tests/helpers/mock_env";
import { registry } from "@web/core/registry";
import { menuService } from "@web/webclient/menus/menu_service";
import { actionService } from "@web/webclient/actions/action_service";
-import { ormService } from "@web/core/orm_service";
-import { viewService } from "@web/views/view_service";
const { Model } = spreadsheet;
@@ -114,8 +112,6 @@ QUnit.module(
},
};
registry.category("services").add("menu", menuService).add("action", actionService);
- registry.category("services").add("view", viewService, { force: true }); // #action-serv-leg-compat-js-class
- registry.category("services").add("orm", ormService, { force: true }); // #action-serv-leg-compat-js-class
},
},
diff --git a/addons/spreadsheet/static/tests/charts/ui/link_chart_figure_test.js b/addons/spreadsheet/static/tests/charts/ui/link_chart_figure_test.js
index be72006be4b..bc701e3ce06 100644
--- a/addons/spreadsheet/static/tests/charts/ui/link_chart_figure_test.js
+++ b/addons/spreadsheet/static/tests/charts/ui/link_chart_figure_test.js
@@ -7,8 +7,6 @@ import { createBasicChart } from "@spreadsheet/../tests/utils/commands";
import { registry } from "@web/core/registry";
import { menuService } from "@web/webclient/menus/menu_service";
import { actionService } from "@web/webclient/actions/action_service";
-import { ormService } from "@web/core/orm_service";
-import { viewService } from "@web/views/view_service";
import { mountSpreadsheet } from "@spreadsheet/../tests/utils/ui";
import { createModelWithDataSource } from "@spreadsheet/../tests/utils/model";
@@ -155,8 +153,6 @@ QUnit.module(
};
patchWithCleanup(session, { uid: 1 });
registry.category("services").add("menu", menuService).add("action", actionService);
- registry.category("services").add("view", viewService, { force: true }); // #action-serv-leg-compat-js-class
- registry.category("services").add("orm", ormService, { force: true }); // #action-serv-leg-compat-js-class
},
},
diff --git a/addons/spreadsheet/static/tests/links/model/link_menu_cells_test.js b/addons/spreadsheet/static/tests/links/model/link_menu_cells_test.js
index de7ff0a1418..ef87d9f3b24 100644
--- a/addons/spreadsheet/static/tests/links/model/link_menu_cells_test.js
+++ b/addons/spreadsheet/static/tests/links/model/link_menu_cells_test.js
@@ -3,8 +3,6 @@ import { spreadsheetLinkMenuCellService } from "@spreadsheet/ir_ui_menu/index";
import spreadsheet from "@spreadsheet/o_spreadsheet/o_spreadsheet_extended";
import { registry } from "@web/core/registry";
import { actionService } from "@web/webclient/actions/action_service";
-import { ormService } from "@web/core/orm_service";
-import { viewService } from "@web/views/view_service";
import { menuService } from "@web/webclient/menus/menu_service";
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
import { setCellContent } from "@spreadsheet/../tests/utils/commands";
@@ -19,8 +17,6 @@ function beforeEach() {
.add("menu", menuService)
.add("action", actionService)
.add("spreadsheetLinkMenuCell", spreadsheetLinkMenuCellService);
- registry.category("services").add("view", viewService, { force: true }); // #action-serv-leg-compat-js-class
- registry.category("services").add("orm", ormService, { force: true }); // #action-serv-leg-compat-js-class
}
QUnit.module("spreadsheet > menu link cells", { beforeEach }, () => {
diff --git a/addons/spreadsheet/static/tests/links/open_links_tests.js b/addons/spreadsheet/static/tests/links/open_links_tests.js
index d24761e1f1d..88725aef421 100644
--- a/addons/spreadsheet/static/tests/links/open_links_tests.js
+++ b/addons/spreadsheet/static/tests/links/open_links_tests.js
@@ -6,8 +6,6 @@ import { actionService } from "@web/webclient/actions/action_service";
import { menuService } from "@web/webclient/menus/menu_service";
import { spreadsheetLinkMenuCellService } from "@spreadsheet/ir_ui_menu/index";
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
-import { viewService } from "@web/views/view_service";
-import { ormService } from "@web/core/orm_service";
import { getMenuServerData } from "@spreadsheet/../tests/links/menu_data_utils";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
import { getEvaluatedCell } from "../utils/getters";
@@ -21,8 +19,6 @@ function beforeEach() {
.add("menu", menuService)
.add("action", actionService)
.add("spreadsheetLinkMenuCell", spreadsheetLinkMenuCellService);
- registry.category("services").add("view", viewService, { force: true }); // #action-serv-leg-compat-js-class
- registry.category("services").add("orm", ormService, { force: true }); // #action-serv-leg-compat-js-class
}
QUnit.module("spreadsheet > link", { beforeEach });
diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py
index cb95c64a2f9..8478cd92bc3 100644
--- a/addons/web/__manifest__.py
+++ b/addons/web/__manifest__.py
@@ -19,7 +19,6 @@ This module provides the core of the Odoo Web Client.
'views/report_templates.xml',
'views/base_document_layout_views.xml',
'views/speedscope_template.xml',
- 'views/lazy_assets.xml',
'views/neutralize_views.xml',
'data/ir_attachment.xml',
'data/report_layout.xml',
@@ -171,8 +170,6 @@ This module provides the core of the Odoo Web Client.
'web/static/src/legacy/js/core/smooth_scroll_on_drag.js',
'web/static/src/legacy/js/widgets/colorpicker.js',
'web/static/src/legacy/xml/colorpicker.xml',
- 'web/static/src/legacy/js/widgets/translation_dialog.js',
- 'web/static/src/legacy/xml/translation_dialog.xml',
],
'web.assets_backend': [
('include', 'web._assets_helpers'),
@@ -230,7 +227,6 @@ This module provides the core of the Odoo Web Client.
'web/static/src/legacy/legacy_client_actions.js',
'web/static/src/legacy/legacy_dialog.js',
'web/static/src/legacy/legacy_load_views.js',
- 'web/static/src/legacy/legacy_views.js',
'web/static/src/legacy/legacy_promise_error_handler.js',
'web/static/src/legacy/legacy_rpc_error_handler.js',
'web/static/src/legacy/root_widget.js',
@@ -254,7 +250,6 @@ This module provides the core of the Odoo Web Client.
'web/static/src/legacy/js/widgets/date_picker.js',
'web/static/src/legacy/js/widgets/domain_selector_dialog.js',
'web/static/src/legacy/js/widgets/domain_selector.js',
- 'web/static/src/legacy/js/widgets/iframe_widget.js',
'web/static/src/legacy/js/widgets/model_field_selector.js',
'web/static/src/legacy/js/widgets/model_field_selector_popover.js',
'web/static/src/legacy/js/env.js',
@@ -264,15 +259,8 @@ This module provides the core of the Odoo Web Client.
'web/static/src/legacy/xml/base.xml',
'web/static/src/legacy/xml/control_panel.xml',
'web/static/src/legacy/xml/fields.xml',
- 'web/static/src/legacy/xml/search_panel.xml',
'web/static/src/legacy/xml/week_days.xml',
],
- "web.assets_backend_legacy_lazy": [
- ("include", "web._assets_helpers"),
- ('include', 'web._assets_backend_helpers'),
- 'web/static/src/scss/pre_variables.scss',
- 'web/static/lib/bootstrap/scss/_variables.scss',
- ],
'web.assets_frontend_minimal': [
'web/static/src/legacy/js/promise_extension.js',
'web/static/src/boot.js',
@@ -415,8 +403,6 @@ This module provides the core of the Odoo Web Client.
'web/static/src/legacy/js/core/smooth_scroll_on_drag.js',
'web/static/src/legacy/js/widgets/colorpicker.js',
'web/static/src/legacy/xml/colorpicker.xml',
- 'web/static/src/legacy/js/widgets/translation_dialog.js',
- 'web/static/src/legacy/xml/translation_dialog.xml',
'web/static/src/env.js',
'web/static/src/core/utils/transitions.scss', # included early because used by other files
diff --git a/addons/web/static/src/legacy/action_adapters.js b/addons/web/static/src/legacy/action_adapters.js
index 77c78bea240..fa24d42f823 100644
--- a/addons/web/static/src/legacy/action_adapters.js
+++ b/addons/web/static/src/legacy/action_adapters.js
@@ -7,7 +7,6 @@ import { objectToQuery } from "../core/browser/router_service";
import { useDebugCategory } from "../core/debug/debug_context";
import { Dialog } from "../core/dialog/dialog";
import { useService } from "@web/core/utils/hooks";
-import { ViewNotFoundError } from "@web/views/view";
import { cleanDomFromBootstrap, wrapSuccessOrFail, useLegacyRefs } from "./utils";
import { mapDoActionOptionAPI } from "./backend_utils";
@@ -16,10 +15,8 @@ import {
onMounted,
onWillUnmount,
onWillUpdateProps,
- status,
useEffect,
useExternalListener,
- useComponent,
xml,
} from "@odoo/owl";
@@ -245,179 +242,3 @@ export class ClientActionAdapter extends ActionAdapter {
this.pushState(state);
}
}
-
-const magicReloadSymbol = Symbol("magicReload");
-
-function useMagicLegacyReload() {
- const comp = useComponent();
- if (comp.props.widget && comp.props.widget[magicReloadSymbol]) {
- return comp.props.widget[magicReloadSymbol];
- }
- let legacyReloadProm = null;
- const getReloadProm = () => legacyReloadProm;
- let manualReload;
- useEffect(
- () => {
- const widget = comp.widget;
- const controllerReload = widget.reload;
- widget.reload = function (...args) {
- manualReload = true;
- legacyReloadProm = controllerReload.call(widget, ...args);
- return legacyReloadProm.finally(() => {
- if (manualReload) {
- legacyReloadProm = null;
- manualReload = false;
- }
- });
- };
- const controllerUpdate = widget.update;
- widget.update = function (...args) {
- const updateProm = controllerUpdate.call(widget, ...args);
- const manualUpdate = !manualReload;
- if (manualUpdate) {
- legacyReloadProm = updateProm;
- }
- return updateProm.finally(() => {
- if (manualUpdate) {
- legacyReloadProm = null;
- }
- });
- };
- widget[magicReloadSymbol] = getReloadProm;
- },
- () => []
- );
- return getReloadProm;
-}
-
-export class ViewAdapter extends ActionAdapter {
- setup() {
- super.setup();
- this.actionService = useService("action");
- this.vm = useService("view");
- this.shouldUpdateWidget = true;
- this.magicReload = useMagicLegacyReload();
- const debugContext = {
- action: this.props.viewParams.action,
- component: this,
- };
- useDebugCategory("action", debugContext);
- useDebugCategory("view", debugContext);
- this.env = Component.env;
- }
-
- get actionId() {
- return this.props.viewParams.action.jsId;
- }
-
- async onWillStart() {
- if (this.props.widget) {
- this.widget = this.props.widget;
- this.widget.setParent(this);
- if (this.props.onReverseBreadcrumb) {
- await this.props.onReverseBreadcrumb();
- }
- return this.updateWidget(this.props.viewParams);
- } else {
- const view = new this.props.View(this.props.viewInfo, this.props.viewParams);
- this.widget = await view.getController(this);
- if (status(this) === "destroyed") {
- // the component might have been destroyed meanwhile, but if so, `this.widget` wasn't
- // destroyed by OwlCompatibility layer as it wasn't set yet, so destroy it now
- if (!this.actionService.__legacy__isActionInStack(this.actionId)) {
- this.widget.destroy();
- }
- return Promise.resolve();
- }
- return this.widget._widgetRenderAndInsert(() => {});
- }
- }
-
- /**
- * @override
- */
- async updateWidget(nextProps) {
- const shouldUpdateWidget = this.shouldUpdateWidget;
- this.shouldUpdateWidget = true;
- if (!shouldUpdateWidget) {
- return this.magicReload();
- }
- await this.widget.willRestore();
- const options = {
- ...this.props.viewParams,
- ...nextProps.viewParams,
- shouldUpdateSearchComponents: true,
- };
- if (!this.magicReload()) {
- this.widget.reload(options);
- }
- return this.magicReload();
- }
-
- async renderWidget() {}
-
- /**
- * Override to add the state of the legacy controller in the exported state.
- */
- exportState() {
- const state = super.exportState();
- const widgetState = this.__widget.exportState();
- return Object.assign({}, state, widgetState);
- }
-
- async loadViews(resModel, context, views) {
- return (await this.vm.loadViews({ resModel, views, context }, {})).__legacy__.fields_views;
- }
-
- /**
- * @private
- * @param {OdooEvent} ev
- */
- async _trigger_up(ev) {
- const payload = ev.data;
- if (ev.name === "switch_view") {
- if (payload.view_type === "form") {
- if (payload.res_id) {
- const activeIds = this.__widget.model.get(this.__widget.handle).res_ids;
- return this.props.selectRecord(payload.res_id, {
- mode: payload.mode,
- activeIds,
- });
- } else {
- return this.props.createRecord();
- }
- } else {
- try {
- await this.actionService.switchView(payload.view_type);
- } catch (e) {
- if (typeof e === "object" && e instanceof ViewNotFoundError) {
- return;
- }
- throw e;
- }
- }
- } else if (ev.name === "execute_action") {
- const buttonContext = new Context(payload.action_data.context).eval();
- const envContext = new Context(payload.env.context).eval();
- wrapSuccessOrFail(
- this.actionService.doActionButton({
- args: payload.action_data.args,
- buttonContext: buttonContext,
- context: envContext,
- close: payload.action_data.close,
- resModel: payload.env.model,
- name: payload.action_data.name,
- resId: payload.env.currentID || null,
- resIds: payload.env.resIDs,
- special: payload.action_data.special,
- type: payload.action_data.type,
- onClose: payload.on_closed,
- effect: payload.action_data.effect,
- }),
- payload
- );
- } else {
- super._trigger_up(ev);
- }
- }
-}
diff --git a/addons/web/static/src/legacy/backend_utils.js b/addons/web/static/src/legacy/backend_utils.js
index b92915cd4a7..348deea5e9b 100644
--- a/addons/web/static/src/legacy/backend_utils.js
+++ b/addons/web/static/src/legacy/backend_utils.js
@@ -1,7 +1,6 @@
/** @odoo-module **/
import ActionModel from "@web/legacy/js/views/action_model";
-import { makeContext } from "@web/core/context";
/**
* @param {string} state
@@ -17,13 +16,8 @@ function searchModelStateFromLegacy(state) {
const newState = {};
if (parsedState.ControlPanelModelExtension) {
- const {
- query,
- filters,
- nextGroupId,
- nextGroupNumber,
- nextId,
- } = parsedState.ControlPanelModelExtension;
+ const { query, filters, nextGroupId, nextGroupNumber, nextId } =
+ parsedState.ControlPanelModelExtension;
newState.nextGroupId = nextGroupId;
newState.nextGroupNumber = nextGroupNumber;
@@ -112,97 +106,7 @@ function searchModelStateFromLegacy(state) {
return JSON.stringify(newState);
}
-/**
- * @param {string} state
- * @returns {string}
- */
-export function searchModelStateToLegacy(state) {
- if (!state) {
- return;
- }
-
- const parsedState = JSON.parse(state);
- const query = [];
- for (const queryElem of parsedState.query) {
- const searchItemId = queryElem.searchItemId;
- const searchItem = parsedState.searchItems[searchItemId];
- const groupId = searchItem.groupId;
- const legacyQueryElem = { filterId: searchItemId, groupId };
- switch (searchItem.type) {
- case "dateFilter":
- legacyQueryElem.optionId = queryElem.generatorId;
- break;
- case "dateGroupBy":
- legacyQueryElem.optionId = queryElem.intervalId;
- break;
- case "comparison":
- legacyQueryElem.type = "comparison";
- legacyQueryElem.dateFilterId = searchItem.dateFilterId;
- break;
- case "field":
- legacyQueryElem.value = queryElem.autocompleteValue.value;
- legacyQueryElem.label = queryElem.autocompleteValue.label;
- legacyQueryElem.operator = queryElem.autocompleteValue.operator;
- break;
- }
- query.push(legacyQueryElem);
- }
-
- const filters = {};
- for (const item of Object.values(parsedState.searchItems)) {
- const filter = Object.assign({}, item);
- switch (item.type) {
- case "dateGroupBy":
- filter.type = "groupBy";
- filter.hasOptions = true;
- filter.defaultOptionId = item.defaultIntervalId;
- delete filter.defaultIntervalId;
- break;
- case "dateFilter":
- filter.type = "filter";
- filter.isDateFilter = true;
- filter.hasOptions = true;
- filter.defaultOptionId = item.defaultGeneratorId;
- delete filter.defaultGeneratorId;
- break;
- case "favorite":
- filter.orderedBy = item.orderBy;
- delete filter.orderBy;
- break;
- case "filter": {
- let context = item.context;
- try {
- context = makeContext([context]);
- } catch {
- // pass
- }
- filter.context = context;
- }
- }
- filters[item.id] = filter;
- }
-
- const { nextGroupId, nextGroupNumber, nextId, sections, searchPanelInfo } = parsedState;
- const legacyState = {};
- legacyState.ControlPanelModelExtension = {
- query,
- filters,
- nextGroupId,
- nextGroupNumber,
- nextId,
- };
- legacyState.SearchPanelModelExtension = { sections, searchPanelInfo };
-
- for (const [key, extension] of Object.entries(ActionModel.registry.entries())) {
- if (!["ControlPanel", "SearchPanel"].includes(key) && parsedState[key] !== undefined) {
- legacyState[extension.name] = parsedState[key];
- }
- }
-
- return JSON.stringify(legacyState);
-}
-
-export function getGlobalState(legacyControllerState) {
+function getGlobalState(legacyControllerState) {
const { resIds, searchModel, searchPanel } = legacyControllerState;
const globalState = {};
if (searchPanel) {
@@ -218,18 +122,6 @@ export function getGlobalState(legacyControllerState) {
return globalState;
}
-export function getLocalState(legacyControllerState) {
- const state = Object.assign({}, legacyControllerState);
- if ("currentId" in state) {
- state.resId = state.currentId;
- delete state.currentId;
- }
- delete state.searchModel;
- delete state.searchPanel;
- delete state.resIds;
- return state;
-}
-
export function mapDoActionOptionAPI(legacyOptions) {
legacyOptions = legacyOptions || {};
// use camelCase instead of snake_case for some keys
diff --git a/addons/web/static/src/legacy/js/views/abstract_controller.js b/addons/web/static/src/legacy/js/views/abstract_controller.js
deleted file mode 100644
index a030415657a..00000000000
--- a/addons/web/static/src/legacy/js/views/abstract_controller.js
+++ /dev/null
@@ -1,607 +0,0 @@
-/** @odoo-module alias=web.AbstractController **/
-
-/**
- * The Controller class is the class coordinating the model and the renderer.
- * It is the C in MVC, and is what was formerly known in Odoo as a View.
- *
- * Its role is to listen to events bubbling up from the model/renderer, and call
- * the appropriate methods if necessary. It also render control panel buttons,
- * and react to changes in the search view. Basically, all interactions from
- * the renderer/model with the outside world (meaning server/reading in session/
- * reading localstorage, ...) has to go through the controller.
- */
-
-import ActionMixin from 'web.ActionMixin';
-import { loadCSS, loadJS } from "@web/core/assets";
-import concurrency from 'web.concurrency';
-import { ComponentWrapper } from 'web.OwlCompatibility';
-import mvc from 'web.mvc';
-import session from 'web.session';
-
-var AbstractController = mvc.Controller.extend(ActionMixin, {
- custom_events: _.extend({}, ActionMixin.custom_events, {
- navigation_move: '_onNavigationMove',
- open_record: '_onOpenRecord',
- switch_view: '_onSwitchView',
- }),
- events: {
- 'click a[type="action"]': '_onActionClicked',
- },
-
- /**
- * @param {Object} param
- * @param {Object[]} params.actionViews
- * @param {string} params.activeActions
- * @param {string} params.bannerRoute
- * @param {Object} [params.controlPanel]
- * @param {string} params.controllerID an id to ease the communication with
- * upstream components
- * @param {string} params.displayName
- * @param {Object} params.initialState
- * @param {string} params.modelName
- * @param {ActionModel} [params.searchModel]
- * @param {string} [params.searchPanel]
- * @param {string} params.viewType
- * @param {boolean} [params.withControlPanel]
- * @param {boolean} [params.withSearchPanel]
- */
- init: function (parent, model, renderer, params) {
- this._super.apply(this, arguments);
- this._title = params.displayName;
- this.modelName = params.modelName;
- this.activeActions = params.activeActions;
- this.controllerID = params.controllerID;
- this.initialState = params.initialState;
- this.bannerRoute = params.bannerRoute;
- this.actionViews = params.actionViews;
- this.viewType = params.viewType;
- // use a DropPrevious to correctly handle concurrent updates
- this.dp = new concurrency.DropPrevious();
-
- this.withControlPanel = params.withControlPanel;
- this.withSearchPanel = params.withSearchPanel;
- if (params.searchModel) {
- this.searchModel = params.searchModel;
- }
- if (this.withControlPanel) {
- const { Component, props } = params.controlPanel;
- this.ControlPanel = Component;
- this.controlPanelProps = props;
- }
- if (this.withSearchPanel) {
- const { Component, props } = params.searchPanel;
- this.SearchPanel = Component;
- this.searchPanelProps = props;
- }
- },
-
- /**
- * Simply renders and updates the url.
- *
- * @returns {Promise}
- */
- start: async function () {
- this.$el.addClass('o_view_controller');
- this.renderButtons();
- const promises = [this._super(...arguments)];
- if (this.withControlPanel) {
- this._updateControlPanelProps(this.initialState);
- this._controlPanelWrapper = new ComponentWrapper(this, this.ControlPanel, this.controlPanelProps);
- this._controlPanelWrapper.env.bus.on('focus-view', this, () => this._giveFocus());
- promises.push(this._controlPanelWrapper.mount(this.el, { position: 'first-child' }));
- }
- if (this.withSearchPanel) {
- this._searchPanelWrapper = new ComponentWrapper(this, this.SearchPanel, this.searchPanelProps);
- const content = this.el.querySelector(':scope .o_content');
- content.classList.add('o_controller_with_searchpanel');
- promises.push(this._searchPanelWrapper.mount(content, { position: 'first-child' }));
- }
- await Promise.all(promises);
- await this._update(this.initialState, { shouldUpdateSearchComponents: false });
- this.updateButtons();
- this.el.classList.toggle('o_legacy_view_sample_data', this.model.isInSampleMode());
- },
- /**
- * @override
- */
- destroy: function () {
- if (this.$buttons) {
- this.$buttons.off();
- }
- ActionMixin.destroy.call(this);
- this._super.apply(this, arguments);
- },
- /**
- * Called each time the controller is attached into the DOM.
- */
- on_attach_callback: function () {
- ActionMixin.on_attach_callback.call(this);
- this.searchModel.on('search', this, this._onSearch);
- if (this.withControlPanel) {
- this.searchModel.on('get-controller-query-params', this, this._onGetOwnedQueryParams);
- }
- if (!(this.renderer instanceof ComponentWrapper)) {
- this.renderer.on_attach_callback();
- }
- },
- /**
- * Called each time the controller is detached from the DOM.
- */
- on_detach_callback: function () {
- ActionMixin.on_detach_callback.call(this);
- this.searchModel.off('search', this);
- if (this.withControlPanel) {
- this.searchModel.off('get-controller-query-params', this);
- }
- if (!(this.renderer instanceof ComponentWrapper)) {
- this.renderer.on_detach_callback();
- }
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * 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
- * example, when the user opens the 'home' screen, the action manager calls
- * this method on the active view to make sure it is ok to open the home
- * screen (and lose all current state).
- *
- * Note that it returns a Promise, because the view could choose to ask the
- * user if he agrees to discard.
- *
- * @param {string} [recordID]
- * if not given, we consider all the changes made by the controller
- * @param {Object} [options]
- * @returns {Promise} resolved if properly discarded, rejected otherwise
- */
- discardChanges: function (recordID, options) {
- return Promise.resolve();
- },
- /**
- * Export the state of the controller containing information that is shared
- * between different controllers of a same action (like the current search
- * model state or the states of some components).
- *
- * @returns {Object}
- */
- exportState() {
- const exported = {
- searchModel: this.searchModel.exportState(),
- };
- if (this.withSearchPanel) {
- const searchPanel = this._searchPanelWrapper.componentRef.comp;
- exported.searchPanel = searchPanel.exportState();
- }
- return exported;
- },
- /**
- * Parses and imports a previously exported state.
- *
- * @param {Object} state
- */
- importState(state) {
- this.searchModel.importState(state.searchModel);
- if (this.withSearchPanel) {
- const searchPanel = this._searchPanelWrapper.componentRef.comp;
- searchPanel.importState(state.searchPanel);
- }
- },
- /**
- * The use of this method is discouraged. It is still snakecased, because
- * it currently is used in many templates, but we will move to a simpler
- * mechanism as soon as we can.
- *
- * @deprecated
- * @param {string} action type of action, such as 'create', 'read', ...
- * @returns {boolean}
- */
- is_action_enabled: function (action) {
- return this.activeActions[action];
- },
- /**
- * Short helper method to reload the view
- *
- * @param {Object} [params={}]
- * @param {Object} [params.controllerState={}]
- * @returns {Promise}
- */
- reload: async function (params = {}) {
- if (params.controllerState) {
- this.importState(params.controllerState);
- Object.assign(params, this.searchModel.get('query'));
- }
- return this.update(params, {});
- },
- /**
- * This is the main entry point for the controller. Changes from the search
- * view arrive in this method, and internal changes can sometimes also call
- * this method. It is basically the way everything notifies the controller
- * that something has changed.
- *
- * The update method is responsible for fetching necessary data, then
- * updating the renderer and wait for the rendering to complete.
- *
- * @param {Object} params will be given to the model and to the renderer
- * @param {Object} [options={}]
- * @param {boolean} [options.reload=true] if true, the model will reload data
- * @returns {Promise}
- */
- async update(params, options = {}) {
- const shouldReload = 'reload' in options ? options.reload : true;
- if (shouldReload) {
- this.handle = await this.dp.add(this.model.reload(this.handle, params));
- }
- const localState = this.renderer.getLocalState();
- const state = this.model.get(this.handle, { withSampleData: true });
- const promises = [
- this._updateRendererState(state, params).then(() => {
- this.renderer.setLocalState(localState);
- }),
- this._update(this.model.get(this.handle), params)
- ];
- await this.dp.add(Promise.all(promises));
- this.updateButtons();
- this.el.classList.toggle('o_legacy_view_sample_data', this.model.isInSampleMode());
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
-
- /**
- * Meant to be overriden to return a proper object.
- * @private
- * @param {Object} [state]
- * @return {(Object|null)}
- */
- _getPagingInfo: function (state) {
- return null;
- },
- /**
- * Meant to be overriden to return a proper object.
- * @private
- * @param {Object} [state]
- * @return {(Object|null)}
- */
- _getActionMenuItems: function (state) {
- return null;
- },
- /**
- * Gives the focus to the renderer if not in sample mode.
- *
- * @private
- */
- _giveFocus() {
- if (!this.model.isInSampleMode()) {
- this.renderer.giveFocus();
- }
- },
- /**
- * Hide an onboarding banner if present and visible
- * @private
- */
- _hideBanner: function () {
- if (this._$banner.length) {
- const $bannerElement = this._$banner.filter('.o_onboarding_container.collapse.show');
- if ($bannerElement.length) {
- Collapse.getOrCreateInstance($bannerElement[0]).toggle();
- }
- }
- },
- /**
- * This method is the way a view can notifies the outside world that
- * something has changed. The main use for this is to update the url, for
- * example with a new id.
- *
- * @private
- */
- _pushState: function () {
- this.trigger_up('push_state', {
- controllerID: this.controllerID,
- state: this.getState(),
- });
- },
- /**
- * @private
- * @param {function} callback function to execute before removing classname
- * 'o_legacy_view_sample_data' (may be async). This allows to reload and/or
- * rerender before removing the className, thus preventing the view from
- * flickering.
- */
- async _removeSampleData(callback) {
- this.model.leaveSampleMode();
- if (callback) {
- await callback();
- }
- this.el.classList.remove('o_legacy_view_sample_data');
- },
- /**
- * Renders the html provided by the route specified by the
- * bannerRoute attribute on the controller (banner_route in the template).
- * Renders it before the view output and add a css class 'o_has_banner' to it.
- * There can be only one banner displayed at a time.
- *
- * If the banner contains stylesheet links or js files, they are moved to
- * (and will only be fetched once).
- *
- * Route example:
- * @http.route('/module/hello', auth='user', type='json')
- * def hello(self):
- * return {'html': '
hello, world
'}
- *
- * @private
- * @returns {Promise}
- */
- _renderBanner: async function () {
- if (this.bannerRoute !== undefined) {
- const response = await this._rpc({
- route: this.bannerRoute,
- params: {context: session.user_context},
- });
- if (!response.html) {
- this.$el.removeClass('o_has_banner');
- return Promise.resolve();
- }
- this.$el.addClass('o_has_banner');
- var $banner = $(response.html);
- // we should only display one banner at a time
- if (this._$banner && this._$banner.remove) {
- this._$banner.remove();
- }
- // Css and js are moved to
- var defs = [];
- $('link[rel="stylesheet"]', $banner).each(function (i, link) {
- defs.push(loadCSS(link.href));
- link.remove();
- });
- $('script[type="text/javascript"]', $banner).each(function (i, js) {
- defs.push(loadJS(js.src));
- js.remove();
- });
- await Promise.all(defs);
- $banner.insertBefore(this.$('> .o_content'));
- this._$banner = $banner;
- }
- },
- /**
- * @override
- * @private
- */
- _startRenderer: function () {
- if (this.renderer instanceof ComponentWrapper) {
- return this.renderer.mount(this.$('.o_content')[0]);
- }
- return this.renderer.appendTo(this.$('.o_content'));
- },
- /**
- * This method is called after each update or when the start method is
- * completed.
- *
- * Its primary use is to be used as a hook to update all parts of the UI,
- * besides the renderer. For example, it may be used to enable/disable
- * some buttons in the control panel, such as the current graph type for a
- * graph view.
- *
- * FIXME: this hook should be synchronous, and called once async rendering
- * has been done.
- *
- * @private
- * @param {Object} state the state given by the model
- * @param {Object} [params={}]
- * @param {Array} [params.breadcrumbs]
- * @param {Object} [params.shouldUpdateSearchComponents]
- * @returns {Promise}
- */
- async _update(state, params) {
- // AAB: update the control panel -> this will be moved elsewhere at some point
- if (!this.$buttons) {
- this.renderButtons();
- }
- const promises = [this._renderBanner()];
- if (params.shouldUpdateSearchComponents !== false) {
- if (this.withControlPanel) {
- this._updateControlPanelProps(state);
- if (params.breadcrumbs) {
- this.controlPanelProps.breadcrumbs = params.breadcrumbs;
- }
- promises.push(this.updateControlPanel());
- }
- if (this.withSearchPanel) {
- this._updateSearchPanel();
- }
- }
- this._pushState();
- await Promise.all(promises);
- },
- /**
- * Can be used to update the key 'cp_content'. This method is called in start and _update methods.
- *
- * @private
- * @param {Object} state the state given by the model
- */
- _updateControlPanelProps(state) {
- if (!this.controlPanelProps.cp_content) {
- this.controlPanelProps.cp_content = {};
- }
- if (this.$buttons) {
- this.controlPanelProps.cp_content.$buttons = this.$buttons;
- }
- Object.assign(this.controlPanelProps, {
- actionMenus: this._getActionMenuItems(state),
- pager: this._getPagingInfo(state),
- title: this.getTitle(),
- });
- },
- /**
- * @private
- * @param {Object} state
- * @param {Object} newProps
- * @returns {Promise}
- */
- _updatePaging: async function (state, newProps) {
- const pagingInfo = this._getPagingInfo(state);
- if (pagingInfo) {
- Object.assign(pagingInfo, newProps);
- return this.updateControlPanel({ pager: pagingInfo });
- }
- },
- /**
- * Updates the state of the renderer (handle both Widget and Component
- * renderers).
- *
- * @private
- * @param {Object} state the model state
- * @param {Object} [params={}] will be given to the model and to the renderer
- * @return {Promise}
- */
- _updateRendererState(state, params = {}) {
- if (this.renderer instanceof ComponentWrapper) {
- return this.renderer.update(state);
- }
- return this.renderer.updateState(state, params);
- },
- /**
- * @private
- * @param {Object} [newProps={}]
- * @return {Promise}
- */
- async _updateSearchPanel(newProps) {
- Object.assign(this.searchPanelProps, newProps);
- await this._searchPanelWrapper.update(this.searchPanelProps);
- },
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * When a user clicks on an link with type="action", we need to actually
- * do the action. This kind of links is used a lot in no-content helpers.
- *
- * * if the link has both data-model and data-method attributes, the
- * corresponding method is called, chained to any action it would
- * return. An optional data-reload-on-close (set to a non-falsy value)
- * also causes th underlying view to be reloaded after the dialog is
- * closed.
- * * if the link has a name attribute, invoke the action with that
- * identifier (see :class:`ActionManager.doAction` to not get the
- * details)
- * * otherwise an *action descriptor* is built from the link's data-
- * attributes (model, res-id, views, domain and context)
- *
- * @private
- * @param ev
- */
- _onActionClicked: function (ev) { // FIXME: maybe this should also work on