[REF] web, *: improve the way views are defined, reintroduce controllers
Earlier in Odoo, JS views were defined by defining 4 elements: View, Controller, Model, Renderer. This was complex in some way, because we wanted to inherit behaviour as well, so it was necessary to think along multiple dimensions to understand how the code was running. Then, with Owl, we rewrote some views, and simplified them: views were now just a Component. Most of the common behaviour now came from the generic View component that instantiated the concrete view with the proper informations. In practice, views were still split in views (which was the equivalent of the Controller of earlier views), Model and Renderer Now, this commit reintroduce the Controller, and change the way views are defined: by an object with multiple metadata, and an (optional) props function to compute the actual props used by the view. As a result, views are now much easier to extend/modify. closes odoo/odoo#89889 Related: odoo/enterprise#26728 Signed-off-by: Géry Debongnie <ged@odoo.com>
This commit is contained in:
@@ -3,10 +3,10 @@ import { ForecastKanbanController } from './forecast_controllers';
|
|||||||
import { ForecastKanbanModel } from './forecast_models';
|
import { ForecastKanbanModel } from './forecast_models';
|
||||||
import { ForecastKanbanRenderer } from './forecast_renderers';
|
import { ForecastKanbanRenderer } from './forecast_renderers';
|
||||||
import { ForecastSearchModel } from "./forecast_search_model";
|
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 KanbanView from 'web.KanbanView';
|
||||||
import ListView from 'web.ListView';
|
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 { registry } from "@web/core/registry";
|
||||||
import viewRegistry from 'web.view_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
|
* - context key `forecast_field` on a date/datetime field
|
||||||
* - special filter "Forecast" (which must set the `forecast_filter:1` context key)
|
* - special filter "Forecast" (which must set the `forecast_filter:1` context key)
|
||||||
*/
|
*/
|
||||||
class ForecastGraphView extends GraphView {}
|
export const forecastGraphView = {
|
||||||
ForecastGraphView.SearchModel = ForecastSearchModel;
|
...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
|
* 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
|
* - context key `forecast_field` on a date/datetime field
|
||||||
* - special filter "Forecast" (which must set the `forecast_filter:1` context key)
|
* - special filter "Forecast" (which must set the `forecast_filter:1` context key)
|
||||||
*/
|
*/
|
||||||
class ForecastPivotView extends PivotView {}
|
export const forecastPivotView = {
|
||||||
ForecastPivotView.SearchModel = ForecastSearchModel;
|
...pivotView,
|
||||||
|
SearchModel: ForecastSearchModel,
|
||||||
|
};
|
||||||
|
|
||||||
registry.category("views").add("forecast_pivot", ForecastPivotView);
|
registry.category("views").add("forecast_pivot", forecastPivotView);
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ForecastGraphView,
|
|
||||||
ForecastKanbanView,
|
ForecastKanbanView,
|
||||||
ForecastListView,
|
ForecastListView,
|
||||||
ForecastPivotView,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/** @odoo-module **/
|
/** @odoo-module **/
|
||||||
|
|
||||||
import { registry } from "@web/core/registry";
|
import { registry } from "@web/core/registry";
|
||||||
import { GraphView } from "@web/views/graph/graph_view";
|
import { graphView } from "@web/views/graph/graph_view";
|
||||||
import { PivotView } from "@web/views/pivot/pivot_view";
|
import { pivotView } from "@web/views/pivot/pivot_view";
|
||||||
|
|
||||||
const viewRegistry = registry.category("views");
|
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
|
* @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
|
* @override
|
||||||
*/
|
*/
|
||||||
@@ -42,4 +45,7 @@ export class AttendanceReportPivotView extends PivotView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewRegistry.add("attendance_report_pivot", AttendanceReportPivotView);
|
viewRegistry.add("attendance_report_pivot", {
|
||||||
|
...pivotView,
|
||||||
|
Controller: AttendanceReportPivotController
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
/** @odoo-module **/
|
/** @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 { hrTimesheetGraphModel } from "./timesheet_graph_model";
|
||||||
import { registry } from "@web/core/registry";
|
import { registry } from "@web/core/registry";
|
||||||
|
|
||||||
const viewRegistry = registry.category("views");
|
const viewRegistry = registry.category("views");
|
||||||
|
|
||||||
export class hrTimesheetGraphView extends ProjectGraphView {}
|
export const hrTimesheetGraphView = {
|
||||||
hrTimesheetGraphView.Model = hrTimesheetGraphModel;
|
...projectGraphView,
|
||||||
|
Model: hrTimesheetGraphModel,
|
||||||
|
};
|
||||||
|
|
||||||
viewRegistry.add("hr_timesheet_graphview", hrTimesheetGraphView);
|
viewRegistry.add("hr_timesheet_graphview", hrTimesheetGraphView);
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
/** @odoo-module **/
|
/** @odoo-module **/
|
||||||
|
|
||||||
import { BurndownChartPivotModel } from "./burndown_chart_pivot_model";
|
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";
|
import { registry } from "@web/core/registry";
|
||||||
|
|
||||||
const viewRegistry = registry.category("views");
|
const viewRegistry = registry.category("views");
|
||||||
|
|
||||||
class BurndownChartPivotView extends PivotView {}
|
const burndownChartPivotView = {...pivotView, Model: BurndownChartPivotModel};
|
||||||
BurndownChartPivotView.Model = BurndownChartPivotModel;
|
viewRegistry.add("burndown_chart_pivot", burndownChartPivotView);
|
||||||
|
|
||||||
viewRegistry.add("burndown_chart_pivot", BurndownChartPivotView);
|
|
||||||
|
|||||||
@@ -2,14 +2,16 @@
|
|||||||
|
|
||||||
import { BurndownChartModel } from "./burndown_chart_model";
|
import { BurndownChartModel } from "./burndown_chart_model";
|
||||||
import { BurndownChartRenderer } from "./burndown_chart_renderer";
|
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";
|
import { registry } from "@web/core/registry";
|
||||||
|
|
||||||
const viewRegistry = registry.category("views");
|
const viewRegistry = registry.category("views");
|
||||||
|
|
||||||
class BurndownChartView extends GraphView {}
|
const burndownChartGraphView = {
|
||||||
BurndownChartView.components = { ...GraphView.components, Renderer: BurndownChartRenderer };
|
...graphView,
|
||||||
BurndownChartView.buttonTemplate = "project.BurndownChartView.Buttons";
|
Renderer: BurndownChartRenderer,
|
||||||
BurndownChartView.Model = BurndownChartModel;
|
buttonTemplate: "project.BurndownChartView.Buttons",
|
||||||
|
Model: BurndownChartModel,
|
||||||
|
};
|
||||||
|
|
||||||
viewRegistry.add("burndown_chart", BurndownChartView);
|
viewRegistry.add("burndown_chart", burndownChartGraphView);
|
||||||
|
|||||||
@@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
import { ProjectControlPanel } from "@project/project_control_panel/project_control_panel";
|
import { ProjectControlPanel } from "@project/project_control_panel/project_control_panel";
|
||||||
import { registry } from "@web/core/registry";
|
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");
|
const viewRegistry = registry.category("views");
|
||||||
|
|
||||||
export class ProjectGraphView extends GraphView {}
|
export const projectGraphView = {...graphView, ControlPanel: ProjectControlPanel};
|
||||||
ProjectGraphView.ControlPanel = ProjectControlPanel;
|
|
||||||
|
|
||||||
viewRegistry.add("project_graph", ProjectGraphView);
|
viewRegistry.add("project_graph", projectGraphView);
|
||||||
|
|||||||
@@ -2,11 +2,8 @@
|
|||||||
|
|
||||||
import { ProjectControlPanel } from "@project/project_control_panel/project_control_panel";
|
import { ProjectControlPanel } from "@project/project_control_panel/project_control_panel";
|
||||||
import { registry } from "@web/core/registry";
|
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 {}
|
registry.category("views").add("project_pivot", projectPivotView);
|
||||||
ProjectPivotView.ControlPanel = ProjectControlPanel;
|
|
||||||
|
|
||||||
viewRegistry.add("project_pivot", ProjectPivotView);
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { _lt } from "@web/core/l10n/translation";
|
import { _lt } from "@web/core/l10n/translation";
|
||||||
import { registry } from "@web/core/registry";
|
import { registry } from "@web/core/registry";
|
||||||
import { GraphArchParser } from "@web/views/graph/graph_arch_parser";
|
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");
|
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?
|
// Would it be not better achiedved by using a proper arch directly?
|
||||||
|
const projectRatingGraphView = {
|
||||||
|
...graphView,
|
||||||
|
ArchParser: ProjectRatingArchParser,
|
||||||
|
};
|
||||||
|
|
||||||
class ProjectRatingGraphView extends GraphView {}
|
viewRegistry.add("project_rating_graph", projectRatingGraphView);
|
||||||
ProjectRatingGraphView.ArchParser = ProjectRatingArchParser;
|
|
||||||
|
|
||||||
viewRegistry.add("project_rating_graph", ProjectRatingGraphView);
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { _lt } from "@web/core/l10n/translation";
|
import { _lt } from "@web/core/l10n/translation";
|
||||||
import { registry } from "@web/core/registry";
|
import { registry } from "@web/core/registry";
|
||||||
import { PivotArchParser } from "@web/views/pivot/pivot_arch_parser";
|
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");
|
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?
|
// Would it be not better achiedved by using a proper arch directly?
|
||||||
|
|
||||||
class ProjectRatingPivotView extends PivotView {}
|
const projectRatingPivotView = {
|
||||||
ProjectRatingPivotView.ArchParser = ProjectRatingArchParser;
|
...pivotView,
|
||||||
|
ArchParser: ProjectRatingArchParser,
|
||||||
|
};
|
||||||
|
|
||||||
viewRegistry.add("project_rating_pivot", ProjectRatingPivotView);
|
viewRegistry.add("project_rating_pivot", projectRatingPivotView);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export class WithSearch extends Component {
|
|||||||
useSubEnv({ __getContext__: new CallbackRecorder() });
|
useSubEnv({ __getContext__: new CallbackRecorder() });
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchModelClass = this.Component.SearchModel || SearchModel;
|
const SearchModelClass = this.props.SearchModel || SearchModel;
|
||||||
this.searchModel = new SearchModelClass(this.env, {
|
this.searchModel = new SearchModelClass(this.env, {
|
||||||
user: useService("user"),
|
user: useService("user"),
|
||||||
orm: useService("orm"),
|
orm: useService("orm"),
|
||||||
@@ -77,6 +77,7 @@ WithSearch.defaultProps = {
|
|||||||
};
|
};
|
||||||
WithSearch.props = {
|
WithSearch.props = {
|
||||||
Component: Function,
|
Component: Function,
|
||||||
|
SearchModel: { type: Function, optional: true },
|
||||||
componentProps: { type: Object, optional: true },
|
componentProps: { type: Object, optional: true },
|
||||||
|
|
||||||
resModel: String,
|
resModel: String,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
+2
-2
@@ -49,13 +49,13 @@
|
|||||||
<div t-att-class="props.className">
|
<div t-att-class="props.className">
|
||||||
<Layout className="model.useSampleModel ? 'o_view_sample_data' : ''">
|
<Layout className="model.useSampleModel ? 'o_view_sample_data' : ''">
|
||||||
<t t-set-slot="control-panel-bottom-left">
|
<t t-set-slot="control-panel-bottom-left">
|
||||||
<t t-call="{{ constructor.buttonTemplate }}"/>
|
<t t-call="{{ props.buttonTemplate }}"/>
|
||||||
</t>
|
</t>
|
||||||
<t t-if="model.data">
|
<t t-if="model.data">
|
||||||
<t t-if="model.useSampleModel and props.info.noContentHelp" t-call="web.ActionHelper">
|
<t t-if="model.useSampleModel and props.info.noContentHelp" t-call="web.ActionHelper">
|
||||||
<t t-set="noContentHelp" t-value="props.info.noContentHelp"/>
|
<t t-set="noContentHelp" t-value="props.info.noContentHelp"/>
|
||||||
</t>
|
</t>
|
||||||
<Renderer
|
<t t-component="props.Renderer"
|
||||||
class="model.useSampleModel ? 'o_sample_data_disabled' : ''"
|
class="model.useSampleModel ? 'o_sample_data_disabled' : ''"
|
||||||
model="model"
|
model="model"
|
||||||
onGraphClicked="(domain) => this.onGraphClicked(domain)"
|
onGraphClicked="(domain) => this.onGraphClicked(domain)"
|
||||||
@@ -1,176 +1,58 @@
|
|||||||
/** @odoo-module **/
|
/** @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 { _lt } from "@web/core/l10n/translation";
|
||||||
import { registry } from "@web/core/registry";
|
import { registry } from "@web/core/registry";
|
||||||
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";
|
|
||||||
import { GraphArchParser } from "./graph_arch_parser";
|
import { GraphArchParser } from "./graph_arch_parser";
|
||||||
import { GraphModel } from "./graph_model";
|
import { GraphModel } from "./graph_model";
|
||||||
|
import { GraphController } from "./graph_controller";
|
||||||
import { GraphRenderer } from "./graph_renderer";
|
import { GraphRenderer } from "./graph_renderer";
|
||||||
|
|
||||||
const viewRegistry = registry.category("views");
|
const viewRegistry = registry.category("views");
|
||||||
const { Component } = owl;
|
|
||||||
|
|
||||||
export class GraphView extends Component {
|
export const graphView = {
|
||||||
setup() {
|
type: "graph",
|
||||||
this.actionService = useService("action");
|
display_name: _lt("Graph"),
|
||||||
|
icon: "fa fa-area-chart",
|
||||||
|
multiRecord: true,
|
||||||
|
Controller: GraphController,
|
||||||
|
Renderer: GraphRenderer,
|
||||||
|
Model: GraphModel,
|
||||||
|
ArchParser: GraphArchParser,
|
||||||
|
searchMenuTypes: ["filter", "groupBy", "comparison", "favorite"],
|
||||||
|
buttonTemplate: "web.GraphView.Buttons",
|
||||||
|
|
||||||
|
props: (genericProps, view) => {
|
||||||
let modelParams;
|
let modelParams;
|
||||||
if (this.props.state) {
|
if (genericProps.state) {
|
||||||
modelParams = this.props.state.metaData;
|
modelParams = genericProps.state.metaData;
|
||||||
} else {
|
} else {
|
||||||
const { arch, fields } = this.props;
|
const { arch, fields, resModel, additionalMeasures, displayScaleLabels } = genericProps;
|
||||||
const parser = new this.constructor.ArchParser();
|
const parser = new view.ArchParser();
|
||||||
const archInfo = parser.parse(arch, fields);
|
const archInfo = parser.parse(arch, fields);
|
||||||
modelParams = {
|
modelParams = {
|
||||||
additionalMeasures: this.props.additionalMeasures,
|
additionalMeasures: additionalMeasures,
|
||||||
disableLinking: Boolean(archInfo.disableLinking),
|
disableLinking: Boolean(archInfo.disableLinking),
|
||||||
displayScaleLabels: this.props.displayScaleLabels,
|
displayScaleLabels: displayScaleLabels,
|
||||||
fieldAttrs: archInfo.fieldAttrs,
|
fieldAttrs: archInfo.fieldAttrs,
|
||||||
fields: this.props.fields,
|
fields: fields,
|
||||||
groupBy: archInfo.groupBy,
|
groupBy: archInfo.groupBy,
|
||||||
measure: archInfo.measure || "__count",
|
measure: archInfo.measure || "__count",
|
||||||
mode: archInfo.mode || "bar",
|
mode: archInfo.mode || "bar",
|
||||||
order: archInfo.order || null,
|
order: archInfo.order || null,
|
||||||
resModel: this.props.resModel,
|
resModel: resModel,
|
||||||
stacked: "stacked" in archInfo ? archInfo.stacked : true,
|
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 {
|
return {
|
||||||
graph_measure: measure,
|
...genericProps,
|
||||||
graph_mode: mode,
|
modelParams,
|
||||||
graph_groupbys: groupBy.map((gb) => gb.spec),
|
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 = {
|
viewRegistry.add("graph", graphView);
|
||||||
...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);
|
|
||||||
|
|||||||
@@ -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: [],
|
||||||
|
};
|
||||||
+2
-2
@@ -20,7 +20,7 @@
|
|||||||
<div t-att-class="props.className">
|
<div t-att-class="props.className">
|
||||||
<Layout className="model.useSampleModel ? 'o_view_sample_data' : ''">
|
<Layout className="model.useSampleModel ? 'o_view_sample_data' : ''">
|
||||||
<t t-set-slot="control-panel-bottom-left">
|
<t t-set-slot="control-panel-bottom-left">
|
||||||
<t t-call="{{ constructor.buttonTemplate }}"/>
|
<t t-call="{{ props.buttonTemplate }}"/>
|
||||||
</t>
|
</t>
|
||||||
<t t-set="displayNoContent" t-value="
|
<t t-set="displayNoContent" t-value="
|
||||||
props.info.noContentHelp !== false and (
|
props.info.noContentHelp !== false and (
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<t t-else="" t-call="web.NoContentHelper"/>
|
<t t-else="" t-call="web.NoContentHelper"/>
|
||||||
</t>
|
</t>
|
||||||
<t t-if="model.hasData() and model.metaData.activeMeasures.length">
|
<t t-if="model.hasData() and model.metaData.activeMeasures.length">
|
||||||
<Renderer model="model" onCellClicked="cell => this.onOpenView(cell)"/>
|
<t t-component="props.Renderer" model="model" onCellClicked="cell => this.onOpenView(cell)"/>
|
||||||
</t>
|
</t>
|
||||||
</Layout>
|
</Layout>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,35 +1,36 @@
|
|||||||
/** @odoo-module **/
|
/** @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 { _lt } from "@web/core/l10n/translation";
|
||||||
import { download } from "@web/core/network/download";
|
|
||||||
import { registry } from "@web/core/registry";
|
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 { PivotArchParser } from "@web/views/pivot/pivot_arch_parser";
|
||||||
|
import { PivotController } from "./pivot_controller";
|
||||||
import { PivotModel } from "@web/views/pivot/pivot_model";
|
import { PivotModel } from "@web/views/pivot/pivot_model";
|
||||||
import { PivotRenderer } from "@web/views/pivot/pivot_renderer";
|
import { PivotRenderer } from "@web/views/pivot/pivot_renderer";
|
||||||
|
|
||||||
const viewRegistry = registry.category("views");
|
const viewRegistry = registry.category("views");
|
||||||
const { Component } = owl;
|
|
||||||
|
|
||||||
export class PivotView extends Component {
|
export const pivotView = {
|
||||||
setup() {
|
type: "pivot",
|
||||||
this.actionService = useService("action");
|
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 = {};
|
let modelParams = {};
|
||||||
if (this.props.state) {
|
if (genericProps.state) {
|
||||||
modelParams.data = this.props.state.data;
|
modelParams.data = genericProps.state.data;
|
||||||
modelParams.metaData = this.props.state.metaData;
|
modelParams.metaData = genericProps.state.metaData;
|
||||||
} else {
|
} else {
|
||||||
const { arch } = this.props;
|
const { arch, additionalMeasures, fields, resModel } = genericProps;
|
||||||
|
|
||||||
// parse arch
|
// parse arch
|
||||||
const archInfo = new this.constructor.ArchParser().parse(arch);
|
const archInfo = new view.ArchParser().parse(arch);
|
||||||
|
|
||||||
if (!archInfo.activeMeasures.length || archInfo.displayQuantity) {
|
if (!archInfo.activeMeasures.length || archInfo.displayQuantity) {
|
||||||
archInfo.activeMeasures.unshift("__count");
|
archInfo.activeMeasures.unshift("__count");
|
||||||
@@ -37,154 +38,27 @@ export class PivotView extends Component {
|
|||||||
|
|
||||||
modelParams.metaData = {
|
modelParams.metaData = {
|
||||||
activeMeasures: archInfo.activeMeasures,
|
activeMeasures: archInfo.activeMeasures,
|
||||||
additionalMeasures: this.props.additionalMeasures,
|
additionalMeasures: additionalMeasures,
|
||||||
colGroupBys: archInfo.colGroupBys,
|
colGroupBys: archInfo.colGroupBys,
|
||||||
defaultOrder: archInfo.defaultOrder,
|
defaultOrder: archInfo.defaultOrder,
|
||||||
disableLinking: Boolean(archInfo.disableLinking),
|
disableLinking: Boolean(archInfo.disableLinking),
|
||||||
fields: this.props.fields,
|
fields: fields,
|
||||||
fieldAttrs: archInfo.fieldAttrs,
|
fieldAttrs: archInfo.fieldAttrs,
|
||||||
resModel: this.props.resModel,
|
resModel: resModel,
|
||||||
rowGroupBys: archInfo.rowGroupBys,
|
rowGroupBys: archInfo.rowGroupBys,
|
||||||
title: archInfo.title || this.env._t("Untitled"),
|
title: archInfo.title || _lt("Untitled"),
|
||||||
widgets: archInfo.widgets,
|
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 {
|
return {
|
||||||
pivot_measures: this.model.metaData.activeMeasures,
|
...genericProps,
|
||||||
pivot_column_groupby: this.model.metaData.fullColGroupBys,
|
Model: view.Model,
|
||||||
pivot_row_groupby: this.model.metaData.fullRowGroupBys,
|
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;
|
viewRegistry.add("pivot", pivotView);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|||||||
@@ -162,8 +162,8 @@ export class View extends Component {
|
|||||||
|
|
||||||
async onWillStart() {
|
async onWillStart() {
|
||||||
// determine view type
|
// determine view type
|
||||||
let ViewClass = viewRegistry.get(this.props.type);
|
let descr = viewRegistry.get(this.props.type);
|
||||||
const type = ViewClass.type;
|
const type = descr.type;
|
||||||
|
|
||||||
// determine views for which descriptions should be obtained
|
// determine views for which descriptions should be obtained
|
||||||
let { viewId, searchViewId } = this.props;
|
let { viewId, searchViewId } = this.props;
|
||||||
@@ -249,7 +249,7 @@ export class View extends Component {
|
|||||||
|
|
||||||
// determine ViewClass to instantiate (if not already done)
|
// determine ViewClass to instantiate (if not already done)
|
||||||
if (subType) {
|
if (subType) {
|
||||||
ViewClass = viewRegistry.get(subType);
|
descr = viewRegistry.get(subType);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(this.env.config, {
|
Object.assign(this.env.config, {
|
||||||
@@ -257,7 +257,7 @@ export class View extends Component {
|
|||||||
viewType: type,
|
viewType: type,
|
||||||
viewSubType: subType,
|
viewSubType: subType,
|
||||||
bannerRoute,
|
bannerRoute,
|
||||||
...extractLayoutComponents(ViewClass),
|
...extractLayoutComponents(descr),
|
||||||
});
|
});
|
||||||
|
|
||||||
// prepare the view props
|
// prepare the view props
|
||||||
@@ -291,11 +291,13 @@ export class View extends Component {
|
|||||||
viewProps.info.noContentHelp = noContentHelp;
|
viewProps.info.noContentHelp = noContentHelp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const finalProps = descr.props ? descr.props(viewProps, descr, this.env.config) : viewProps;
|
||||||
// prepare the WithSearch component props
|
// prepare the WithSearch component props
|
||||||
this.withSearchProps = {
|
this.withSearchProps = {
|
||||||
...toRaw(this.props),
|
...toRaw(this.props),
|
||||||
Component: ViewClass,
|
Component: descr.Controller,
|
||||||
componentProps: viewProps,
|
SearchModel: descr.SearchModel,
|
||||||
|
componentProps: finalProps,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (searchViewId !== undefined) {
|
if (searchViewId !== undefined) {
|
||||||
@@ -312,7 +314,7 @@ export class View extends Component {
|
|||||||
if (!this.withSearchProps.searchMenuTypes) {
|
if (!this.withSearchProps.searchMenuTypes) {
|
||||||
this.withSearchProps.searchMenuTypes =
|
this.withSearchProps.searchMenuTypes =
|
||||||
this.props.searchMenuTypes ||
|
this.props.searchMenuTypes ||
|
||||||
ViewClass.searchMenuTypes ||
|
descr.searchMenuTypes ||
|
||||||
this.constructor.searchMenuTypes;
|
this.constructor.searchMenuTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -422,15 +422,18 @@ QUnit.module("DebugMenu", (hooks) => {
|
|||||||
// removing the toy view and using the kanban view directly
|
// removing the toy view and using the kanban view directly
|
||||||
prepareRegistriesWithCleanup();
|
prepareRegistriesWithCleanup();
|
||||||
|
|
||||||
class ToyView extends Component {
|
class ToyController extends Component {
|
||||||
setup() {
|
setup() {
|
||||||
useSetupView();
|
useSetupView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToyView.template = xml`<div class="o-toy-view"/>`;
|
ToyController.template = xml`<div class="o-toy-view"/>`;
|
||||||
ToyView.type = "toy";
|
|
||||||
ToyView.display_name = "toy view";
|
registry.category("views").add("toy", {
|
||||||
registry.category("views").add("toy", ToyView);
|
type: "toy",
|
||||||
|
display_name: "toy view",
|
||||||
|
Controller: ToyController,
|
||||||
|
});
|
||||||
|
|
||||||
const mockRPC = async (route, args) => {
|
const mockRPC = async (route, args) => {
|
||||||
if (args.method === "check_access_rights") {
|
if (args.method === "check_access_rights") {
|
||||||
|
|||||||
@@ -79,18 +79,17 @@ QUnit.module("Views", (hooks) => {
|
|||||||
setupControlPanelServiceRegistry();
|
setupControlPanelServiceRegistry();
|
||||||
serviceRegistry.add("dialog", dialogService);
|
serviceRegistry.add("dialog", dialogService);
|
||||||
|
|
||||||
class ToyView extends LegacyComponent {}
|
class ToyController extends LegacyComponent {}
|
||||||
ToyView.components = { ControlPanel };
|
ToyController.template = xml`<div class="o_toy_view"><ControlPanel /></div>`;
|
||||||
ToyView.display_name = _lt("Toy view");
|
ToyController.components = { ControlPanel};
|
||||||
ToyView.icon = "fab fa-android";
|
|
||||||
ToyView.multiRecord = true;
|
viewRegistry.add("toy", {
|
||||||
ToyView.searchMenuTypes = ["filter", "groupBy", "comparison", "favorite"];
|
type: "toy",
|
||||||
ToyView.template = xml`
|
display_name: _lt("Toy view"),
|
||||||
<div class="o_toy_view">
|
multiRecord: true,
|
||||||
<ControlPanel />
|
searchMenuTypes: ["filter", "groupBy", "comparison", "favorite"],
|
||||||
</div>
|
Controller: ToyController,
|
||||||
`;
|
});
|
||||||
ToyView.type = "toy";
|
|
||||||
|
|
||||||
const LegacyToyView = AbstractView.extend({
|
const LegacyToyView = AbstractView.extend({
|
||||||
display_name: _lt("Legacy toy view"),
|
display_name: _lt("Legacy toy view"),
|
||||||
@@ -100,7 +99,6 @@ QUnit.module("Views", (hooks) => {
|
|||||||
searchMenuTypes: ["filter", "groupBy", "comparison", "favorite"],
|
searchMenuTypes: ["filter", "groupBy", "comparison", "favorite"],
|
||||||
});
|
});
|
||||||
|
|
||||||
viewRegistry.add("toy", ToyView);
|
|
||||||
legacyViewRegistry.add("legacy_toy", LegacyToyView);
|
legacyViewRegistry.add("legacy_toy", LegacyToyView);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ QUnit.module("Search", (hooks) => {
|
|||||||
QUnit.test("delete an active favorite", async function (assert) {
|
QUnit.test("delete an active favorite", async function (assert) {
|
||||||
assert.expect(11);
|
assert.expect(11);
|
||||||
|
|
||||||
class ToyView extends Component {
|
class ToyController extends Component {
|
||||||
setup() {
|
setup() {
|
||||||
assert.deepEqual(this.props.domain, [["foo", "=", "qsdf"]]);
|
assert.deepEqual(this.props.domain, [["foo", "=", "qsdf"]]);
|
||||||
onWillUpdateProps((nextProps) => {
|
onWillUpdateProps((nextProps) => {
|
||||||
@@ -138,17 +138,19 @@ QUnit.module("Search", (hooks) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToyView.components = { FavoriteMenu, SearchBar };
|
ToyController.components = { FavoriteMenu, SearchBar };
|
||||||
ToyView.template = xml`
|
ToyController.template = xml`
|
||||||
<div>
|
<div>
|
||||||
<SearchBar/>
|
<SearchBar/>
|
||||||
<FavoriteMenu/>
|
<FavoriteMenu/>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
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"] = `<toy />`;
|
serverData.views["foo,false,toy"] = `<toy />`;
|
||||||
serverData.models.foo.filters = [
|
serverData.models.foo.filters = [
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ QUnit.module("Views", (hooks) => {
|
|||||||
|
|
||||||
// this is important for export functionality.
|
// this is important for export functionality.
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
pivot.model.metaData.title,
|
pivot.model.metaData.title.toString(),
|
||||||
pivot.env._t("Untitled"),
|
pivot.env._t("Untitled"),
|
||||||
"should have a valid title"
|
"should have a valid title"
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -71,25 +71,29 @@ QUnit.module("Views", (hooks) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
class ToyView extends Component {
|
class ToyController extends Component {
|
||||||
setup() {
|
setup() {
|
||||||
this.class = "toy";
|
this.class = "toy";
|
||||||
this.template = xml`${this.props.arch}`;
|
this.template = xml`${this.props.arch}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToyView.template = xml`<div t-attf-class="{{class}} {{props.className}}"><t t-call="{{ template }}"/></div>`;
|
ToyController.template = xml`<div t-attf-class="{{class}} {{props.className}}"><t t-call="{{ template }}"/></div>`;
|
||||||
ToyView.type = "toy";
|
ToyController.components = { Banner: OnboardingBanner };
|
||||||
ToyView.components = { Banner: OnboardingBanner };
|
|
||||||
|
|
||||||
class ToyViewImp extends ToyView {
|
const toyView = {
|
||||||
|
type: "toy",
|
||||||
|
Controller: ToyController,
|
||||||
|
};
|
||||||
|
|
||||||
|
class ToyControllerImp extends ToyController {
|
||||||
setup() {
|
setup() {
|
||||||
super.setup();
|
super.setup();
|
||||||
this.class = "toy_imp";
|
this.class = "toy_imp";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewRegistry.add("toy", ToyView);
|
viewRegistry.add("toy", toyView);
|
||||||
viewRegistry.add("toy_imp", ToyViewImp);
|
viewRegistry.add("toy_imp", { ...toyView, Controller: ToyControllerImp });
|
||||||
|
|
||||||
setupControlPanelServiceRegistry();
|
setupControlPanelServiceRegistry();
|
||||||
|
|
||||||
@@ -115,8 +119,8 @@ QUnit.module("Views", (hooks) => {
|
|||||||
QUnit.test("simple rendering", async function (assert) {
|
QUnit.test("simple rendering", async function (assert) {
|
||||||
assert.expect(10);
|
assert.expect(10);
|
||||||
|
|
||||||
const ToyView = viewRegistry.get("toy");
|
const ToyController = viewRegistry.get("toy").Controller;
|
||||||
patchWithCleanup(ToyView.prototype, {
|
patchWithCleanup(ToyController.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
this._super();
|
this._super();
|
||||||
const { arch, fields, info } = this.props;
|
const { arch, fields, info } = this.props;
|
||||||
@@ -153,8 +157,8 @@ QUnit.module("Views", (hooks) => {
|
|||||||
QUnit.test("rendering with given viewId", async function (assert) {
|
QUnit.test("rendering with given viewId", async function (assert) {
|
||||||
assert.expect(8);
|
assert.expect(8);
|
||||||
|
|
||||||
const ToyView = viewRegistry.get("toy");
|
const ToyController = viewRegistry.get("toy").Controller;
|
||||||
patchWithCleanup(ToyView.prototype, {
|
patchWithCleanup(ToyController.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
this._super();
|
this._super();
|
||||||
const { arch, fields, info } = this.props;
|
const { arch, fields, info } = this.props;
|
||||||
@@ -190,8 +194,8 @@ QUnit.module("Views", (hooks) => {
|
|||||||
QUnit.test("rendering with given 'views' param", async function (assert) {
|
QUnit.test("rendering with given 'views' param", async function (assert) {
|
||||||
assert.expect(8);
|
assert.expect(8);
|
||||||
|
|
||||||
const ToyView = viewRegistry.get("toy");
|
const ToyController = viewRegistry.get("toy").Controller;
|
||||||
patchWithCleanup(ToyView.prototype, {
|
patchWithCleanup(ToyController.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
this._super();
|
this._super();
|
||||||
const { arch, fields, info } = this.props;
|
const { arch, fields, info } = this.props;
|
||||||
@@ -231,8 +235,8 @@ QUnit.module("Views", (hooks) => {
|
|||||||
async function (assert) {
|
async function (assert) {
|
||||||
assert.expect(8);
|
assert.expect(8);
|
||||||
|
|
||||||
const ToyView = viewRegistry.get("toy");
|
const ToyController = viewRegistry.get("toy").Controller;
|
||||||
patchWithCleanup(ToyView.prototype, {
|
patchWithCleanup(ToyController.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
this._super();
|
this._super();
|
||||||
const { arch, fields, info } = this.props;
|
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) {
|
QUnit.test("viewId defined as prop and in 'views' prop", async function (assert) {
|
||||||
assert.expect(8);
|
assert.expect(8);
|
||||||
|
|
||||||
const ToyView = viewRegistry.get("toy");
|
const ToyController = viewRegistry.get("toy").Controller;
|
||||||
patchWithCleanup(ToyView.prototype, {
|
patchWithCleanup(ToyController.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
this._super();
|
this._super();
|
||||||
const { arch, fields, info } = this.props;
|
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) {
|
QUnit.test("rendering with given arch and fields", async function (assert) {
|
||||||
assert.expect(6);
|
assert.expect(6);
|
||||||
|
|
||||||
const ToyView = viewRegistry.get("toy");
|
const ToyController = viewRegistry.get("toy").Controller;
|
||||||
patchWithCleanup(ToyView.prototype, {
|
patchWithCleanup(ToyController.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
this._super();
|
this._super();
|
||||||
const { arch, fields, info } = this.props;
|
const { arch, fields, info } = this.props;
|
||||||
@@ -353,8 +357,8 @@ QUnit.module("Views", (hooks) => {
|
|||||||
QUnit.test("rendering with loadActionMenus='true'", async function (assert) {
|
QUnit.test("rendering with loadActionMenus='true'", async function (assert) {
|
||||||
assert.expect(8);
|
assert.expect(8);
|
||||||
|
|
||||||
const ToyView = viewRegistry.get("toy");
|
const ToyController = viewRegistry.get("toy").Controller;
|
||||||
patchWithCleanup(ToyView.prototype, {
|
patchWithCleanup(ToyController.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
this._super();
|
this._super();
|
||||||
const { arch, fields, info } = this.props;
|
const { arch, fields, info } = this.props;
|
||||||
@@ -393,8 +397,8 @@ QUnit.module("Views", (hooks) => {
|
|||||||
async function (assert) {
|
async function (assert) {
|
||||||
assert.expect(8);
|
assert.expect(8);
|
||||||
|
|
||||||
const ToyView = viewRegistry.get("toy");
|
const ToyController = viewRegistry.get("toy").Controller;
|
||||||
patchWithCleanup(ToyView.prototype, {
|
patchWithCleanup(ToyController.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
this._super();
|
this._super();
|
||||||
const { arch, fields, info } = this.props;
|
const { arch, fields, info } = this.props;
|
||||||
@@ -436,8 +440,8 @@ QUnit.module("Views", (hooks) => {
|
|||||||
async function (assert) {
|
async function (assert) {
|
||||||
assert.expect(6);
|
assert.expect(6);
|
||||||
|
|
||||||
const ToyView = viewRegistry.get("toy");
|
const ToyController = viewRegistry.get("toy").Controller;
|
||||||
patchWithCleanup(ToyView.prototype, {
|
patchWithCleanup(ToyController.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
this._super();
|
this._super();
|
||||||
const { arch, fields, info } = this.props;
|
const { arch, fields, info } = this.props;
|
||||||
@@ -474,8 +478,8 @@ QUnit.module("Views", (hooks) => {
|
|||||||
QUnit.test("rendering with given searchViewId", async function (assert) {
|
QUnit.test("rendering with given searchViewId", async function (assert) {
|
||||||
assert.expect(8);
|
assert.expect(8);
|
||||||
|
|
||||||
const ToyView = viewRegistry.get("toy");
|
const ToyController = viewRegistry.get("toy").Controller;
|
||||||
patchWithCleanup(ToyView.prototype, {
|
patchWithCleanup(ToyController.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
this._super();
|
this._super();
|
||||||
const {
|
const {
|
||||||
@@ -522,8 +526,8 @@ QUnit.module("Views", (hooks) => {
|
|||||||
async function (assert) {
|
async function (assert) {
|
||||||
assert.expect(6);
|
assert.expect(6);
|
||||||
|
|
||||||
const ToyView = viewRegistry.get("toy");
|
const ToyController = viewRegistry.get("toy").Controller;
|
||||||
patchWithCleanup(ToyView.prototype, {
|
patchWithCleanup(ToyController.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
this._super();
|
this._super();
|
||||||
const {
|
const {
|
||||||
@@ -566,8 +570,8 @@ QUnit.module("Views", (hooks) => {
|
|||||||
async function (assert) {
|
async function (assert) {
|
||||||
assert.expect(6);
|
assert.expect(6);
|
||||||
|
|
||||||
const ToyView = viewRegistry.get("toy");
|
const ToyController = viewRegistry.get("toy").Controller;
|
||||||
patchWithCleanup(ToyView.prototype, {
|
patchWithCleanup(ToyController.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
this._super();
|
this._super();
|
||||||
const {
|
const {
|
||||||
@@ -609,8 +613,8 @@ QUnit.module("Views", (hooks) => {
|
|||||||
async function (assert) {
|
async function (assert) {
|
||||||
assert.expect(8);
|
assert.expect(8);
|
||||||
|
|
||||||
const ToyView = viewRegistry.get("toy");
|
const ToyController = viewRegistry.get("toy").Controller;
|
||||||
patchWithCleanup(ToyView.prototype, {
|
patchWithCleanup(ToyController.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
this._super();
|
this._super();
|
||||||
const {
|
const {
|
||||||
@@ -674,8 +678,8 @@ QUnit.module("Views", (hooks) => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const ToyView = viewRegistry.get("toy");
|
const ToyController = viewRegistry.get("toy").Controller;
|
||||||
patchWithCleanup(ToyView.prototype, {
|
patchWithCleanup(ToyController.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
this._super();
|
this._super();
|
||||||
const {
|
const {
|
||||||
@@ -1018,14 +1022,18 @@ QUnit.module("Views", (hooks) => {
|
|||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
let toy;
|
let toy;
|
||||||
const ToyView = viewRegistry.get("toy");
|
const toyView = viewRegistry.get("toy");
|
||||||
class ToyViewExtended extends ToyView {
|
class ToyViewExtendedController extends toyView.Controller {
|
||||||
setup() {
|
setup() {
|
||||||
super.setup();
|
super.setup();
|
||||||
toy = this;
|
toy = this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewRegistry.add("toy", ToyViewExtended, { force: true });
|
viewRegistry.add(
|
||||||
|
"toy",
|
||||||
|
{ ...toyView, Controller: ToyViewExtendedController },
|
||||||
|
{ force: true }
|
||||||
|
);
|
||||||
|
|
||||||
const mockRPC = (route) => {
|
const mockRPC = (route) => {
|
||||||
if (route === "/mybody/isacage") {
|
if (route === "/mybody/isacage") {
|
||||||
@@ -1407,7 +1415,7 @@ QUnit.module("Views", (hooks) => {
|
|||||||
async function (assert) {
|
async function (assert) {
|
||||||
assert.expect(4);
|
assert.expect(4);
|
||||||
|
|
||||||
class ToyView extends Component {
|
class ToyController extends Component {
|
||||||
setup() {
|
setup() {
|
||||||
const { context, domain, groupBy, orderBy } = this.props;
|
const { context, domain, groupBy, orderBy } = this.props;
|
||||||
assert.deepEqual(context, {
|
assert.deepEqual(context, {
|
||||||
@@ -1421,10 +1429,9 @@ QUnit.module("Views", (hooks) => {
|
|||||||
assert.deepEqual(orderBy, ["bar"]);
|
assert.deepEqual(orderBy, ["bar"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToyView.template = xml`<div/>`;
|
ToyController.template = xml`<div/>`;
|
||||||
ToyView.type = "toy";
|
|
||||||
|
|
||||||
viewRegistry.add("toy", ToyView, { force: true });
|
viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true });
|
||||||
|
|
||||||
const env = await makeTestEnv({ serverData });
|
const env = await makeTestEnv({ serverData });
|
||||||
const props = {
|
const props = {
|
||||||
@@ -1442,14 +1449,13 @@ QUnit.module("Views", (hooks) => {
|
|||||||
QUnit.test("non empty prop 'noContentHelp'", async function (assert) {
|
QUnit.test("non empty prop 'noContentHelp'", async function (assert) {
|
||||||
assert.expect(1);
|
assert.expect(1);
|
||||||
|
|
||||||
class ToyView extends Component {
|
class ToyController extends Component {
|
||||||
setup() {
|
setup() {
|
||||||
assert.strictEqual(this.props.info.noContentHelp, "<div>Help</div>");
|
assert.strictEqual(this.props.info.noContentHelp, "<div>Help</div>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToyView.template = xml`<div/>`;
|
ToyController.template = xml`<div/>`;
|
||||||
ToyView.type = "toy";
|
viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true });
|
||||||
viewRegistry.add("toy", ToyView, { force: true });
|
|
||||||
|
|
||||||
const env = await makeTestEnv({ serverData });
|
const env = await makeTestEnv({ serverData });
|
||||||
const props = {
|
const props = {
|
||||||
@@ -1463,14 +1469,13 @@ QUnit.module("Views", (hooks) => {
|
|||||||
QUnit.test("useSampleModel false by default", async function (assert) {
|
QUnit.test("useSampleModel false by default", async function (assert) {
|
||||||
assert.expect(1);
|
assert.expect(1);
|
||||||
|
|
||||||
class ToyView extends Component {
|
class ToyController extends Component {
|
||||||
setup() {
|
setup() {
|
||||||
assert.strictEqual(this.props.useSampleModel, false);
|
assert.strictEqual(this.props.useSampleModel, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToyView.template = xml`<div/>`;
|
ToyController.template = xml`<div/>`;
|
||||||
ToyView.type = "toy";
|
viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true });
|
||||||
viewRegistry.add("toy", ToyView, { force: true });
|
|
||||||
|
|
||||||
const env = await makeTestEnv({ serverData });
|
const env = await makeTestEnv({ serverData });
|
||||||
const props = { resModel: "animal", type: "toy" };
|
const props = { resModel: "animal", type: "toy" };
|
||||||
@@ -1480,14 +1485,13 @@ QUnit.module("Views", (hooks) => {
|
|||||||
QUnit.test("sample='1' on arch", async function (assert) {
|
QUnit.test("sample='1' on arch", async function (assert) {
|
||||||
assert.expect(1);
|
assert.expect(1);
|
||||||
|
|
||||||
class ToyView extends Component {
|
class ToyController extends Component {
|
||||||
setup() {
|
setup() {
|
||||||
assert.strictEqual(this.props.useSampleModel, true);
|
assert.strictEqual(this.props.useSampleModel, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToyView.template = xml`<div/>`;
|
ToyController.template = xml`<div/>`;
|
||||||
ToyView.type = "toy";
|
viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true });
|
||||||
viewRegistry.add("toy", ToyView, { force: true });
|
|
||||||
|
|
||||||
const env = await makeTestEnv({ serverData });
|
const env = await makeTestEnv({ serverData });
|
||||||
const props = {
|
const props = {
|
||||||
@@ -1502,14 +1506,13 @@ QUnit.module("Views", (hooks) => {
|
|||||||
QUnit.test("sample='0' on arch and useSampleModel=true", async function (assert) {
|
QUnit.test("sample='0' on arch and useSampleModel=true", async function (assert) {
|
||||||
assert.expect(1);
|
assert.expect(1);
|
||||||
|
|
||||||
class ToyView extends Component {
|
class ToyController extends Component {
|
||||||
setup() {
|
setup() {
|
||||||
assert.strictEqual(this.props.useSampleModel, true);
|
assert.strictEqual(this.props.useSampleModel, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToyView.template = xml`<div/>`;
|
ToyController.template = xml`<div/>`;
|
||||||
ToyView.type = "toy";
|
viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true });
|
||||||
viewRegistry.add("toy", ToyView, { force: true });
|
|
||||||
|
|
||||||
const env = await makeTestEnv({ serverData });
|
const env = await makeTestEnv({ serverData });
|
||||||
const props = {
|
const props = {
|
||||||
@@ -1525,14 +1528,13 @@ QUnit.module("Views", (hooks) => {
|
|||||||
QUnit.test("sample='1' on arch and useSampleModel=false", async function (assert) {
|
QUnit.test("sample='1' on arch and useSampleModel=false", async function (assert) {
|
||||||
assert.expect(1);
|
assert.expect(1);
|
||||||
|
|
||||||
class ToyView extends Component {
|
class ToyController extends Component {
|
||||||
setup() {
|
setup() {
|
||||||
assert.strictEqual(this.props.useSampleModel, false);
|
assert.strictEqual(this.props.useSampleModel, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToyView.template = xml`<div/>`;
|
ToyController.template = xml`<div/>`;
|
||||||
ToyView.type = "toy";
|
viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true });
|
||||||
viewRegistry.add("toy", ToyView, { force: true });
|
|
||||||
|
|
||||||
const env = await makeTestEnv({ serverData });
|
const env = await makeTestEnv({ serverData });
|
||||||
const props = {
|
const props = {
|
||||||
@@ -1548,14 +1550,13 @@ QUnit.module("Views", (hooks) => {
|
|||||||
QUnit.test("useSampleModel=true", async function (assert) {
|
QUnit.test("useSampleModel=true", async function (assert) {
|
||||||
assert.expect(1);
|
assert.expect(1);
|
||||||
|
|
||||||
class ToyView extends Component {
|
class ToyController extends Component {
|
||||||
setup() {
|
setup() {
|
||||||
assert.strictEqual(this.props.useSampleModel, true);
|
assert.strictEqual(this.props.useSampleModel, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToyView.template = xml`<div/>`;
|
ToyController.template = xml`<div/>`;
|
||||||
ToyView.type = "toy";
|
viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true });
|
||||||
viewRegistry.add("toy", ToyView, { force: true });
|
|
||||||
|
|
||||||
const env = await makeTestEnv({ serverData });
|
const env = await makeTestEnv({ serverData });
|
||||||
const props = { resModel: "animal", type: "toy", useSampleModel: true };
|
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) {
|
QUnit.test("rendering with given prop", async function (assert) {
|
||||||
assert.expect(1);
|
assert.expect(1);
|
||||||
|
|
||||||
class ToyView extends Component {
|
class ToyController extends Component {
|
||||||
setup() {
|
setup() {
|
||||||
assert.strictEqual(this.props.specificProp, "specificProp");
|
assert.strictEqual(this.props.specificProp, "specificProp");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToyView.template = xml`<div/>`;
|
ToyController.template = xml`<div/>`;
|
||||||
ToyView.type = "toy";
|
viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true });
|
||||||
viewRegistry.add("toy", ToyView, { force: true });
|
|
||||||
|
|
||||||
const env = await makeTestEnv({ serverData });
|
const env = await makeTestEnv({ serverData });
|
||||||
const props = { resModel: "animal", type: "toy", specificProp: "specificProp" };
|
const props = { resModel: "animal", type: "toy", specificProp: "specificProp" };
|
||||||
@@ -1584,7 +1584,7 @@ QUnit.module("Views", (hooks) => {
|
|||||||
async function (assert) {
|
async function (assert) {
|
||||||
assert.expect(4);
|
assert.expect(4);
|
||||||
|
|
||||||
class ToyView extends Component {
|
class ToyController extends Component {
|
||||||
setup() {
|
setup() {
|
||||||
const { context, domain, groupBy, orderBy } = this.props;
|
const { context, domain, groupBy, orderBy } = this.props;
|
||||||
assert.deepEqual(context, {
|
assert.deepEqual(context, {
|
||||||
@@ -1597,9 +1597,8 @@ QUnit.module("Views", (hooks) => {
|
|||||||
assert.deepEqual(orderBy, ["bar"]);
|
assert.deepEqual(orderBy, ["bar"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToyView.template = xml`<div/>`;
|
ToyController.template = xml`<div/>`;
|
||||||
ToyView.type = "toy";
|
viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true });
|
||||||
viewRegistry.add("toy", ToyView, { force: true });
|
|
||||||
|
|
||||||
const env = await makeTestEnv({ serverData });
|
const env = await makeTestEnv({ serverData });
|
||||||
const props = {
|
const props = {
|
||||||
@@ -1622,7 +1621,7 @@ QUnit.module("Views", (hooks) => {
|
|||||||
QUnit.test("react to prop 'domain' changes", async function (assert) {
|
QUnit.test("react to prop 'domain' changes", async function (assert) {
|
||||||
assert.expect(2);
|
assert.expect(2);
|
||||||
|
|
||||||
class ToyView extends Component {
|
class ToyController extends Component {
|
||||||
setup() {
|
setup() {
|
||||||
onWillStart(() => {
|
onWillStart(() => {
|
||||||
assert.deepEqual(this.props.domain, [["type", "=", "carnivorous"]]);
|
assert.deepEqual(this.props.domain, [["type", "=", "carnivorous"]]);
|
||||||
@@ -1632,9 +1631,8 @@ QUnit.module("Views", (hooks) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToyView.template = xml`<div/>`;
|
ToyController.template = xml`<div/>`;
|
||||||
ToyView.type = "toy";
|
viewRegistry.add("toy", { type: "toy", Controller: ToyController }, { force: true });
|
||||||
viewRegistry.add("toy", ToyView, { force: true });
|
|
||||||
|
|
||||||
const env = await makeTestEnv({ serverData });
|
const env = await makeTestEnv({ serverData });
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { createWebClient, doAction, getActionManagerServerData } from "./../help
|
|||||||
import FormController from "web.FormController";
|
import FormController from "web.FormController";
|
||||||
import { makeFakeUserService } from "@web/../tests/helpers/mock_services";
|
import { makeFakeUserService } from "@web/../tests/helpers/mock_services";
|
||||||
import ListController from "web.ListController";
|
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";
|
import { registry } from "@web/core/registry";
|
||||||
|
|
||||||
const serviceRegistry = registry.category("services");
|
const serviceRegistry = registry.category("services");
|
||||||
@@ -194,7 +194,7 @@ QUnit.module("ActionManager", (hooks) => {
|
|||||||
async function (assert) {
|
async function (assert) {
|
||||||
assert.expect(7);
|
assert.expect(7);
|
||||||
let pivot;
|
let pivot;
|
||||||
patchWithCleanup(PivotView.prototype, {
|
patchWithCleanup(pivotView.Controller.prototype, {
|
||||||
setup() {
|
setup() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
pivot = this;
|
pivot = this;
|
||||||
|
|||||||
@@ -669,7 +669,7 @@ QUnit.module("ActionManager", (hooks) => {
|
|||||||
let def = Promise.resolve();
|
let def = Promise.resolve();
|
||||||
|
|
||||||
let id = 1;
|
let id = 1;
|
||||||
class ToyView extends Component {
|
class ToyController extends Component {
|
||||||
setup() {
|
setup() {
|
||||||
this.id = id++;
|
this.id = id++;
|
||||||
assert.step(JSON.stringify(this.props.state || "no state"));
|
assert.step(JSON.stringify(this.props.state || "no state"));
|
||||||
@@ -681,18 +681,20 @@ QUnit.module("ActionManager", (hooks) => {
|
|||||||
owl.onWillStart(() => def);
|
owl.onWillStart(() => def);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToyView.components = { ControlPanel };
|
ToyController.template = xml`
|
||||||
ToyView.display_name = "Toy";
|
|
||||||
ToyView.icon = "fab fa-android";
|
|
||||||
ToyView.multiRecord = true;
|
|
||||||
ToyView.searchMenuTypes = ["filter"];
|
|
||||||
ToyView.template = xml`
|
|
||||||
<div class="o_toy_view">
|
<div class="o_toy_view">
|
||||||
<ControlPanel />
|
<ControlPanel />
|
||||||
</div>
|
</div>`;
|
||||||
`;
|
ToyController.components = { ControlPanel };
|
||||||
ToyView.type = "toy";
|
|
||||||
registry.category("views").add("toy", ToyView);
|
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 });
|
const webClient = await createWebClient({ serverData });
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user