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 });