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