summaryrefslogtreecommitdiff
path: root/addons/web/static/src
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src')
-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
15 files changed, 265 insertions, 255 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"/>