diff --git a/addons/crm/static/src/js/forecast/forecast_views.js b/addons/crm/static/src/js/forecast/forecast_views.js index f1eac289a37..607510db57e 100644 --- a/addons/crm/static/src/js/forecast/forecast_views.js +++ b/addons/crm/static/src/js/forecast/forecast_views.js @@ -3,10 +3,10 @@ import { ForecastKanbanController } from './forecast_controllers'; import { ForecastKanbanModel } from './forecast_models'; import { ForecastKanbanRenderer } from './forecast_renderers'; import { ForecastSearchModel } from "./forecast_search_model"; -import { GraphView } from "@web/views/graph/graph_view"; +import { graphView } from "@web/views/graph/graph_view"; import KanbanView from 'web.KanbanView'; import ListView from 'web.ListView'; -import { PivotView } from "@web/views/pivot/pivot_view"; +import { pivotView } from "@web/views/pivot/pivot_view"; import { registry } from "@web/core/registry"; import viewRegistry from 'web.view_registry'; @@ -16,10 +16,12 @@ import viewRegistry from 'web.view_registry'; * - context key `forecast_field` on a date/datetime field * - special filter "Forecast" (which must set the `forecast_filter:1` context key) */ -class ForecastGraphView extends GraphView {} -ForecastGraphView.SearchModel = ForecastSearchModel; +export const forecastGraphView = { + ...graphView, + SearchModel: ForecastSearchModel, +}; -registry.category("views").add("forecast_graph", ForecastGraphView); +registry.category("views").add("forecast_graph", forecastGraphView); /** * Kanban view to be used for a Forecast @@ -71,14 +73,14 @@ viewRegistry.add('forecast_list', ForecastListView); * - context key `forecast_field` on a date/datetime field * - special filter "Forecast" (which must set the `forecast_filter:1` context key) */ -class ForecastPivotView extends PivotView {} -ForecastPivotView.SearchModel = ForecastSearchModel; +export const forecastPivotView = { + ...pivotView, + SearchModel: ForecastSearchModel, +}; -registry.category("views").add("forecast_pivot", ForecastPivotView); +registry.category("views").add("forecast_pivot", forecastPivotView); export { - ForecastGraphView, ForecastKanbanView, ForecastListView, - ForecastPivotView, }; diff --git a/addons/hr_attendance/static/src/js/attendance_report_views.js b/addons/hr_attendance/static/src/js/attendance_report_views.js index 6de235aa274..07513fb7338 100644 --- a/addons/hr_attendance/static/src/js/attendance_report_views.js +++ b/addons/hr_attendance/static/src/js/attendance_report_views.js @@ -1,8 +1,8 @@ /** @odoo-module **/ import { registry } from "@web/core/registry"; -import { GraphView } from "@web/views/graph/graph_view"; -import { PivotView } from "@web/views/pivot/pivot_view"; +import { graphView } from "@web/views/graph/graph_view"; +import { pivotView } from "@web/views/pivot/pivot_view"; const viewRegistry = registry.category("views"); @@ -22,7 +22,7 @@ function openView(component, domain, views, context) { }); } -export class AttendanceReportGraphView extends GraphView { +export class AttendanceReportGraphController extends graphView.Controller { /** * @override */ @@ -31,9 +31,12 @@ export class AttendanceReportGraphView extends GraphView { } } -viewRegistry.add("attendance_report_graph", AttendanceReportGraphView); +viewRegistry.add("attendance_report_graph", { + ...graphView, + Controller: AttendanceReportGraphController +}); -export class AttendanceReportPivotView extends PivotView { +export class AttendanceReportPivotController extends pivotView.Controller { /** * @override */ @@ -42,4 +45,7 @@ export class AttendanceReportPivotView extends PivotView { } } -viewRegistry.add("attendance_report_pivot", AttendanceReportPivotView); +viewRegistry.add("attendance_report_pivot", { + ...pivotView, + Controller: AttendanceReportPivotController +}); diff --git a/addons/hr_timesheet/static/src/js/timesheet_graph_view.js b/addons/hr_timesheet/static/src/js/timesheet_graph_view.js index 155d4b17626..e6df6c7ad85 100644 --- a/addons/hr_timesheet/static/src/js/timesheet_graph_view.js +++ b/addons/hr_timesheet/static/src/js/timesheet_graph_view.js @@ -1,12 +1,14 @@ /** @odoo-module **/ -import { ProjectGraphView } from "@project/js/project_graph_view"; +import { projectGraphView } from "@project/js/project_graph_view"; import { hrTimesheetGraphModel } from "./timesheet_graph_model"; import { registry } from "@web/core/registry"; const viewRegistry = registry.category("views"); -export class hrTimesheetGraphView extends ProjectGraphView {} -hrTimesheetGraphView.Model = hrTimesheetGraphModel; +export const hrTimesheetGraphView = { + ...projectGraphView, + Model: hrTimesheetGraphModel, +}; viewRegistry.add("hr_timesheet_graphview", hrTimesheetGraphView); diff --git a/addons/project/static/src/burndown_chart/burndown_chart_pivot_view.js b/addons/project/static/src/burndown_chart/burndown_chart_pivot_view.js index 99d70fbab7a..52e4d96cf7d 100644 --- a/addons/project/static/src/burndown_chart/burndown_chart_pivot_view.js +++ b/addons/project/static/src/burndown_chart/burndown_chart_pivot_view.js @@ -1,12 +1,10 @@ /** @odoo-module **/ import { BurndownChartPivotModel } from "./burndown_chart_pivot_model"; -import { PivotView } from "@web/views/pivot/pivot_view"; +import { pivotView } from "@web/views/pivot/pivot_view"; import { registry } from "@web/core/registry"; const viewRegistry = registry.category("views"); -class BurndownChartPivotView extends PivotView {} -BurndownChartPivotView.Model = BurndownChartPivotModel; - -viewRegistry.add("burndown_chart_pivot", BurndownChartPivotView); +const burndownChartPivotView = {...pivotView, Model: BurndownChartPivotModel}; +viewRegistry.add("burndown_chart_pivot", burndownChartPivotView); diff --git a/addons/project/static/src/burndown_chart/burndown_chart_view.js b/addons/project/static/src/burndown_chart/burndown_chart_view.js index a3f727a29a5..e6d5c2b0f8d 100644 --- a/addons/project/static/src/burndown_chart/burndown_chart_view.js +++ b/addons/project/static/src/burndown_chart/burndown_chart_view.js @@ -2,14 +2,16 @@ import { BurndownChartModel } from "./burndown_chart_model"; import { BurndownChartRenderer } from "./burndown_chart_renderer"; -import { GraphView } from "@web/views/graph/graph_view"; +import { graphView } from "@web/views/graph/graph_view"; import { registry } from "@web/core/registry"; const viewRegistry = registry.category("views"); -class BurndownChartView extends GraphView {} -BurndownChartView.components = { ...GraphView.components, Renderer: BurndownChartRenderer }; -BurndownChartView.buttonTemplate = "project.BurndownChartView.Buttons"; -BurndownChartView.Model = BurndownChartModel; +const burndownChartGraphView = { + ...graphView, + Renderer: BurndownChartRenderer, + buttonTemplate: "project.BurndownChartView.Buttons", + Model: BurndownChartModel, +}; -viewRegistry.add("burndown_chart", BurndownChartView); +viewRegistry.add("burndown_chart", burndownChartGraphView); diff --git a/addons/project/static/src/js/project_graph_view.js b/addons/project/static/src/js/project_graph_view.js index 416835d1a1f..7c3b7eda969 100644 --- a/addons/project/static/src/js/project_graph_view.js +++ b/addons/project/static/src/js/project_graph_view.js @@ -2,11 +2,10 @@ import { ProjectControlPanel } from "@project/project_control_panel/project_control_panel"; import { registry } from "@web/core/registry"; -import { GraphView } from "@web/views/graph/graph_view"; +import { graphView } from "@web/views/graph/graph_view"; const viewRegistry = registry.category("views"); -export class ProjectGraphView extends GraphView {} -ProjectGraphView.ControlPanel = ProjectControlPanel; +export const projectGraphView = {...graphView, ControlPanel: ProjectControlPanel}; -viewRegistry.add("project_graph", ProjectGraphView); +viewRegistry.add("project_graph", projectGraphView); diff --git a/addons/project/static/src/js/project_pivot_view.js b/addons/project/static/src/js/project_pivot_view.js index a221d933405..5699d007ac8 100644 --- a/addons/project/static/src/js/project_pivot_view.js +++ b/addons/project/static/src/js/project_pivot_view.js @@ -2,11 +2,8 @@ import { ProjectControlPanel } from "@project/project_control_panel/project_control_panel"; import { registry } from "@web/core/registry"; -import { PivotView } from "@web/views/pivot/pivot_view"; +import { pivotView } from "@web/views/pivot/pivot_view"; -const viewRegistry = registry.category("views"); +const projectPivotView = {...pivotView, ControlPanel: ProjectControlPanel}; -class ProjectPivotView extends PivotView {} -ProjectPivotView.ControlPanel = ProjectControlPanel; - -viewRegistry.add("project_pivot", ProjectPivotView); +registry.category("views").add("project_pivot", projectPivotView); diff --git a/addons/project/static/src/js/project_rating_graph_view.js b/addons/project/static/src/js/project_rating_graph_view.js index 380ef3ffb2b..00e3d9fde56 100644 --- a/addons/project/static/src/js/project_rating_graph_view.js +++ b/addons/project/static/src/js/project_rating_graph_view.js @@ -3,7 +3,7 @@ import { _lt } from "@web/core/l10n/translation"; import { registry } from "@web/core/registry"; import { GraphArchParser } from "@web/views/graph/graph_arch_parser"; -import { GraphView } from "@web/views/graph/graph_view"; +import { graphView } from "@web/views/graph/graph_view"; const viewRegistry = registry.category("views"); @@ -27,8 +27,9 @@ class ProjectRatingArchParser extends GraphArchParser { } // Would it be not better achiedved by using a proper arch directly? +const projectRatingGraphView = { + ...graphView, + ArchParser: ProjectRatingArchParser, +}; -class ProjectRatingGraphView extends GraphView {} -ProjectRatingGraphView.ArchParser = ProjectRatingArchParser; - -viewRegistry.add("project_rating_graph", ProjectRatingGraphView); +viewRegistry.add("project_rating_graph", projectRatingGraphView); diff --git a/addons/project/static/src/js/project_rating_pivot_view.js b/addons/project/static/src/js/project_rating_pivot_view.js index e5dae9a110a..f921b30c338 100644 --- a/addons/project/static/src/js/project_rating_pivot_view.js +++ b/addons/project/static/src/js/project_rating_pivot_view.js @@ -3,7 +3,7 @@ import { _lt } from "@web/core/l10n/translation"; import { registry } from "@web/core/registry"; import { PivotArchParser } from "@web/views/pivot/pivot_arch_parser"; -import { PivotView } from "@web/views/pivot/pivot_view"; +import { pivotView } from "@web/views/pivot/pivot_view"; const viewRegistry = registry.category("views"); @@ -28,7 +28,9 @@ class ProjectRatingArchParser extends PivotArchParser { // Would it be not better achiedved by using a proper arch directly? -class ProjectRatingPivotView extends PivotView {} -ProjectRatingPivotView.ArchParser = ProjectRatingArchParser; +const projectRatingPivotView = { + ...pivotView, + ArchParser: ProjectRatingArchParser, +}; -viewRegistry.add("project_rating_pivot", ProjectRatingPivotView); +viewRegistry.add("project_rating_pivot", projectRatingPivotView); diff --git a/addons/web/static/src/search/with_search/with_search.js b/addons/web/static/src/search/with_search/with_search.js index 046331cfb69..3b786f7166e 100644 --- a/addons/web/static/src/search/with_search/with_search.js +++ b/addons/web/static/src/search/with_search/with_search.js @@ -17,7 +17,7 @@ export class WithSearch extends Component { useSubEnv({ __getContext__: new CallbackRecorder() }); } - const SearchModelClass = this.Component.SearchModel || SearchModel; + const SearchModelClass = this.props.SearchModel || SearchModel; this.searchModel = new SearchModelClass(this.env, { user: useService("user"), orm: useService("orm"), @@ -77,6 +77,7 @@ WithSearch.defaultProps = { }; WithSearch.props = { Component: Function, + SearchModel: { type: Function, optional: true }, componentProps: { type: Object, optional: true }, resModel: String, diff --git a/addons/web/static/src/views/graph/graph_controller.js b/addons/web/static/src/views/graph/graph_controller.js new file mode 100644 index 00000000000..b99260c23d2 --- /dev/null +++ b/addons/web/static/src/views/graph/graph_controller.js @@ -0,0 +1,134 @@ +/** @odoo-module **/ + +import { Dropdown } from "@web/core/dropdown/dropdown"; +import { DropdownItem } from "@web/core/dropdown/dropdown_item"; +import { useService } from "@web/core/utils/hooks"; +import { Layout } from "@web/search/layout"; +import { GroupByMenu } from "@web/search/group_by_menu/group_by_menu"; +import { standardViewProps } from "@web/views/helpers/standard_view_props"; +import { useSetupView } from "@web/views/helpers/view_hook"; +import { useModel } from "../helpers/model"; + +const { Component } = owl; + +export class GraphController extends Component { + setup() { + this.actionService = useService("action"); + this.model = useModel(this.props.Model, this.props.modelParams); + + useSetupView({ + getLocalState: () => { + return { metaData: this.model.metaData }; + }, + getContext: () => this.getContext(), + }); + } + + /** + * @returns {Object} + */ + getContext() { + // expand context object? change keys? + const { measure, groupBy, mode } = this.model.metaData; + return { + graph_measure: measure, + graph_mode: mode, + graph_groupbys: groupBy.map((gb) => gb.spec), + }; + } + + /** + * Execute the action to open the view on the current model. + * + * @param {Array} domain + * @param {Array} views + * @param {Object} context + */ + openView(domain, views, context) { + this.actionService.doAction( + { + context, + domain, + name: this.model.metaData.title, + res_model: this.model.metaData.resModel, + target: "current", + type: "ir.actions.act_window", + views, + }, + { + viewType: "list", + } + ); + } + /** + * @param {string} domain the domain of the clicked area + */ + onGraphClicked(domain) { + const { context } = this.model.metaData; + + Object.keys(context).forEach((x) => { + if (x === "group_by" || x.startsWith("search_default_")) { + delete context[x]; + } + }); + + const views = {}; + for (const [viewId, viewType] of this.env.config.views || []) { + views[viewType] = viewId; + } + function getView(viewType) { + return [views[viewType] || false, viewType]; + } + const actionViews = [getView("list"), getView("form")]; + this.openView(domain, actionViews, context); + } + + /** + * @param {Object} param0 + * @param {string} param0.measure + */ + onMeasureSelected({ measure }) { + this.model.updateMetaData({ measure }); + } + + /** + * @param {"bar"|"line"|"pie"} mode + */ + onModeSelected(mode) { + this.model.updateMetaData({ mode }); + } + + /** + * @param {"ASC"|"DESC"} order + */ + toggleOrder(order) { + const { order: currentOrder } = this.model.metaData; + const nextOrder = currentOrder === order ? null : order; + this.model.updateMetaData({ order: nextOrder }); + } + + toggleStacked() { + const { stacked } = this.model.metaData; + this.model.updateMetaData({ stacked: !stacked }); + } +} + +GraphController.template = "web.GraphView"; +GraphController.components = { Dropdown, DropdownItem, GroupByMenu, Layout }; + +GraphController.defaultProps = { + additionalMeasures: [], + displayGroupByMenu: false, + displayScaleLabels: true, +}; + +GraphController.props = { + ...standardViewProps, + additionalMeasures: { type: Array, elements: String, optional: true }, + displayGroupByMenu: { type: Boolean, optional: true }, + displayScaleLabels: { type: Boolean, optional: true }, + Model: Function, + modelParams: Object, + Renderer: Function, + buttonTemplate: String, +}; diff --git a/addons/web/static/src/views/graph/graph_view.xml b/addons/web/static/src/views/graph/graph_controller.xml similarity index 97% rename from addons/web/static/src/views/graph/graph_view.xml rename to addons/web/static/src/views/graph/graph_controller.xml index 077ea4ea42e..0f5432bd93d 100644 --- a/addons/web/static/src/views/graph/graph_view.xml +++ b/addons/web/static/src/views/graph/graph_controller.xml @@ -49,13 +49,13 @@
- + - { let modelParams; - if (this.props.state) { - modelParams = this.props.state.metaData; + if (genericProps.state) { + modelParams = genericProps.state.metaData; } else { - const { arch, fields } = this.props; - const parser = new this.constructor.ArchParser(); + const { arch, fields, resModel, additionalMeasures, displayScaleLabels } = genericProps; + const parser = new view.ArchParser(); const archInfo = parser.parse(arch, fields); modelParams = { - additionalMeasures: this.props.additionalMeasures, + additionalMeasures: additionalMeasures, disableLinking: Boolean(archInfo.disableLinking), - displayScaleLabels: this.props.displayScaleLabels, + displayScaleLabels: displayScaleLabels, fieldAttrs: archInfo.fieldAttrs, - fields: this.props.fields, + fields: fields, groupBy: archInfo.groupBy, measure: archInfo.measure || "__count", mode: archInfo.mode || "bar", order: archInfo.order || null, - resModel: this.props.resModel, + resModel: resModel, stacked: "stacked" in archInfo ? archInfo.stacked : true, - title: archInfo.title || this.env._t("Untitled"), + title: archInfo.title || _lt("Untitled"), }; } - this.model = useModel(this.constructor.Model, modelParams); - - useSetupView({ - getLocalState: () => { - return { metaData: this.model.metaData }; - }, - getContext: () => this.getContext(), - }); - } - - /** - * @returns {Object} - */ - getContext() { - // expand context object? change keys? - const { measure, groupBy, mode } = this.model.metaData; return { - graph_measure: measure, - graph_mode: mode, - graph_groupbys: groupBy.map((gb) => gb.spec), + ...genericProps, + modelParams, + Model: view.Model, + Renderer: view.Renderer, + buttonTemplate: view.buttonTemplate, }; - } - - /** - * Execute the action to open the view on the current model. - * - * @param {Array} domain - * @param {Array} views - * @param {Object} context - */ - openView(domain, views, context) { - this.actionService.doAction( - { - context, - domain, - name: this.model.metaData.title, - res_model: this.model.metaData.resModel, - target: "current", - type: "ir.actions.act_window", - views: views, - }, - { - viewType: "list", - } - ); - } - /** - * @param {string} domain the domain of the clicked area - */ - onGraphClicked(domain) { - const { context } = this.model.metaData; - - Object.keys(context).forEach((x) => { - if (x === "group_by" || x.startsWith("search_default_")) { - delete context[x]; - } - }); - - const views = {}; - for (const [viewId, viewType] of this.env.config.views || []) { - views[viewType] = viewId; - } - function getView(viewType) { - return [views[viewType] || false, viewType]; - } - const actionViews = [getView("list"), getView("form")]; - this.openView(domain, actionViews, context); - } - - /** - * @param {Object} param0 - * @param {string} param0.measure - */ - onMeasureSelected({ measure }) { - this.model.updateMetaData({ measure }); - } - - /** - * @param {"bar"|"line"|"pie"} mode - */ - onModeSelected(mode) { - this.model.updateMetaData({ mode }); - } - - /** - * @param {"ASC"|"DESC"} order - */ - toggleOrder(order) { - const { order: currentOrder } = this.model.metaData; - const nextOrder = currentOrder === order ? null : order; - this.model.updateMetaData({ order: nextOrder }); - } - - toggleStacked() { - const { stacked } = this.model.metaData; - this.model.updateMetaData({ stacked: !stacked }); - } -} - -GraphView.template = "web.GraphView"; -GraphView.buttonTemplate = "web.GraphView.Buttons"; - -GraphView.components = { Dropdown, DropdownItem, GroupByMenu, Renderer: GraphRenderer, Layout }; - -GraphView.defaultProps = { - additionalMeasures: [], - displayGroupByMenu: false, - displayScaleLabels: true, + }, }; -GraphView.props = { - ...standardViewProps, - additionalMeasures: { type: Array, elements: String, optional: true }, - displayGroupByMenu: { type: Boolean, optional: true }, - displayScaleLabels: { type: Boolean, optional: true }, -}; - -GraphView.type = "graph"; - -GraphView.display_name = _lt("Graph"); -GraphView.icon = "fa fa-area-chart"; -GraphView.multiRecord = true; - -GraphView.Model = GraphModel; - -GraphView.ArchParser = GraphArchParser; - -GraphView.searchMenuTypes = ["filter", "groupBy", "comparison", "favorite"]; - -viewRegistry.add("graph", GraphView); +viewRegistry.add("graph", graphView); diff --git a/addons/web/static/src/views/pivot/pivot_controller.js b/addons/web/static/src/views/pivot/pivot_controller.js new file mode 100644 index 00000000000..dc4b82329da --- /dev/null +++ b/addons/web/static/src/views/pivot/pivot_controller.js @@ -0,0 +1,145 @@ +/** @odoo-module **/ + +import { Dropdown } from "@web/core/dropdown/dropdown"; +import { DropdownItem } from "@web/core/dropdown/dropdown_item"; +import { download } from "@web/core/network/download"; +import { useService } from "@web/core/utils/hooks"; +import { Layout } from "@web/search/layout"; +import { useModel } from "@web/views/helpers/model"; +import { standardViewProps } from "@web/views/helpers/standard_view_props"; +import { useSetupView } from "@web/views/helpers/view_hook"; + +const { Component } = owl; + +export class PivotController extends Component { + setup() { + this.actionService = useService("action"); + this.model = useModel(this.props.Model, this.props.modelParams); + + useSetupView({ + getLocalState: () => { + const { data, metaData } = this.model; + return { data, metaData }; + }, + getContext: () => this.getContext(), + }); + } + /** + * @returns {Object} + */ + getContext() { + return { + pivot_measures: this.model.metaData.activeMeasures, + pivot_column_groupby: this.model.metaData.fullColGroupBys, + pivot_row_groupby: this.model.metaData.fullRowGroupBys, + }; + } + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Exports the current pivot table data in a xls file. For this, we have to + * serialize the current state, then call the server /web/pivot/export_xlsx. + * Force a reload before exporting to ensure to export up-to-date data. + */ + onDownloadButtonClicked() { + if (this.model.getTableWidth() > 16384) { + throw new Error( + this.env._t( + "For Excel compatibility, data cannot be exported if there are more than 16384 columns.\n\nTip: try to flip axis, filter further or reduce the number of measures." + ) + ); + } + const table = this.model.exportData(); + download({ + url: "/web/pivot/export_xlsx", + data: { data: JSON.stringify(table) }, + }); + } + /** + * Expands all groups + */ + onExpandButtonClicked() { + this.model.expandAll(); + } + /** + * Flips axis + */ + onFlipButtonClicked() { + this.model.flip(); + } + /** + * Toggles the given measure + * + * @param {Object} param0 + * @param {string} param0.measure + */ + onMeasureSelected({ measure }) { + this.model.toggleMeasure(measure); + } + /** + * Execute the action to open the view on the current model. + * + * @param {Array} domain + * @param {Array} views + * @param {Object} context + */ + openView(domain, views, context) { + this.actionService.doAction({ + type: "ir.actions.act_window", + name: this.model.metaData.title, + res_model: this.props.resModel, + views: views, + view_mode: "list", + target: "current", + context, + domain, + }); + } + /** + * @param {CustomEvent} ev + */ + onOpenView(cell) { + if (cell.value === undefined || this.model.metaData.disableLinking) { + return; + } + + const context = Object.assign({}, this.model.searchParams.context); + Object.keys(context).forEach((x) => { + if (x === "group_by" || x.startsWith("search_default_")) { + delete context[x]; + } + }); + + // retrieve form and list view ids from the action + const { views = [] } = this.env.config; + this.views = ["list", "form"].map((viewType) => { + const view = views.find((view) => view[1] === viewType); + return [view ? view[0] : false, viewType]; + }); + + const group = { + rowValues: cell.groupId[0], + colValues: cell.groupId[1], + originIndex: cell.originIndexes[0], + }; + this.openView(this.model.getGroupDomain(group), this.views, context); + } +} + +PivotController.template = "web.PivotView"; +PivotController.components = { Dropdown, DropdownItem, Layout }; + +PivotController.props = { + ...standardViewProps, + additionalMeasures: { type: Array, elements: String, optional: 1 }, + Model: Function, + modelParams: Object, + Renderer: Function, + buttonTemplate: String, +}; +PivotController.defaultProps = { + additionalMeasures: [], +}; diff --git a/addons/web/static/src/views/pivot/pivot_view.xml b/addons/web/static/src/views/pivot/pivot_controller.xml similarity index 93% rename from addons/web/static/src/views/pivot/pivot_view.xml rename to addons/web/static/src/views/pivot/pivot_controller.xml index 0a7ffe8f11a..11c6f04d564 100644 --- a/addons/web/static/src/views/pivot/pivot_view.xml +++ b/addons/web/static/src/views/pivot/pivot_controller.xml @@ -20,7 +20,7 @@
- + - +
diff --git a/addons/web/static/src/views/pivot/pivot_view.js b/addons/web/static/src/views/pivot/pivot_view.js index a039dfa0772..d8ea9e95521 100644 --- a/addons/web/static/src/views/pivot/pivot_view.js +++ b/addons/web/static/src/views/pivot/pivot_view.js @@ -1,35 +1,36 @@ /** @odoo-module **/ -import { Dropdown } from "@web/core/dropdown/dropdown"; -import { DropdownItem } from "@web/core/dropdown/dropdown_item"; import { _lt } from "@web/core/l10n/translation"; -import { download } from "@web/core/network/download"; import { registry } from "@web/core/registry"; -import { useService } from "@web/core/utils/hooks"; -import { Layout } from "@web/search/layout"; -import { useModel } from "@web/views/helpers/model"; -import { standardViewProps } from "@web/views/helpers/standard_view_props"; -import { useSetupView } from "@web/views/helpers/view_hook"; import { PivotArchParser } from "@web/views/pivot/pivot_arch_parser"; +import { PivotController } from "./pivot_controller"; import { PivotModel } from "@web/views/pivot/pivot_model"; import { PivotRenderer } from "@web/views/pivot/pivot_renderer"; const viewRegistry = registry.category("views"); -const { Component } = owl; -export class PivotView extends Component { - setup() { - this.actionService = useService("action"); +export const pivotView = { + type: "pivot", + display_name: _lt("Pivot"), + icon: "oi oi-view-pivot", + multiRecord: true, + Controller: PivotController, + Renderer: PivotRenderer, + Model: PivotModel, + ArchParser: PivotArchParser, + searchMenuTypes: ["filter", "groupBy", "comparison", "favorite"], + buttonTemplate: "web.PivotView.Buttons", + props: (genericProps, view) => { let modelParams = {}; - if (this.props.state) { - modelParams.data = this.props.state.data; - modelParams.metaData = this.props.state.metaData; + if (genericProps.state) { + modelParams.data = genericProps.state.data; + modelParams.metaData = genericProps.state.metaData; } else { - const { arch } = this.props; + const { arch, additionalMeasures, fields, resModel } = genericProps; // parse arch - const archInfo = new this.constructor.ArchParser().parse(arch); + const archInfo = new view.ArchParser().parse(arch); if (!archInfo.activeMeasures.length || archInfo.displayQuantity) { archInfo.activeMeasures.unshift("__count"); @@ -37,154 +38,27 @@ export class PivotView extends Component { modelParams.metaData = { activeMeasures: archInfo.activeMeasures, - additionalMeasures: this.props.additionalMeasures, + additionalMeasures: additionalMeasures, colGroupBys: archInfo.colGroupBys, defaultOrder: archInfo.defaultOrder, disableLinking: Boolean(archInfo.disableLinking), - fields: this.props.fields, + fields: fields, fieldAttrs: archInfo.fieldAttrs, - resModel: this.props.resModel, + resModel: resModel, rowGroupBys: archInfo.rowGroupBys, - title: archInfo.title || this.env._t("Untitled"), + title: archInfo.title || _lt("Untitled"), widgets: archInfo.widgets, }; } - this.model = useModel(this.constructor.Model, modelParams); - - useSetupView({ - getLocalState: () => { - const { data, metaData } = this.model; - return { data, metaData }; - }, - getContext: () => this.getContext(), - }); - } - /** - * @returns {Object} - */ - getContext() { return { - pivot_measures: this.model.metaData.activeMeasures, - pivot_column_groupby: this.model.metaData.fullColGroupBys, - pivot_row_groupby: this.model.metaData.fullRowGroupBys, + ...genericProps, + Model: view.Model, + modelParams, + Renderer: view.Renderer, + buttonTemplate: view.buttonTemplate, }; - } - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Exports the current pivot table data in a xls file. For this, we have to - * serialize the current state, then call the server /web/pivot/export_xlsx. - * Force a reload before exporting to ensure to export up-to-date data. - */ - onDownloadButtonClicked() { - if (this.model.getTableWidth() > 16384) { - throw new Error( - this.env._t( - "For Excel compatibility, data cannot be exported if there are more than 16384 columns.\n\nTip: try to flip axis, filter further or reduce the number of measures." - ) - ); - } - const table = this.model.exportData(); - download({ - url: "/web/pivot/export_xlsx", - data: { data: JSON.stringify(table) }, - }); - } - /** - * Expands all groups - */ - onExpandButtonClicked() { - this.model.expandAll(); - } - /** - * Flips axis - */ - onFlipButtonClicked() { - this.model.flip(); - } - /** - * Toggles the given measure - * - * @param {Object} param0 - * @param {string} param0.measure - */ - onMeasureSelected({ measure }) { - this.model.toggleMeasure(measure); - } - /** - * Execute the action to open the view on the current model. - * - * @param {Array} domain - * @param {Array} views - * @param {Object} context - */ - openView(domain, views, context) { - this.actionService.doAction({ - type: "ir.actions.act_window", - name: this.model.metaData.title, - res_model: this.props.resModel, - views: views, - view_mode: "list", - target: "current", - context, - domain, - }); - } - /** - * @param {CustomEvent} ev - */ - onOpenView(cell) { - if (cell.value === undefined || this.model.metaData.disableLinking) { - return; - } - - const context = Object.assign({}, this.model.searchParams.context); - Object.keys(context).forEach((x) => { - if (x === "group_by" || x.startsWith("search_default_")) { - delete context[x]; - } - }); - - // retrieve form and list view ids from the action - const { views = [] } = this.env.config; - this.views = ["list", "form"].map((viewType) => { - const view = views.find((view) => view[1] === viewType); - return [view ? view[0] : false, viewType]; - }); - - const group = { - rowValues: cell.groupId[0], - colValues: cell.groupId[1], - originIndex: cell.originIndexes[0], - }; - this.openView(this.model.getGroupDomain(group), this.views, context); - } -} - -PivotView.template = "web.PivotView"; -PivotView.buttonTemplate = "web.PivotView.Buttons"; -PivotView.components = { Dropdown, DropdownItem, Renderer: PivotRenderer, Layout }; - -PivotView.props = { - ...standardViewProps, - additionalMeasures: { type: Array, elements: String, optional: 1 }, -}; -PivotView.defaultProps = { - additionalMeasures: [], + }, }; -PivotView.Model = PivotModel; - -PivotView.ArchParser = PivotArchParser; - -PivotView.type = "pivot"; -PivotView.display_name = _lt("Pivot"); -PivotView.icon = "oi oi-view-pivot"; -PivotView.multiRecord = true; -PivotView.searchMenuTypes = ["filter", "groupBy", "comparison", "favorite"]; - -viewRegistry.add("pivot", PivotView); +viewRegistry.add("pivot", pivotView); diff --git a/addons/web/static/src/views/view.js b/addons/web/static/src/views/view.js index 7f85bd9fc4a..67462af3ae9 100644 --- a/addons/web/static/src/views/view.js +++ b/addons/web/static/src/views/view.js @@ -162,8 +162,8 @@ export class View extends Component { async onWillStart() { // determine view type - let ViewClass = viewRegistry.get(this.props.type); - const type = ViewClass.type; + let descr = viewRegistry.get(this.props.type); + const type = descr.type; // determine views for which descriptions should be obtained let { viewId, searchViewId } = this.props; @@ -249,7 +249,7 @@ export class View extends Component { // determine ViewClass to instantiate (if not already done) if (subType) { - ViewClass = viewRegistry.get(subType); + descr = viewRegistry.get(subType); } Object.assign(this.env.config, { @@ -257,7 +257,7 @@ export class View extends Component { viewType: type, viewSubType: subType, bannerRoute, - ...extractLayoutComponents(ViewClass), + ...extractLayoutComponents(descr), }); // prepare the view props @@ -291,11 +291,13 @@ export class View extends Component { viewProps.info.noContentHelp = noContentHelp; } + const finalProps = descr.props ? descr.props(viewProps, descr, this.env.config) : viewProps; // prepare the WithSearch component props this.withSearchProps = { ...toRaw(this.props), - Component: ViewClass, - componentProps: viewProps, + Component: descr.Controller, + SearchModel: descr.SearchModel, + componentProps: finalProps, }; if (searchViewId !== undefined) { @@ -312,7 +314,7 @@ export class View extends Component { if (!this.withSearchProps.searchMenuTypes) { this.withSearchProps.searchMenuTypes = this.props.searchMenuTypes || - ViewClass.searchMenuTypes || + descr.searchMenuTypes || this.constructor.searchMenuTypes; } diff --git a/addons/web/static/tests/core/debug/debug_manager_tests.js b/addons/web/static/tests/core/debug/debug_manager_tests.js index 6a04a5fd2cc..e37d49f4435 100644 --- a/addons/web/static/tests/core/debug/debug_manager_tests.js +++ b/addons/web/static/tests/core/debug/debug_manager_tests.js @@ -422,15 +422,18 @@ QUnit.module("DebugMenu", (hooks) => { // removing the toy view and using the kanban view directly prepareRegistriesWithCleanup(); - class ToyView extends Component { + class ToyController extends Component { setup() { useSetupView(); } } - ToyView.template = xml`
`; - ToyView.type = "toy"; - ToyView.display_name = "toy view"; - registry.category("views").add("toy", ToyView); + ToyController.template = xml`
`; + + registry.category("views").add("toy", { + type: "toy", + display_name: "toy view", + Controller: ToyController, + }); const mockRPC = async (route, args) => { if (args.method === "check_access_rights") { diff --git a/addons/web/static/tests/legacy/views/state_mapping_tests.js b/addons/web/static/tests/legacy/views/state_mapping_tests.js index d6399e705d9..fda85b64a5a 100644 --- a/addons/web/static/tests/legacy/views/state_mapping_tests.js +++ b/addons/web/static/tests/legacy/views/state_mapping_tests.js @@ -79,18 +79,17 @@ QUnit.module("Views", (hooks) => { setupControlPanelServiceRegistry(); serviceRegistry.add("dialog", dialogService); - class ToyView extends LegacyComponent {} - ToyView.components = { ControlPanel }; - ToyView.display_name = _lt("Toy view"); - ToyView.icon = "fab fa-android"; - ToyView.multiRecord = true; - ToyView.searchMenuTypes = ["filter", "groupBy", "comparison", "favorite"]; - ToyView.template = xml` -
- -
- `; - ToyView.type = "toy"; + class ToyController extends LegacyComponent {} + ToyController.template = xml`
`; + ToyController.components = { ControlPanel}; + + viewRegistry.add("toy", { + type: "toy", + display_name: _lt("Toy view"), + multiRecord: true, + searchMenuTypes: ["filter", "groupBy", "comparison", "favorite"], + Controller: ToyController, + }); const LegacyToyView = AbstractView.extend({ display_name: _lt("Legacy toy view"), @@ -100,7 +99,6 @@ QUnit.module("Views", (hooks) => { searchMenuTypes: ["filter", "groupBy", "comparison", "favorite"], }); - viewRegistry.add("toy", ToyView); legacyViewRegistry.add("legacy_toy", LegacyToyView); }); diff --git a/addons/web/static/tests/search/favorite_menu_tests.js b/addons/web/static/tests/search/favorite_menu_tests.js index 379c5ed0acf..e85903d6275 100644 --- a/addons/web/static/tests/search/favorite_menu_tests.js +++ b/addons/web/static/tests/search/favorite_menu_tests.js @@ -130,7 +130,7 @@ QUnit.module("Search", (hooks) => { QUnit.test("delete an active favorite", async function (assert) { assert.expect(11); - class ToyView extends Component { + class ToyController extends Component { setup() { assert.deepEqual(this.props.domain, [["foo", "=", "qsdf"]]); onWillUpdateProps((nextProps) => { @@ -138,17 +138,19 @@ QUnit.module("Search", (hooks) => { }); } } - ToyView.components = { FavoriteMenu, SearchBar }; - ToyView.template = xml` + ToyController.components = { FavoriteMenu, SearchBar }; + ToyController.template = xml`
`; - ToyView.type = "toy"; - ToyView.display_name = "Toy"; - viewRegistry.add("toy", ToyView); + viewRegistry.add("toy", { + type: "toy", + display_name: "Toy", + Controller: ToyController, + }); serverData.views["foo,false,toy"] = ``; serverData.models.foo.filters = [ diff --git a/addons/web/static/tests/views/pivot_view_tests.js b/addons/web/static/tests/views/pivot_view_tests.js index 46b5e519f4b..8f2d471f7f4 100644 --- a/addons/web/static/tests/views/pivot_view_tests.js +++ b/addons/web/static/tests/views/pivot_view_tests.js @@ -348,7 +348,7 @@ QUnit.module("Views", (hooks) => { // this is important for export functionality. assert.strictEqual( - pivot.model.metaData.title, + pivot.model.metaData.title.toString(), pivot.env._t("Untitled"), "should have a valid title" ); diff --git a/addons/web/static/tests/views/view_tests.js b/addons/web/static/tests/views/view_tests.js index fa15569ae21..5a694a025c1 100644 --- a/addons/web/static/tests/views/view_tests.js +++ b/addons/web/static/tests/views/view_tests.js @@ -71,25 +71,29 @@ QUnit.module("Views", (hooks) => { }, }; - class ToyView extends Component { + class ToyController extends Component { setup() { this.class = "toy"; this.template = xml`${this.props.arch}`; } } - ToyView.template = xml`
`; - ToyView.type = "toy"; - ToyView.components = { Banner: OnboardingBanner }; + ToyController.template = xml`
`; + ToyController.components = { Banner: OnboardingBanner }; - class ToyViewImp extends ToyView { + const toyView = { + type: "toy", + Controller: ToyController, + }; + + class ToyControllerImp extends ToyController { setup() { super.setup(); this.class = "toy_imp"; } } - viewRegistry.add("toy", ToyView); - viewRegistry.add("toy_imp", ToyViewImp); + viewRegistry.add("toy", toyView); + viewRegistry.add("toy_imp", { ...toyView, Controller: ToyControllerImp }); setupControlPanelServiceRegistry(); @@ -115,8 +119,8 @@ QUnit.module("Views", (hooks) => { QUnit.test("simple rendering", async function (assert) { assert.expect(10); - const ToyView = viewRegistry.get("toy"); - patchWithCleanup(ToyView.prototype, { + const ToyController = viewRegistry.get("toy").Controller; + patchWithCleanup(ToyController.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; @@ -153,8 +157,8 @@ QUnit.module("Views", (hooks) => { QUnit.test("rendering with given viewId", async function (assert) { assert.expect(8); - const ToyView = viewRegistry.get("toy"); - patchWithCleanup(ToyView.prototype, { + const ToyController = viewRegistry.get("toy").Controller; + patchWithCleanup(ToyController.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; @@ -190,8 +194,8 @@ QUnit.module("Views", (hooks) => { QUnit.test("rendering with given 'views' param", async function (assert) { assert.expect(8); - const ToyView = viewRegistry.get("toy"); - patchWithCleanup(ToyView.prototype, { + const ToyController = viewRegistry.get("toy").Controller; + patchWithCleanup(ToyController.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; @@ -231,8 +235,8 @@ QUnit.module("Views", (hooks) => { async function (assert) { assert.expect(8); - const ToyView = viewRegistry.get("toy"); - patchWithCleanup(ToyView.prototype, { + const ToyController = viewRegistry.get("toy").Controller; + patchWithCleanup(ToyController.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; @@ -274,8 +278,8 @@ QUnit.module("Views", (hooks) => { QUnit.test("viewId defined as prop and in 'views' prop", async function (assert) { assert.expect(8); - const ToyView = viewRegistry.get("toy"); - patchWithCleanup(ToyView.prototype, { + const ToyController = viewRegistry.get("toy").Controller; + patchWithCleanup(ToyController.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; @@ -320,8 +324,8 @@ QUnit.module("Views", (hooks) => { QUnit.test("rendering with given arch and fields", async function (assert) { assert.expect(6); - const ToyView = viewRegistry.get("toy"); - patchWithCleanup(ToyView.prototype, { + const ToyController = viewRegistry.get("toy").Controller; + patchWithCleanup(ToyController.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; @@ -353,8 +357,8 @@ QUnit.module("Views", (hooks) => { QUnit.test("rendering with loadActionMenus='true'", async function (assert) { assert.expect(8); - const ToyView = viewRegistry.get("toy"); - patchWithCleanup(ToyView.prototype, { + const ToyController = viewRegistry.get("toy").Controller; + patchWithCleanup(ToyController.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; @@ -393,8 +397,8 @@ QUnit.module("Views", (hooks) => { async function (assert) { assert.expect(8); - const ToyView = viewRegistry.get("toy"); - patchWithCleanup(ToyView.prototype, { + const ToyController = viewRegistry.get("toy").Controller; + patchWithCleanup(ToyController.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; @@ -436,8 +440,8 @@ QUnit.module("Views", (hooks) => { async function (assert) { assert.expect(6); - const ToyView = viewRegistry.get("toy"); - patchWithCleanup(ToyView.prototype, { + const ToyController = viewRegistry.get("toy").Controller; + patchWithCleanup(ToyController.prototype, { setup() { this._super(); const { arch, fields, info } = this.props; @@ -474,8 +478,8 @@ QUnit.module("Views", (hooks) => { QUnit.test("rendering with given searchViewId", async function (assert) { assert.expect(8); - const ToyView = viewRegistry.get("toy"); - patchWithCleanup(ToyView.prototype, { + const ToyController = viewRegistry.get("toy").Controller; + patchWithCleanup(ToyController.prototype, { setup() { this._super(); const { @@ -522,8 +526,8 @@ QUnit.module("Views", (hooks) => { async function (assert) { assert.expect(6); - const ToyView = viewRegistry.get("toy"); - patchWithCleanup(ToyView.prototype, { + const ToyController = viewRegistry.get("toy").Controller; + patchWithCleanup(ToyController.prototype, { setup() { this._super(); const { @@ -566,8 +570,8 @@ QUnit.module("Views", (hooks) => { async function (assert) { assert.expect(6); - const ToyView = viewRegistry.get("toy"); - patchWithCleanup(ToyView.prototype, { + const ToyController = viewRegistry.get("toy").Controller; + patchWithCleanup(ToyController.prototype, { setup() { this._super(); const { @@ -609,8 +613,8 @@ QUnit.module("Views", (hooks) => { async function (assert) { assert.expect(8); - const ToyView = viewRegistry.get("toy"); - patchWithCleanup(ToyView.prototype, { + const ToyController = viewRegistry.get("toy").Controller; + patchWithCleanup(ToyController.prototype, { setup() { this._super(); const { @@ -674,8 +678,8 @@ QUnit.module("Views", (hooks) => { }, ]; - const ToyView = viewRegistry.get("toy"); - patchWithCleanup(ToyView.prototype, { + const ToyController = viewRegistry.get("toy").Controller; + patchWithCleanup(ToyController.prototype, { setup() { this._super(); const { @@ -1018,14 +1022,18 @@ QUnit.module("Views", (hooks) => {
`; let toy; - const ToyView = viewRegistry.get("toy"); - class ToyViewExtended extends ToyView { + const toyView = viewRegistry.get("toy"); + class ToyViewExtendedController extends toyView.Controller { setup() { super.setup(); toy = this; } } - viewRegistry.add("toy", ToyViewExtended, { force: true }); + viewRegistry.add( + "toy", + { ...toyView, Controller: ToyViewExtendedController }, + { force: true } + ); const mockRPC = (route) => { if (route === "/mybody/isacage") { @@ -1407,7 +1415,7 @@ QUnit.module("Views", (hooks) => { async function (assert) { assert.expect(4); - class ToyView extends Component { + class ToyController extends Component { setup() { const { context, domain, groupBy, orderBy } = this.props; assert.deepEqual(context, { @@ -1421,10 +1429,9 @@ QUnit.module("Views", (hooks) => { assert.deepEqual(orderBy, ["bar"]); } } - ToyView.template = xml`
`; - ToyView.type = "toy"; + ToyController.template = xml`
`; - viewRegistry.add("toy", ToyView, { force: true }); + viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const env = await makeTestEnv({ serverData }); const props = { @@ -1442,14 +1449,13 @@ QUnit.module("Views", (hooks) => { QUnit.test("non empty prop 'noContentHelp'", async function (assert) { assert.expect(1); - class ToyView extends Component { + class ToyController extends Component { setup() { assert.strictEqual(this.props.info.noContentHelp, "
Help
"); } } - ToyView.template = xml`
`; - ToyView.type = "toy"; - viewRegistry.add("toy", ToyView, { force: true }); + ToyController.template = xml`
`; + viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const env = await makeTestEnv({ serverData }); const props = { @@ -1463,14 +1469,13 @@ QUnit.module("Views", (hooks) => { QUnit.test("useSampleModel false by default", async function (assert) { assert.expect(1); - class ToyView extends Component { + class ToyController extends Component { setup() { assert.strictEqual(this.props.useSampleModel, false); } } - ToyView.template = xml`
`; - ToyView.type = "toy"; - viewRegistry.add("toy", ToyView, { force: true }); + ToyController.template = xml`
`; + viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const env = await makeTestEnv({ serverData }); const props = { resModel: "animal", type: "toy" }; @@ -1480,14 +1485,13 @@ QUnit.module("Views", (hooks) => { QUnit.test("sample='1' on arch", async function (assert) { assert.expect(1); - class ToyView extends Component { + class ToyController extends Component { setup() { assert.strictEqual(this.props.useSampleModel, true); } } - ToyView.template = xml`
`; - ToyView.type = "toy"; - viewRegistry.add("toy", ToyView, { force: true }); + ToyController.template = xml`
`; + viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const env = await makeTestEnv({ serverData }); const props = { @@ -1502,14 +1506,13 @@ QUnit.module("Views", (hooks) => { QUnit.test("sample='0' on arch and useSampleModel=true", async function (assert) { assert.expect(1); - class ToyView extends Component { + class ToyController extends Component { setup() { assert.strictEqual(this.props.useSampleModel, true); } } - ToyView.template = xml`
`; - ToyView.type = "toy"; - viewRegistry.add("toy", ToyView, { force: true }); + ToyController.template = xml`
`; + viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const env = await makeTestEnv({ serverData }); const props = { @@ -1525,14 +1528,13 @@ QUnit.module("Views", (hooks) => { QUnit.test("sample='1' on arch and useSampleModel=false", async function (assert) { assert.expect(1); - class ToyView extends Component { + class ToyController extends Component { setup() { assert.strictEqual(this.props.useSampleModel, false); } } - ToyView.template = xml`
`; - ToyView.type = "toy"; - viewRegistry.add("toy", ToyView, { force: true }); + ToyController.template = xml`
`; + viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const env = await makeTestEnv({ serverData }); const props = { @@ -1548,14 +1550,13 @@ QUnit.module("Views", (hooks) => { QUnit.test("useSampleModel=true", async function (assert) { assert.expect(1); - class ToyView extends Component { + class ToyController extends Component { setup() { assert.strictEqual(this.props.useSampleModel, true); } } - ToyView.template = xml`
`; - ToyView.type = "toy"; - viewRegistry.add("toy", ToyView, { force: true }); + ToyController.template = xml`
`; + viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const env = await makeTestEnv({ serverData }); const props = { resModel: "animal", type: "toy", useSampleModel: true }; @@ -1565,14 +1566,13 @@ QUnit.module("Views", (hooks) => { QUnit.test("rendering with given prop", async function (assert) { assert.expect(1); - class ToyView extends Component { + class ToyController extends Component { setup() { assert.strictEqual(this.props.specificProp, "specificProp"); } } - ToyView.template = xml`
`; - ToyView.type = "toy"; - viewRegistry.add("toy", ToyView, { force: true }); + ToyController.template = xml`
`; + viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const env = await makeTestEnv({ serverData }); const props = { resModel: "animal", type: "toy", specificProp: "specificProp" }; @@ -1584,7 +1584,7 @@ QUnit.module("Views", (hooks) => { async function (assert) { assert.expect(4); - class ToyView extends Component { + class ToyController extends Component { setup() { const { context, domain, groupBy, orderBy } = this.props; assert.deepEqual(context, { @@ -1597,9 +1597,8 @@ QUnit.module("Views", (hooks) => { assert.deepEqual(orderBy, ["bar"]); } } - ToyView.template = xml`
`; - ToyView.type = "toy"; - viewRegistry.add("toy", ToyView, { force: true }); + ToyController.template = xml`
`; + viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const env = await makeTestEnv({ serverData }); const props = { @@ -1622,7 +1621,7 @@ QUnit.module("Views", (hooks) => { QUnit.test("react to prop 'domain' changes", async function (assert) { assert.expect(2); - class ToyView extends Component { + class ToyController extends Component { setup() { onWillStart(() => { assert.deepEqual(this.props.domain, [["type", "=", "carnivorous"]]); @@ -1632,9 +1631,8 @@ QUnit.module("Views", (hooks) => { }); } } - ToyView.template = xml`
`; - ToyView.type = "toy"; - viewRegistry.add("toy", ToyView, { force: true }); + ToyController.template = xml`
`; + viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true }); const env = await makeTestEnv({ serverData }); diff --git a/addons/web/static/tests/webclient/actions/close_action_tests.js b/addons/web/static/tests/webclient/actions/close_action_tests.js index 77d38da3fd4..a852e580d89 100644 --- a/addons/web/static/tests/webclient/actions/close_action_tests.js +++ b/addons/web/static/tests/webclient/actions/close_action_tests.js @@ -12,7 +12,7 @@ import { createWebClient, doAction, getActionManagerServerData } from "./../help import FormController from "web.FormController"; import { makeFakeUserService } from "@web/../tests/helpers/mock_services"; import ListController from "web.ListController"; -import { PivotView } from "@web/views/pivot/pivot_view"; +import { pivotView } from "@web/views/pivot/pivot_view"; import { registry } from "@web/core/registry"; const serviceRegistry = registry.category("services"); @@ -194,7 +194,7 @@ QUnit.module("ActionManager", (hooks) => { async function (assert) { assert.expect(7); let pivot; - patchWithCleanup(PivotView.prototype, { + patchWithCleanup(pivotView.Controller.prototype, { setup() { this._super(...arguments); pivot = this; diff --git a/addons/web/static/tests/webclient/actions/concurrency_tests.js b/addons/web/static/tests/webclient/actions/concurrency_tests.js index 31292228366..2bfb5af3048 100644 --- a/addons/web/static/tests/webclient/actions/concurrency_tests.js +++ b/addons/web/static/tests/webclient/actions/concurrency_tests.js @@ -669,7 +669,7 @@ QUnit.module("ActionManager", (hooks) => { let def = Promise.resolve(); let id = 1; - class ToyView extends Component { + class ToyController extends Component { setup() { this.id = id++; assert.step(JSON.stringify(this.props.state || "no state")); @@ -681,18 +681,20 @@ QUnit.module("ActionManager", (hooks) => { owl.onWillStart(() => def); } } - ToyView.components = { ControlPanel }; - ToyView.display_name = "Toy"; - ToyView.icon = "fab fa-android"; - ToyView.multiRecord = true; - ToyView.searchMenuTypes = ["filter"]; - ToyView.template = xml` + ToyController.template = xml`
-
- `; - ToyView.type = "toy"; - registry.category("views").add("toy", ToyView); +
`; + ToyController.components = { ControlPanel }; + + registry.category("views").add("toy", { + type: "toy", + display_name: "Toy", + icon: "fab fa-android", + multiRecord: true, + searchMenuTypes: ["filter"], + Controller: ToyController, + }); const webClient = await createWebClient({ serverData });