summaryrefslogtreecommitdiff
path: root/addons/web
diff options
context:
space:
mode:
authorJulien Mougenot <jum@odoo.com>2020-07-13 13:23:36 +0000
committerMathieu Duckerts-Antoine <dam@odoo.com>2020-07-23 08:21:10 +0000
commitbb3bc7c9e4992dc07b10e87f87662bdd72f24d88 (patch)
tree073d814a44b00f13d7e8d5c9d939f169d54bbb08 /addons/web
parent3162141e4d71e9c55aff5241c5578571b68c332d (diff)
[IMP] *: adapt modules to Owl search panel
Adaptation of the rest of the environment to the changes regarding the refactored search panel and model. This commit includes the following changes: - Since the control panel model is now part of the search model, the view widget is responsible of its instantiation with the appropriate API; creating an ActionModel holding the required model extensions. - The controller is now importing/exporting the state of the entire search model as well as the current state of the search panel (given throught its props). Changes have been made to adapt to this behaviour. - The multiple components linked to the control panel model have been adapted to reflect the changes brought to it (they will now listen to the "searchModel" instead of the "controlPanelModel"). - The `test_utils_create` methods have also been adapted to properly instantiate a searchModel Part of task 2228968 Co-authored-by: Julien Mougenot <jum@odoo.com> Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
Diffstat (limited to 'addons/web')
-rw-r--r--addons/web/static/src/js/chrome/abstract_action.js61
-rw-r--r--addons/web/static/src/js/chrome/action_manager_act_window.js3
-rw-r--r--addons/web/static/src/js/control_panel/comparison_menu.js6
-rw-r--r--addons/web/static/src/js/control_panel/control_panel.js16
-rw-r--r--addons/web/static/src/js/control_panel/custom_favorite_item.js6
-rw-r--r--addons/web/static/src/js/control_panel/custom_filter_item.js4
-rw-r--r--addons/web/static/src/js/control_panel/custom_group_by_item.js4
-rw-r--r--addons/web/static/src/js/control_panel/favorite_menu.js8
-rw-r--r--addons/web/static/src/js/control_panel/filter_menu.js6
-rw-r--r--addons/web/static/src/js/control_panel/groupby_menu.js6
-rw-r--r--addons/web/static/src/js/control_panel/search_bar.js19
-rw-r--r--addons/web/static/src/js/views/abstract_controller.js164
-rw-r--r--addons/web/static/src/js/views/abstract_view.js207
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_controller.js2
-rw-r--r--addons/web/static/src/xml/base.xml8
-rw-r--r--addons/web/views/webclient_templates.xml2
16 files changed, 266 insertions, 256 deletions
diff --git a/addons/web/static/src/js/chrome/abstract_action.js b/addons/web/static/src/js/chrome/abstract_action.js
index 982b750ab56..fc5c1fd4873 100644
--- a/addons/web/static/src/js/chrome/abstract_action.js
+++ b/addons/web/static/src/js/chrome/abstract_action.js
@@ -9,8 +9,8 @@ odoo.define('web.AbstractAction', function (require) {
*/
var ActionMixin = require('web.ActionMixin');
+const ActionModel = require('web/static/src/js/views/action_model.js');
var ControlPanel = require('web.ControlPanel');
-var ControlPanelModel = require('web.ControlPanelModel');
var Widget = require('web.Widget');
const { ComponentWrapper } = require('web.OwlCompatibility');
@@ -35,7 +35,7 @@ var AbstractAction = Widget.extend(ActionMixin, {
* For example, the Discuss application adds the following line in its
* constructor::
*
- * this.controlPanelModelConfig.modelName = 'mail.message';
+ * this.searchModelConfig.modelName = 'mail.message';
*
* @type boolean
*/
@@ -76,12 +76,16 @@ var AbstractAction = Widget.extend(ActionMixin, {
this._super(parent);
this._title = action.display_name || action.name;
+ this.searchModelConfig = {
+ context: Object.assign({}, action.context),
+ domain: action.domain || [],
+ env: owl.Component.env,
+ searchMenuTypes: this.searchMenuTypes,
+ };
+ this.extensions = {};
if (this.hasControlPanel) {
- this.controlPanelModelConfig = {
+ this.extensions.ControlPanel = {
actionId: action.id,
- actionContext: action.context || {},
- actionDomain: action.domain || [],
- env: owl.Component.env,
withSearchBar: this.withSearchBar,
};
@@ -102,23 +106,36 @@ var AbstractAction = Widget.extend(ActionMixin, {
* @override
*/
willStart: async function () {
- const proms = [this._super(...arguments)];
+ const superPromise = this._super(...arguments);
if (this.hasControlPanel) {
if (this.loadControlPanel) {
- const { context, searchMenuTypes } = this.controlPanelProps;
- const { modelName } = this.controlPanelModelConfig;
- const options = { load_filters: searchMenuTypes.includes('favorite') };
- const args = [modelName, context || {}, this.viewId, 'search', options];
- const loadFieldViewPromise = this.loadFieldView(...args);
- const {arch, fields, favoriteFilters } = await loadFieldViewPromise;
- this.controlPanelModelConfig.viewInfo = {arch, fields, favoriteFilters };
+ const { context, modelName } = this.searchModelConfig;
+ const options = { load_filters: this.searchMenuTypes.includes('favorite') };
+ const { arch, fields, favoriteFilters } = await this.loadFieldView(
+ modelName,
+ context || {},
+ this.viewId,
+ 'search',
+ options
+ );
+ const archs = { search: arch };
+ const { ControlPanel: controlPanelInfo } = ActionModel.extractArchInfo(archs);
+ Object.assign(this.extensions.ControlPanel, {
+ archNodes: controlPanelInfo.children,
+ favoriteFilters,
+ fields,
+ });
this.controlPanelProps.fields = fields;
}
- this._controlPanelModel = new ControlPanelModel(this.controlPanelModelConfig);
- this.controlPanelProps.controlPanelModel = this._controlPanelModel;
- proms.push(this._controlPanelModel.isReady);
}
- return Promise.all(proms);
+ this.searchModel = new ActionModel(this.extensions, this.searchModelConfig);
+ if (this.hasControlPanel) {
+ this.controlPanelProps.searchModel = this.searchModel;
+ }
+ return Promise.all([
+ superPromise,
+ this.searchModel.load(),
+ ]);
},
/**
* @override
@@ -147,9 +164,9 @@ var AbstractAction = Widget.extend(ActionMixin, {
*/
on_attach_callback: function () {
ActionMixin.on_attach_callback.call(this);
+ this.searchModel.on('search', this, this._onSearch);
if (this.hasControlPanel) {
- this._controlPanelModel.on('search', this, this._onSearch);
- this._controlPanelModel.on('get-controller-query-params', this, this._onGetOwnedQueryParams);
+ this.searchModel.on('get-controller-query-params', this, this._onGetOwnedQueryParams);
}
},
/**
@@ -157,9 +174,9 @@ var AbstractAction = Widget.extend(ActionMixin, {
*/
on_detach_callback: function () {
ActionMixin.on_detach_callback.call(this);
+ this.searchModel.off('search', this);
if (this.hasControlPanel) {
- this._controlPanelModel.off('search', this);
- this._controlPanelModel.off('get-controller-query-params', this);
+ this.searchModel.off('get-controller-query-params', this);
}
},
diff --git a/addons/web/static/src/js/chrome/action_manager_act_window.js b/addons/web/static/src/js/chrome/action_manager_act_window.js
index f6c5661eb8e..749feacb3d6 100644
--- a/addons/web/static/src/js/chrome/action_manager_act_window.js
+++ b/addons/web/static/src/js/chrome/action_manager_act_window.js
@@ -488,6 +488,7 @@ ActionManager.include({
// can't switch to an unknown view
return Promise.reject();
}
+
var currentController = this.getCurrentController();
var index;
if (currentController.actionID !== action.jsID) {
@@ -546,7 +547,7 @@ ActionManager.include({
return Promise.resolve(controller.widget.willRestore()).then(function () {
viewOptions = _.extend({}, viewOptions, {
breadcrumbs: self._getBreadcrumbs(self.controllerStack.slice(0, index)),
- shouldUpdateControlPanel: true,
+ shouldUpdateSearchComponents: true,
});
return controller.widget.reload(viewOptions).then(function () {
return controller;
diff --git a/addons/web/static/src/js/control_panel/comparison_menu.js b/addons/web/static/src/js/control_panel/comparison_menu.js
index 4504370e7c4..dee1c982d2e 100644
--- a/addons/web/static/src/js/control_panel/comparison_menu.js
+++ b/addons/web/static/src/js/control_panel/comparison_menu.js
@@ -3,7 +3,7 @@ odoo.define("web.ComparisonMenu", function (require) {
const DropdownMenu = require("web.DropdownMenu");
const { FACET_ICONS } = require("web.searchUtils");
- const { useModel } = require("web.model");
+ const { useModel } = require("web/static/src/js/model.js");
/**
* "Comparison" menu
@@ -15,7 +15,7 @@ odoo.define("web.ComparisonMenu", function (require) {
class ComparisonMenu extends DropdownMenu {
constructor() {
super(...arguments);
- this.model = useModel("controlPanelModel");
+ this.model = useModel('searchModel');
}
//---------------------------------------------------------------------
@@ -33,7 +33,7 @@ odoo.define("web.ComparisonMenu", function (require) {
* @override
*/
get items() {
- return this.model.getFiltersOfType("comparison");
+ return this.model.get('filters', f => f.type === 'comparison');
}
/**
diff --git a/addons/web/static/src/js/control_panel/control_panel.js b/addons/web/static/src/js/control_panel/control_panel.js
index b315893076a..aca28bc0eb2 100644
--- a/addons/web/static/src/js/control_panel/control_panel.js
+++ b/addons/web/static/src/js/control_panel/control_panel.js
@@ -2,15 +2,15 @@ odoo.define('web.ControlPanel', function (require) {
"use strict";
const ActionMenus = require('web.ActionMenus');
- const ControlPanelModel = require('web.ControlPanelModel');
const ComparisonMenu = require('web.ComparisonMenu');
+ const ActionModel = require('web/static/src/js/views/action_model.js');
const FavoriteMenu = require('web.FavoriteMenu');
const FilterMenu = require('web.FilterMenu');
const GroupByMenu = require('web.GroupByMenu');
const patchMixin = require('web.patchMixin');
const Pager = require('web.Pager');
const SearchBar = require('web.SearchBar');
- const { useModel } = require('web.model');
+ const { useModel } = require('web/static/src/js/model.js');
const { Component, hooks } = owl;
const { useRef, useSubEnv } = hooks;
@@ -105,14 +105,14 @@ odoo.define('web.ControlPanel', function (require) {
useSubEnv({
action: this.props.action,
- controlPanelModel: this.props.controlPanelModel,
+ searchModel: this.props.searchModel,
view: this.props.view,
});
// Connect to the model
// TODO: move this in enterprise whenever possible
- if (this.env.controlPanelModel) {
- this.model = useModel('controlPanelModel');
+ if (this.env.searchModel) {
+ this.model = useModel('searchModel');
}
// Reference hooks
@@ -135,7 +135,7 @@ odoo.define('web.ControlPanel', function (require) {
}
async willUpdateProps(nextProps) {
- // Note: action and controlPanelModel are not likely to change during
+ // Note: action and searchModel are not likely to change during
// the lifespan of a ControlPanel instance, so we only need to update
// the view information.
if ('view' in nextProps) {
@@ -185,6 +185,7 @@ odoo.define('web.ControlPanel', function (require) {
return formattedFields;
}
}
+ ControlPanel.modelExtension = "ControlPanel";
ControlPanel.components = {
SearchBar,
@@ -202,7 +203,7 @@ odoo.define('web.ControlPanel', function (require) {
ControlPanel.props = {
action: Object,
breadcrumbs: Array,
- controlPanelModel: ControlPanelModel,
+ searchModel: ActionModel,
cp_content: { type: Object, optional: 1 },
fields: Object,
pager: { validate: p => typeof p === 'object' || p === null, optional: 1 },
@@ -214,7 +215,6 @@ odoo.define('web.ControlPanel', function (require) {
withBreadcrumbs: Boolean,
withSearchBar: Boolean,
};
-
ControlPanel.template = 'web.ControlPanel';
return patchMixin(ControlPanel);
diff --git a/addons/web/static/src/js/control_panel/custom_favorite_item.js b/addons/web/static/src/js/control_panel/custom_favorite_item.js
index b7b3a34ef81..b7adb031467 100644
--- a/addons/web/static/src/js/control_panel/custom_favorite_item.js
+++ b/addons/web/static/src/js/control_panel/custom_favorite_item.js
@@ -4,7 +4,7 @@ odoo.define('web.CustomFavoriteItem', function (require) {
const DropdownMenuItem = require('web.DropdownMenuItem');
const FavoriteMenu = require('web.FavoriteMenu');
const { useAutofocus } = require('web.custom_hooks');
- const { useModel } = require('web.model');
+ const { useModel } = require('web/static/src/js/model.js');
const { useRef } = owl.hooks;
@@ -39,7 +39,7 @@ odoo.define('web.CustomFavoriteItem', function (require) {
this.shareAllUsersId = `o_favorite_share_all_users_${favId}`;
this.descriptionRef = useRef('description');
- this.model = useModel('controlPanelModel');
+ this.model = useModel('searchModel');
this.interactive = true;
Object.assign(this.state, {
description: this.env.action.name || "",
@@ -65,7 +65,7 @@ odoo.define('web.CustomFavoriteItem', function (require) {
});
return this.descriptionRef.el.focus();
}
- const favorites = this.model.getFiltersOfType('favorite');
+ const favorites = this.model.get('filters', f => f.type === 'favorite');
if (favorites.some(f => f.description === this.state.description)) {
this.env.services.notification.notify({
message: this.env._t("Filter with same name already exists."),
diff --git a/addons/web/static/src/js/control_panel/custom_filter_item.js b/addons/web/static/src/js/control_panel/custom_filter_item.js
index 592a948d135..98080f6a418 100644
--- a/addons/web/static/src/js/control_panel/custom_filter_item.js
+++ b/addons/web/static/src/js/control_panel/custom_filter_item.js
@@ -7,7 +7,7 @@ odoo.define('web.CustomFilterItem', function (require) {
const { FIELD_OPERATORS, FIELD_TYPES } = require('web.searchUtils');
const field_utils = require('web.field_utils');
const patchMixin = require('web.patchMixin');
- const { useModel } = require('web.model');
+ const { useModel } = require('web/static/src/js/model.js');
/**
* Filter generator menu
@@ -46,7 +46,7 @@ odoo.define('web.CustomFilterItem', function (require) {
constructor() {
super(...arguments);
- this.model = useModel('controlPanelModel');
+ this.model = useModel('searchModel');
this.canBeOpened = true;
this.state.conditions = [];
diff --git a/addons/web/static/src/js/control_panel/custom_group_by_item.js b/addons/web/static/src/js/control_panel/custom_group_by_item.js
index 8082608d3ff..20a1c3c7eb7 100644
--- a/addons/web/static/src/js/control_panel/custom_group_by_item.js
+++ b/addons/web/static/src/js/control_panel/custom_group_by_item.js
@@ -2,7 +2,7 @@ odoo.define('web.CustomGroupByItem', function (require) {
"use strict";
const DropdownMenuItem = require('web.DropdownMenuItem');
- const { useModel } = require('web.model');
+ const { useModel } = require('web/static/src/js/model.js');
/**
* Group by generator menu
@@ -20,7 +20,7 @@ odoo.define('web.CustomGroupByItem', function (require) {
this.canBeOpened = true;
this.state.fieldName = this.props.fields[0].name;
- this.model = useModel('controlPanelModel');
+ this.model = useModel('searchModel');
}
//---------------------------------------------------------------------
diff --git a/addons/web/static/src/js/control_panel/favorite_menu.js b/addons/web/static/src/js/control_panel/favorite_menu.js
index 066b30f30d5..ff09109fe59 100644
--- a/addons/web/static/src/js/control_panel/favorite_menu.js
+++ b/addons/web/static/src/js/control_panel/favorite_menu.js
@@ -5,7 +5,7 @@ odoo.define('web.FavoriteMenu', function (require) {
const DropdownMenu = require('web.DropdownMenu');
const { FACET_ICONS } = require("web.searchUtils");
const Registry = require('web.Registry');
- const { useModel } = require('web.model');
+ const { useModel } = require('web/static/src/js/model.js');
/**
* 'Favorites' menu
@@ -22,7 +22,7 @@ odoo.define('web.FavoriteMenu', function (require) {
constructor() {
super(...arguments);
- this.model = useModel('controlPanelModel');
+ this.model = useModel('searchModel');
this.state.deletedFavorite = false;
}
@@ -41,7 +41,7 @@ odoo.define('web.FavoriteMenu', function (require) {
* @override
*/
get items() {
- const favorites = this.model.getFiltersOfType('favorite');
+ const favorites = this.model.get('filters', f => f.type === 'favorite');
const registryMenus = this.constructor.registry.values().reduce(
(menus, Component) => {
if (Component.shouldBeDisplayed(this.env)) {
@@ -91,7 +91,7 @@ odoo.define('web.FavoriteMenu', function (require) {
* @private
*/
async _onRemoveFavorite() {
- await this.model.dispatch('deleteFavorite', this.state.deletedFavorite.id);
+ this.model.dispatch('deleteFavorite', this.state.deletedFavorite.id);
this.state.deletedFavorite = false;
}
}
diff --git a/addons/web/static/src/js/control_panel/filter_menu.js b/addons/web/static/src/js/control_panel/filter_menu.js
index 2341def75b0..da37eb2359b 100644
--- a/addons/web/static/src/js/control_panel/filter_menu.js
+++ b/addons/web/static/src/js/control_panel/filter_menu.js
@@ -4,7 +4,7 @@ odoo.define('web.FilterMenu', function (require) {
const CustomFilterItem = require('web.CustomFilterItem');
const DropdownMenu = require('web.DropdownMenu');
const { FACET_ICONS } = require("web.searchUtils");
- const { useModel } = require('web.model');
+ const { useModel } = require('web/static/src/js/model.js');
/**
* 'Filters' menu
@@ -20,7 +20,7 @@ odoo.define('web.FilterMenu', function (require) {
constructor() {
super(...arguments);
- this.model = useModel('controlPanelModel');
+ this.model = useModel('searchModel');
}
//---------------------------------------------------------------------
@@ -38,7 +38,7 @@ odoo.define('web.FilterMenu', function (require) {
* @override
*/
get items() {
- return this.model.getFiltersOfType('filter');
+ return this.model.get('filters', f => f.type === 'filter');
}
/**
diff --git a/addons/web/static/src/js/control_panel/groupby_menu.js b/addons/web/static/src/js/control_panel/groupby_menu.js
index 97bad1a2ce7..546952ad50d 100644
--- a/addons/web/static/src/js/control_panel/groupby_menu.js
+++ b/addons/web/static/src/js/control_panel/groupby_menu.js
@@ -4,7 +4,7 @@ odoo.define('web.GroupByMenu', function (require) {
const CustomGroupByItem = require('web.CustomGroupByItem');
const DropdownMenu = require('web.DropdownMenu');
const { FACET_ICONS, GROUPABLE_TYPES } = require('web.searchUtils');
- const { useModel } = require('web.model');
+ const { useModel } = require('web/static/src/js/model.js');
/**
* 'Group by' menu
@@ -24,7 +24,7 @@ odoo.define('web.GroupByMenu', function (require) {
.filter(field => this._validateField(field))
.sort(({ string: a }, { string: b }) => a > b ? 1 : a < b ? -1 : 0);
- this.model = useModel('controlPanelModel');
+ this.model = useModel('searchModel');
}
//---------------------------------------------------------------------
@@ -42,7 +42,7 @@ odoo.define('web.GroupByMenu', function (require) {
* @override
*/
get items() {
- return this.model.getFiltersOfType('groupBy');
+ return this.model.get('filters', f => f.type === 'groupBy');
}
/**
diff --git a/addons/web/static/src/js/control_panel/search_bar.js b/addons/web/static/src/js/control_panel/search_bar.js
index 3082c38c055..22f6ee6a60b 100644
--- a/addons/web/static/src/js/control_panel/search_bar.js
+++ b/addons/web/static/src/js/control_panel/search_bar.js
@@ -4,7 +4,7 @@ odoo.define('web.SearchBar', function (require) {
const Domain = require('web.Domain');
const field_utils = require('web.field_utils');
const { useAutofocus } = require('web.custom_hooks');
- const { useModel } = require('web.model');
+ const { useModel } = require('web/static/src/js/model.js');
const CHAR_FIELDS = ['char', 'html', 'many2many', 'many2one', 'one2many', 'text'];
const { Component, hooks } = owl;
@@ -42,14 +42,14 @@ odoo.define('web.SearchBar', function (require) {
this.focusOnUpdate = useAutofocus();
this.inputRef = useRef('search-input');
- this.model = useModel('controlPanelModel');
+ this.model = useModel('searchModel');
this.state = useState({
sources: [],
focusedItem: 0,
inputValue: "",
});
- this.autoCompleteSources = this.model.getFiltersOfType('field').map(
+ this.autoCompleteSources = this.model.get('filters', f => f.type === 'field').map(
filter => this._createSource(filter)
);
this.noResultItem = [null, this.env._t("(no result)")];
@@ -60,15 +60,15 @@ odoo.define('web.SearchBar', function (require) {
mounted() {
// 'search' will always patch the search bar, 'focus' will never.
- this.env.controlPanelModel.on('search', this, this.focusOnUpdate);
- this.env.controlPanelModel.on('focus-control-panel', this, () => {
+ this.env.searchModel.on('search', this, this.focusOnUpdate);
+ this.env.searchModel.on('focus-control-panel', this, () => {
this.inputRef.el.focus();
});
}
willUnmount() {
- this.env.controlPanelModel.off('search', this);
- this.env.controlPanelModel.off('focus-control-panel', this);
+ this.env.searchModel.off('search', this);
+ this.env.searchModel.off('focus-control-panel', this);
}
//---------------------------------------------------------------------
@@ -376,9 +376,8 @@ odoo.define('web.SearchBar', function (require) {
this.state.focusedItem = this.state.sources.indexOf(currentItem.parent);
// Priority 3: Do nothing (navigation inside text).
} else if (ev.target.selectionStart === 0) {
- const facets = this.model.getFacets();
// Priority 4: navigate to rightmost facet.
- this._focusFacet(facets.length - 1);
+ this._focusFacet(this.model.get("facets").length - 1);
}
break;
case 'ArrowRight':
@@ -408,7 +407,7 @@ odoo.define('web.SearchBar', function (require) {
break;
case 'Backspace':
if (!this.state.inputValue.length) {
- const facets = this.model.getFacets();
+ const facets = this.model.get("facets");
if (facets.length) {
this._onFacetRemove(facets[facets.length - 1]);
}
diff --git a/addons/web/static/src/js/views/abstract_controller.js b/addons/web/static/src/js/views/abstract_controller.js
index 8a48c201fa6..5128fa6d9a8 100644
--- a/addons/web/static/src/js/views/abstract_controller.js
+++ b/addons/web/static/src/js/views/abstract_controller.js
@@ -16,7 +16,6 @@ var ActionMixin = require('web.ActionMixin');
var ajax = require('web.ajax');
var concurrency = require('web.concurrency');
const { ComponentWrapper } = require('web.OwlCompatibility');
-const ControlPanel = require('web.ControlPanel');
var mvc = require('web.mvc');
var session = require('web.session');
@@ -25,7 +24,6 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
custom_events: _.extend({}, ActionMixin.custom_events, {
navigation_move: '_onNavigationMove',
open_record: '_onOpenRecord',
- search_panel_domain_updated: '_onSearchPanelDomainUpdated',
switch_view: '_onSwitchView',
}),
events: {
@@ -37,14 +35,13 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
* @param {Object[]} params.actionViews
* @param {string} params.activeActions
* @param {string} params.bannerRoute
- * @param {Array[]} params.controlPanelDomain
- * @param {ControlPanelModel} [params.controlPanelModel]
- * @param {Object} [params.controlPanelProps]
+ * @param {Object} [params.controlPanel]
* @param {string} params.controllerID an id to ease the communication with
* upstream components
* @param {string} params.displayName
* @param {Object} params.initialState
* @param {string} params.modelName
+ * @param {ActionModel} [params.searchModel]
* @param {string} [params.searchPanel]
* @param {string} params.viewType
* @param {boolean} [params.withControlPanel]
@@ -64,18 +61,20 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
this.dp = new concurrency.DropPrevious();
this.withControlPanel = params.withControlPanel;
+ this.withSearchPanel = params.withSearchPanel;
+ if (params.searchModel) {
+ this.searchModel = params.searchModel;
+ }
if (this.withControlPanel) {
- this.controlPanelProps = params.controlPanelProps;
- this._controlPanelModel = params.controlPanelModel;
+ const { Component, props } = params.controlPanel;
+ this.ControlPanel = Component;
+ this.controlPanelProps = props;
}
-
- this.withSearchPanel = params.withSearchPanel && params.searchPanel;
- // the following attributes are used when there is a searchPanel
if (this.withSearchPanel) {
- this._searchPanel = params.searchPanel;
+ const { Component, props } = params.searchPanel;
+ this.SearchPanel = Component;
+ this.searchPanelProps = props;
}
- this.controlPanelDomain = params.controlPanelDomain || [];
- this.searchPanelDomain = this._searchPanel ? this._searchPanel.getDomain() : [];
},
/**
@@ -84,23 +83,23 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
* @returns {Promise}
*/
start: async function () {
- if (this.withSearchPanel) {
- this.$('.o_content')
- .addClass('o_controller_with_searchpanel')
- .prepend(this._searchPanel.$el);
- }
this.$el.addClass('o_view_controller');
-
this.renderButtons();
const promises = [this._super(...arguments)];
if (this.withControlPanel) {
this._updateControlPanelProps(this.initialState);
- this._controlPanelWrapper = new ComponentWrapper(this, ControlPanel, this.controlPanelProps);
+ this._controlPanelWrapper = new ComponentWrapper(this, this.ControlPanel, this.controlPanelProps);
this._controlPanelWrapper.env.bus.on('focus-view', this, () => this._giveFocus());
promises.push(this._controlPanelWrapper.mount(this.el, { position: 'first-child' }));
}
+ if (this.withSearchPanel) {
+ this._searchPanelWrapper = new ComponentWrapper(this, this.SearchPanel, this.searchPanelProps);
+ const content = this.el.querySelector(':scope .o_content');
+ content.classList.add('o_controller_with_searchpanel');
+ promises.push(this._searchPanelWrapper.mount(content, { position: 'first-child' }));
+ }
await Promise.all(promises);
- await this._update(this.initialState, { shouldUpdateControlPanel: false });
+ await this._update(this.initialState, { shouldUpdateSearchComponents: false });
this.updateButtons();
},
/**
@@ -118,12 +117,9 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
*/
on_attach_callback: function () {
ActionMixin.on_attach_callback.call(this);
- if (this.withSearchPanel) {
- this._searchPanel.on_attach_callback();
- }
+ this.searchModel.on('search', this, this._onSearch);
if (this.withControlPanel) {
- this._controlPanelModel.on('search', this, this._onSearch);
- this._controlPanelModel.on('get-controller-query-params', this, this._onGetOwnedQueryParams);
+ this.searchModel.on('get-controller-query-params', this, this._onGetOwnedQueryParams);
}
if (!(this.renderer instanceof owl.Component)) {
this.renderer.on_attach_callback();
@@ -134,9 +130,9 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
*/
on_detach_callback: function () {
ActionMixin.on_detach_callback.call(this);
+ this.searchModel.off('search', this);
if (this.withControlPanel) {
- this._controlPanelModel.off('search', this);
- this._controlPanelModel.off('get-controller-query-params', this);
+ this.searchModel.off('get-controller-query-params', this);
}
if (!(this.renderer instanceof owl.Component)) {
this.renderer.on_detach_callback();
@@ -177,20 +173,32 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
},
/**
* Export the state of the controller containing information that is shared
- * between different controllers of a same action (like the current
- * searchQuery of the controlPanel).
+ * between different controllers of a same action (like the current search
+ * model state or the states of some components).
*
* @returns {Object}
*/
- exportState: function () {
- var state = {};
- if (this.withControlPanel) {
- state.cpState = this._controlPanelModel.exportState();
+ exportState() {
+ const exported = {
+ searchModel: this.searchModel.exportState(),
+ };
+ if (this.withSearchPanel) {
+ const searchPanel = this._searchPanelWrapper.componentRef.comp;
+ exported.searchPanel = searchPanel.exportState();
}
+ return exported;
+ },
+ /**
+ * Parses and imports a previously exported state.
+ *
+ * @param {Object} state
+ */
+ importState(state) {
+ this.searchModel.importState(state.searchModel);
if (this.withSearchPanel) {
- state.spState = this._searchPanel.exportState();
+ const searchPanel = this._searchPanelWrapper.componentRef.comp;
+ searchPanel.importState(state.searchPanel);
}
- return state;
},
/**
* The use of this method is discouraged. It is still snakecased, because
@@ -207,40 +215,17 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
/**
* Short helper method to reload the view
*
- * @param {Object} [params={}] This object will simply be given to the update
+ * @param {Object} [params={}]
+ * @param {Object} [params.controllerState={}]
* @returns {Promise}
*/
reload: async function (params = {}) {
- const controllerState = params.controllerState || {};
- const cpState = controllerState.cpState;
- if (this.withControlPanel && cpState) {
- this._controlPanelModel.importState(cpState);
- const searchQuery = this._controlPanelModel.getQuery();
- params = Object.assign({}, params, searchQuery);
- }
- let postponeRendering = false;
- if (this.withSearchPanel) {
- if (params.domain) {
- this.controlPanelDomain = params.domain;
- }
- if (controllerState.spState) {
- this._searchPanel.importState(controllerState.spState);
- } else {
- const viewDomain = await this._getViewDomain();
- await this._searchPanel.update({
- searchDomain: this.controlPanelDomain,
- viewDomain,
- });
- postponeRendering = !params.noRender;
- params.noRender = true; // wait for searchpanel to be ready to render
- }
- this.searchPanelDomain = this._searchPanel.getDomain();
- params.domain = this.controlPanelDomain.concat(this.searchPanelDomain);
- }
- await this.update(params, {});
- if (postponeRendering) {
- return this._updateRendererState(false);
+ if (params.controllerState) {
+ this.importState(params.controllerState);
+ await this.searchModel.load();
+ Object.assign(params, this.searchModel.get('query'));
}
+ return this.update(params, {});
},
/**
* This is the main entry point for the controller. Changes from the search
@@ -288,15 +273,6 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
return null;
},
/**
- * Get the domain defined by the view. It is meant to be overridden.
- *
- * @private
- * @returns {Promise<Array[]>}
- */
- _getViewDomain: async function () {
- return [];
- },
- /**
* Meant to be overriden to return a proper object.
* @private
* @param {Object} [state]
@@ -409,7 +385,8 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
* @private
* @param {Object} state the state given by the model
* @param {Object} [params={}]
- * @param {Object} [params.shouldUpdateControlPanel]
+ * @param {Array} [params.breadcrumbs]
+ * @param {Object} [params.shouldUpdateSearchComponents]
* @returns {Promise}
*/
async _update(state, params) {
@@ -418,12 +395,17 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
this.renderButtons();
}
const promises = [this._renderBanner()];
- if (this.withControlPanel && params.shouldUpdateControlPanel !== false) {
- this._updateControlPanelProps(state);
- if (params.breadcrumbs) {
- this.controlPanelProps.breadcrumbs = params.breadcrumbs;
+ if (params.shouldUpdateSearchComponents !== false) {
+ if (this.withControlPanel) {
+ this._updateControlPanelProps(state);
+ if (params.breadcrumbs) {
+ this.controlPanelProps.breadcrumbs = params.breadcrumbs;
+ }
+ promises.push(this.updateControlPanel());
+ }
+ if (this.withSearchPanel) {
+ this._updateSearchPanel();
}
- promises.push(this.updateControlPanel());
}
this._pushState();
await Promise.all(promises);
@@ -476,6 +458,15 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
}
return this.renderer.updateState(state, params);
},
+ /**
+ * @private
+ * @param {Object} [newProps={}]
+ * @return {Promise}
+ */
+ async _updateSearchPanel(newProps) {
+ Object.assign(this.searchPanelProps, newProps);
+ await this._searchPanelWrapper.update(this.searchPanelProps);
+ },
//--------------------------------------------------------------------------
// Handlers
@@ -551,7 +542,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
switch (ev.data.direction) {
case 'up':
ev.stopPropagation();
- this._controlPanelModel.trigger('focus-control-panel');
+ this.searchModel.trigger('focus-control-panel');
break;
case 'down':
ev.stopPropagation();
@@ -594,15 +585,6 @@ var AbstractController = mvc.Controller.extend(ActionMixin, {
this.reload(_.extend({ offset: 0, groupsOffset: 0 }, searchQuery));
},
/**
- * @private
- * @param {OdooEvent} ev
- * @param {Array[]} ev.data.domain the current domain of the searchPanel
- */
- _onSearchPanelDomainUpdated: function (ev) {
- this.searchPanelDomain = ev.data.domain;
- this.reload({offset: 0});
- },
- /**
* Intercepts the 'switch_view' event to add the controllerID into the data,
* and lets the event bubble up.
*
diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js
index 6504314ddd1..04b1b781848 100644
--- a/addons/web/static/src/js/views/abstract_view.js
+++ b/addons/web/static/src/js/views/abstract_view.js
@@ -23,12 +23,13 @@ odoo.define('web.AbstractView', function (require) {
* in most case discarded.
*/
+const ActionModel = require("web/static/src/js/views/action_model.js");
var AbstractModel = require('web.AbstractModel');
var AbstractRenderer = require('web.AbstractRenderer');
var AbstractController = require('web.AbstractController');
-const ControlPanelModel = require('web.ControlPanelModel');
+const ControlPanel = require('web.ControlPanel');
+const SearchPanel = require("web/static/src/js/views/search_panel.js");
var mvc = require('web.mvc');
-var SearchPanel = require('web.SearchPanel');
var viewUtils = require('web.viewUtils');
const { Component } = owl;
@@ -60,7 +61,8 @@ var AbstractView = Factory.extend({
Model: AbstractModel,
Renderer: AbstractRenderer,
Controller: AbstractController,
- SearchPanel: SearchPanel,
+ ControlPanel,
+ SearchPanel,
}),
/**
@@ -116,10 +118,6 @@ var AbstractView = Factory.extend({
this.arch = this.fieldsView.arch;
this.fields = this.fieldsView.viewFields;
this.userContext = params.userContext || {};
- this.withControlPanel = this.withControlPanel && params.withControlPanel;
- const searchPanelDisabled = 'search_panel' in params.context && !params.search_panel;
- this.withSearchPanel = this.withSearchPanel && this.multi_record &&
- params.withSearchPanel && !searchPanelDisabled;
// the boolean parameter 'isEmbedded' determines if the view should be
// considered as a subview. For now this is only used by the graph
@@ -147,8 +145,6 @@ var AbstractView = Factory.extend({
isEmbedded: isEmbedded,
modelName: params.modelName,
viewType: this.viewType,
- withControlPanel: this.withControlPanel,
- withSearchPanel: this.withSearchPanel,
};
var controllerState = params.controllerState || {};
@@ -183,84 +179,140 @@ var AbstractView = Factory.extend({
this._updateMVCParams(params.searchQuery);
}
+ this.withControlPanel = this.withControlPanel && params.withControlPanel;
+ this.withSearchPanel = this.withSearchPanel &&
+ this.multi_record && params.withSearchPanel &&
+ !('search_panel' in params.context && !params.search_panel);
+
+ const searchModelParams = Object.assign({}, params, { action });
+ if (this.withControlPanel || this.withSearchPanel) {
+ const { arch, fields, favoriteFilters } = params.controlPanelFieldsView || {};
+ const archInfo = ActionModel.extractArchInfo({ search: arch }, this.viewType);
+ const controlPanelInfo = archInfo[this.config.ControlPanel.modelExtension];
+ const searchPanelInfo = archInfo[this.config.SearchPanel.modelExtension];
+ this.withSearchPanel = this.withSearchPanel && Boolean(searchPanelInfo);
+ Object.assign(searchModelParams, {
+ fields,
+ favoriteFilters,
+ controlPanelInfo,
+ searchPanelInfo,
+ });
+ }
+ const searchModel = this._createSearchModel(searchModelParams);
+ this.controllerParams.searchModel = searchModel;
+ if (this.controllerParams.controlPanel) {
+ this.controllerParams.controlPanel.props.searchModel = searchModel;
+ }
+ if (this.controllerParams.searchPanel) {
+ this.controllerParams.searchPanel.props.searchModel = searchModel;
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @param {Object} params
+ * @param {Object} extraExtensions
+ * @returns {ActionModel}
+ */
+ _createSearchModel: function (params, extraExtensions) {
+ // Search model + common config
+ const { fields, favoriteFilters, controlPanelInfo, searchPanelInfo } = params;
+ const extensions = Object.assign({}, extraExtensions);
+ const importedState = params.controllerState || {};
+
+ // Control panel params
if (this.withControlPanel) {
- this.controlPanelModelConfig = {
- env: Component.env,
- actionId: action.id,
- actionContext: Object.assign({}, this.loadParams.context || {}),
- actionDomain: this.loadParams.domain || [],
- modelName: params.modelName,
+ // Control panel (Model)
+ const ControlPanelComponent = this.config.ControlPanel;
+ extensions[ControlPanelComponent.modelExtension] = {
+ actionId: params.action.id,
// control initialization
activateDefaultFavorite: params.activateDefaultFavorite,
+ archNodes: controlPanelInfo.children,
dynamicFilters: params.dynamicFilters,
- viewInfo: params.controlPanelFieldsView,
+ favoriteFilters,
withSearchBar: params.withSearchBar,
- // used to avoid timeRanges in query
- searchMenuTypes: params.searchMenuTypes,
- // avoid work to initialize
- importedState: controllerState.cpState,
};
-
- const controlPanelModel = new ControlPanelModel(this.controlPanelModelConfig);
-
+ this.controllerParams.withControlPanel = true;
+ // Control panel (Component)
const controlPanelProps = {
- action,
+ action: params.action,
breadcrumbs: params.breadcrumbs,
- controlPanelModel,
fields: this.fields,
searchMenuTypes: params.searchMenuTypes,
view: this.fieldsView,
- views: action.views && action.views.filter(v => v.multiRecord === this.multi_record),
+ views: params.action.views && params.action.views.filter(
+ v => v.multiRecord === this.multi_record
+ ),
withBreadcrumbs: params.withBreadcrumbs,
withSearchBar: params.withSearchBar,
};
- this.controllerParams.controlPanelModel = controlPanelModel;
- this.controllerParams.controlPanelProps = controlPanelProps;
+ this.controllerParams.controlPanel = {
+ Component: ControlPanelComponent,
+ props: controlPanelProps,
+ };
}
+ // Search panel params
if (this.withSearchPanel) {
- this.searchPanelParams = {
- arch: (params.controlPanelFieldsView || {}).arch,
+ // Search panel (Model)
+ const SearchPanelComponent = this.config.SearchPanel;
+ const defaultValues = {};
+ Object.keys(this.loadParams.context).forEach((key) => {
+ let match = /^searchpanel_default_(.*)$/.exec(key);
+ if (match) {
+ defaultValues[match[1]] = this.loadParams.context[key];
+ }
+ });
+ extensions[SearchPanelComponent.modelExtension] = {
+ archNodes: searchPanelInfo.children,
defaultNoFilter: params.searchPanelDefaultNoFilter,
- fields: this.fields,
- model: this.loadParams.modelName,
- state: controllerState.spState,
+ defaultValues,
+ };
+ this.controllerParams.withSearchPanel = true;
+ this.rendererParams.withSearchPanel = true;
+ // Search panel (Component)
+ const searchPanelProps = {
+ importedState: importedState.searchPanel,
+ };
+ if (searchPanelInfo.attrs.class) {
+ searchPanelProps.className = searchPanelInfo.attrs.class;
+ }
+ this.controllerParams.searchPanel = {
+ Component: SearchPanelComponent,
+ props: searchPanelProps,
};
}
- },
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
+ const searchModel = new ActionModel(extensions, {
+ env: Component.env,
+ modelName: params.modelName,
+ context: Object.assign({}, this.loadParams.context),
+ domain: this.loadParams.domain || [],
+ importedState: importedState.searchModel,
+ searchMenuTypes: params.searchMenuTypes,
+ searchQuery: params.searchQuery,
+ fields,
+ });
+
+ return searchModel;
+ },
/**
* @override
*/
- getController: async function (parent) {
+ getController: async function () {
const _super = this._super.bind(this);
- if (this.withControlPanel) {
- await this.controllerParams.controlPanelModel.isReady;
- const query = this.controllerParams.controlPanelModel.getQuery();
- this._updateMVCParams(query);
- }
- let searchPanel = false;
- if (this.withSearchPanel) {
- const spProto = this.config.SearchPanel.prototype;
- const { arch, fields } = this.searchPanelParams;
- const searchPanelParams = spProto.computeSearchPanelParams(arch, fields, this.viewType);
- if (searchPanelParams.sections) {
- this.searchPanelParams.sections = searchPanelParams.sections;
- this.rendererParams.withSearchPanel = true;
- searchPanel = await this._createSearchPanel(parent, searchPanelParams);
- }
- }
+ await this.controllerParams.searchModel.load();
+ const query = this.controllerParams.searchModel.get('query');
+ this._updateMVCParams(query);
// get the parent of the model if it already exists, as _super will
// set the new controller as parent, which we don't want
const modelParent = this.model && this.model.getParent();
const controller = await _super(...arguments);
- if (searchPanel) {
- searchPanel.setParent(controller);
- }
if (modelParent) {
// if we already add a model, restore its parent
this.model.setParent(modelParent);
@@ -294,36 +346,6 @@ var AbstractView = Factory.extend({
/**
* @private
- * @param {Widget} parent
- * @returns {Promise<SearchPanel>} resolved when the searchPanel is ready
- */
- _createSearchPanel: async function (parent, params) {
- var defaultValues = {};
- Object.keys(this.loadParams.context).forEach((key) => {
- let match = /^searchpanel_default_(.*)$/.exec(key);
- if (match) {
- defaultValues[match[1]] = this.loadParams.context[key];
- }
- });
- var controlPanelDomain = this.loadParams.domain;
- const viewDomain = await this._getViewDomain(parent);
- var spParams = _.extend({}, this.searchPanelParams, {
- defaultValues: defaultValues,
- searchDomain: controlPanelDomain,
- viewDomain,
- classes: params.classes || [],
- });
- var searchPanel = new this.config.SearchPanel(parent, spParams);
- this.controllerParams.searchPanel = searchPanel;
- this.controllerParams.controlPanelDomain = controlPanelDomain;
- await searchPanel.appendTo(document.createDocumentFragment());
-
- var searchPanelDomain = searchPanel.getDomain();
- this.loadParams.domain = controlPanelDomain.concat(searchPanelDomain);
- return searchPanel;
- },
- /**
- * @private
* @param {Object} [action]
* @param {Object} [action.context || {}]
* @param {boolean} [action.context.no_breadcrumbs=false]
@@ -364,17 +386,6 @@ var AbstractView = Factory.extend({
};
},
/**
- * Get the domain defined by the view. It is meant to be overridden. The parent
- * is provided in case some subcomponents need to be retrieved.
- *
- * @private
- * @param {Object} parent
- * @returns {Promise<Array[]>}
- */
- _getViewDomain: async function (parent) {
- return [];
- },
- /**
* Processes a fieldsView. In particular, parses its arch.
*
* @private
diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js
index f7028d93d08..e872fff811f 100644
--- a/addons/web/static/src/js/views/kanban/kanban_controller.js
+++ b/addons/web/static/src/js/views/kanban/kanban_controller.js
@@ -101,7 +101,7 @@ var KanbanController = BasicController.extend({
*/
_addQuickCreate(groupId) {
this._removeSampleData(async () => {
- await this.update({ shouldUpdateControlPanel: false }, { reload: false });
+ await this.update({ shouldUpdateSearchComponents: false }, { reload: false });
return this.renderer.addQuickCreate(groupId);
});
},
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index abe3856b91e..1e75a8dacc0 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -62,7 +62,7 @@
class="o_group_by_menu"
fields="fields"
/>
- <ComparisonMenu t-if="props.searchMenuTypes.includes('comparison') and model.getFiltersOfType('comparison').length"
+ <ComparisonMenu t-if="props.searchMenuTypes.includes('comparison') and model.get('filters', f => f.type === 'comparison').length"
class="o_comparison_menu"
/>
<FavoriteMenu t-if="props.searchMenuTypes.includes('favorite')"
@@ -555,15 +555,15 @@
<t t-name="web.SearchBar" owl="1">
<div class="o_searchview_input_container">
- <div t-foreach="model.getFacets()" t-as="facet" t-key="facet_index"
+ <div t-foreach="model.get('facets')" t-as="facet" t-key="facet_index"
tabindex="0"
class="o_searchview_facet"
role="img"
aria-label="search"
t-on-keydown="_onFacetKeydown(facet, facet_index)"
>
- <span t-if="facet.title" class="o_searchview_facet_label" t-esc="facet.title"/>
- <span t-else="" t-attf-class="o_searchview_facet_label {{ facet.icon }}"/>
+ <span t-if="facet.icon" t-attf-class="o_searchview_facet_label {{ facet.icon }}"/>
+ <span t-else="" class="o_searchview_facet_label" t-esc="facet.title"/>
<div class="o_facet_values">
<t t-foreach="facet.values" t-as="facetValue" t-key="facetValue_index">
<span t-if="!facetValue_first" class="o_facet_values_sep" t-esc="facet.separator"/>
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml
index 42492ede85a..c0812ace9da 100644
--- a/addons/web/views/webclient_templates.xml
+++ b/addons/web/views/webclient_templates.xml
@@ -751,7 +751,7 @@
<script type="text/javascript" src="/web/static/tests/components/action_menus_tests.js"></script>
<script type="text/javascript" src="/web/static/tests/components/dropdown_menu_tests.js"></script>
- <script type="text/javascript" src="/web/static/tests/control_panel/control_panel_model_tests.js"></script>
+ <script type="text/javascript" src="/web/static/tests/control_panel/control_panel_model_extension_tests.js"></script>
<script type="text/javascript" src="/web/static/tests/control_panel/control_panel_tests.js"></script>
<script type="text/javascript" src="/web/static/tests/control_panel/comparison_menu_tests.js"></script>
<script type="text/javascript" src="/web/static/tests/control_panel/favorite_menu_tests.js"></script>