summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/views
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/static/src/js/views
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/static/src/js/views')
-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
3 files changed, 183 insertions, 190 deletions
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);
});
},