diff options
| author | Lucas Perais (lpe) <lpe@odoo.com> | 2019-07-04 07:40:18 +0000 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2019-07-19 08:39:07 +0000 |
| commit | dd7022eccd99b5ba9688f63b517d23c405f23797 (patch) | |
| tree | 2e1d786ec1527a027ac2df693f21a3d6ddb131ee /addons/web/static/src/js/views | |
| parent | 38b5a666c50d47a0efe060c031d68a2d70689dc6 (diff) | |
[IMP] base,web*: searchpanel in all multi record views
Before this rev. the searchpanel could only be activated in kanban
views (and it was specified in the kanban arch). From now on, the
searchpanel can be activated in any multi record view. It's arch
is moved to the search view arch (inside the <searchpanel> tag).
Task 1985921
Co-authored-by: Aaron Bohy <aab@odoo.com>
Diffstat (limited to 'addons/web/static/src/js/views')
| -rw-r--r-- | addons/web/static/src/js/views/abstract_controller.js | 76 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/abstract_renderer.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/abstract_view.js | 67 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/basic/basic_view.js | 2 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/calendar/calendar_view.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/control_panel/control_panel_view.js | 10 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/graph/graph_view.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/kanban/kanban_controller.js | 59 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/kanban/kanban_renderer.js | 11 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/kanban/kanban_view.js | 129 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/pivot/pivot_view.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/qweb/qweb_view.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/search_panel.js (renamed from addons/web/static/src/js/views/kanban/search_panel.js) | 143 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/view_dialogs.js | 1 |
14 files changed, 281 insertions, 237 deletions
diff --git a/addons/web/static/src/js/views/abstract_controller.js b/addons/web/static/src/js/views/abstract_controller.js index d8cc6baa8f5..58a011599d1 100644 --- a/addons/web/static/src/js/views/abstract_controller.js +++ b/addons/web/static/src/js/views/abstract_controller.js @@ -28,6 +28,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { open_record: '_onOpenRecord', search: '_onSearch', switch_view: '_onSwitchView', + search_panel_domain_updated: '_onSearchPanelDomainUpdated', }, events: { 'click a[type="action"]': '_onActionClicked', @@ -58,6 +59,11 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { this.viewType = params.viewType; // use a DropPrevious to correctly handle concurrent updates this.dp = new concurrency.DropPrevious(); + + // the following attributes are used when there is a searchPanel + this._searchPanel = params.searchPanel; + this.controlPanelDomain = params.controlPanelDomain || []; + this.searchPanelDomain = this._searchPanel ? this._searchPanel.getDomain() : []; }, /** * Simply renders and updates the url. @@ -66,6 +72,11 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { */ start: function () { var self = this; + if (this._searchPanel) { + this.$('.o_content') + .addClass('o_controller_with_searchpanel') + .prepend(this._searchPanel.$el); + } this.$el.addClass('o_view_controller'); @@ -123,7 +134,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { */ canBeRemoved: function () { // AAB: get rid of this option when on_hashchange mechanism is improved - return this.discardChanges(undefined, { readonlyIfRealDiscard: true }); + return this.discardChanges(undefined, {readonlyIfRealDiscard: true}); }, /** * Discards the changes made on the record associated to the given ID, or @@ -154,6 +165,9 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { if (this._controlPanel) { state.cpState = this._controlPanel.exportState(); } + if (this._searchPanel) { + state.spState = this._searchPanel.exportState(); + } return state; }, /** @@ -180,20 +194,33 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { * @param {Object} [params] This object will simply be given to the update * @returns {Promise} */ - reload: function (params) { + reload: async function (params) { params = params || {}; - var self = this; - var def; + var searchPanelUpdateProm; var controllerState = params.controllerState || {}; var cpState = controllerState.cpState; if (this._controlPanel && cpState) { - def = this._controlPanel.importState(cpState).then(function (searchQuery) { + await this._controlPanel.importState(cpState).then(function (searchQuery) { params = _.extend({}, params, searchQuery); }); } - return Promise.resolve(def).then(function () { - return self.update(params, {}); - }); + var postponeRendering = false; + if (this._searchPanel) { + this.controlPanelDomain = params.domain || this.controlPanelDomain; + if (controllerState.spState) { + this._searchPanel.importState(controllerState.spState); + this.searchPanelDomain = this._searchPanel.getDomain(); + } else { + searchPanelUpdateProm = this._searchPanel.update({searchDomain: this._getSearchDomain()}); + postponeRendering = !params.noRender; + params.noRender = true; // wait for searchpanel to be ready to render + } + params.domain = this.controlPanelDomain.concat(this.searchPanelDomain); + } + await Promise.all([this.update(params, {}), searchPanelUpdateProm]); + if (postponeRendering) { + return this.renderer._render(); + } }, /** * For views that require a pager, this method will be called to allow the @@ -267,6 +294,18 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { //-------------------------------------------------------------------------- /** + * Return the current search domain. This is the searchDomain used to update + * the searchpanel. It returns the domain coming from the controlpanel. This + * function can be overridden to add sub-domains coming from other parts of + * the interface. + * + * @private + * @returns {Array[]} + */ + _getSearchDomain: function () { + return this.controlPanelDomain; + }, + /** * This method is the way a view can notifies the outside world that * something has changed. The main use for this is to update the url, for * example with a new id. @@ -302,7 +341,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { if (this.bannerRoute !== undefined) { var self = this; return this.dp - .add(this._rpc({ route: this.bannerRoute })) + .add(this._rpc({route: this.bannerRoute})) .then(function (response) { if (!response.html) { self.$el.removeClass('o_has_banner'); @@ -370,7 +409,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { */ _renderSwitchButtons: function () { var self = this; - var views = _.filter(this.actionViews, { multiRecord: this.isMultiRecord }); + var views = _.filter(this.actionViews, {multiRecord: this.isMultiRecord}); if (views.length <= 1) { return $(); @@ -389,7 +428,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { var $switchButtonsFiltered = config.device.isMobile ? $switchButtons.find('button') : $switchButtons.filter('button'); $switchButtonsFiltered.click(_.debounce(function (event) { var viewType = $(event.target).data('view-type'); - self.trigger_up('switch_view', { view_type: viewType }); + self.trigger_up('switch_view', {view_type: viewType}); }, 200, true)); // set active view's icon as view switcher button's icon in mobile @@ -519,8 +558,8 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { * @private * @param {OdooEvent} ev */ - _onNavigationMove : function (ev) { - switch(ev.data.direction) { + _onNavigationMove: function (ev) { + switch (ev.data.direction) { case 'down' : ev.stopPropagation(); this.giveFocus(); @@ -543,7 +582,7 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { */ _onOpenRecord: function (ev) { ev.stopPropagation(); - var record = this.model.get(ev.data.id, { raw: true }); + var record = this.model.get(ev.data.id, {raw: true}); this.trigger_up('switch_view', { view_type: 'form', res_id: record.res_id, @@ -566,6 +605,15 @@ var AbstractController = mvc.Controller.extend(ActionMixin, { this.reload(_.extend({offset: 0}, ev.data)); }, /** + * @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_renderer.js b/addons/web/static/src/js/views/abstract_renderer.js index 724b63bae79..afaf275ab5b 100644 --- a/addons/web/static/src/js/views/abstract_renderer.js +++ b/addons/web/static/src/js/views/abstract_renderer.js @@ -21,6 +21,7 @@ return mvc.Renderer.extend({ this._super.apply(this, arguments); this.arch = params.arch; this.noContentHelp = params.noContentHelp; + this.withSearchPanel = params.withSearchPanel; }, /** * The rendering is asynchronous. The start @@ -30,6 +31,9 @@ return mvc.Renderer.extend({ */ start: function () { this.$el.addClass(this.arch.attrs.class); + if (this.withSearchPanel) { + this.$el.addClass('o_renderer_with_searchpanel'); + } return Promise.all([this._render(), this._super()]); }, /** diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js index dbb1b3e557c..fc8e73136d8 100644 --- a/addons/web/static/src/js/views/abstract_view.js +++ b/addons/web/static/src/js/views/abstract_view.js @@ -28,6 +28,7 @@ var AbstractRenderer = require('web.AbstractRenderer'); var AbstractController = require('web.AbstractController'); var ControlPanelView = require('web.ControlPanelView'); var mvc = require('web.mvc'); +var SearchPanel = require('web.SearchPanel'); var viewUtils = require('web.viewUtils'); var Factory = mvc.Factory; @@ -50,11 +51,14 @@ var AbstractView = Factory.extend({ searchMenuTypes: ['filter', 'groupBy', 'favorite'], // determines if a control panel should be instantiated withControlPanel: true, + // determines if a search panel could be instantiated + withSearchPanel: true, // determines the MVC components to use config: _.extend({}, Factory.prototype.config, { Model: AbstractModel, Renderer: AbstractRenderer, Controller: AbstractController, + SearchPanel: SearchPanel, }), /** @@ -75,7 +79,7 @@ var AbstractView = Factory.extend({ * @param {string} [params.controllerID] * @param {number} [params.count] * @param {number} [params.currentId] - * @param {string} [params.controllerState] + * @param {Object} [params.controllerState] * @param {string} [params.displayName] * @param {Array[]} [params.domain=[]] * @param {Object[]} [params.dynamicFilters] transmitted to the @@ -88,6 +92,7 @@ var AbstractView = Factory.extend({ * @param {string[]} [params.searchQuery.groupBy=[]] * @param {Object} [params.userContext={}] * @param {boolean} [params.withControlPanel=true] + * @param {boolean} [params.withSearchPanel=true] */ init: function (viewInfo, params) { this._super.apply(this, arguments); @@ -110,6 +115,7 @@ var AbstractView = Factory.extend({ this.fields = this.fieldsView.viewFields; this.userContext = params.userContext || {}; this.withControlPanel = this.withControlPanel && params.withControlPanel; + this.withSearchPanel = this.withSearchPanel && this.multi_record && params.withSearchPanel; // the boolean parameter 'isEmbedded' determines if the view should be // considered as a subview. For now this is only used by the graph @@ -180,6 +186,9 @@ var AbstractView = Factory.extend({ withBreadcrumbs: params.withBreadcrumbs, withSearchBar: params.withSearchBar, }; + this.searchPanelParams = { + state: controllerState.spState, + }; }, //-------------------------------------------------------------------------- @@ -191,20 +200,32 @@ var AbstractView = Factory.extend({ */ getController: function (parent) { var self = this; - var def; - if (this.withControlPanel) { - def = this._createControlPanel(parent); + var cpDef = this.withControlPanel && this._createControlPanel(parent); + var spDef; + if (this.withSearchPanel) { + var spProto = this.config.SearchPanel.prototype; + var viewInfo = this.controlPanelParams.viewInfo; + var sections = spProto.computeSearchPanelParams(viewInfo, this.viewType); + if (sections) { + this.searchPanelParams.sections = sections; + this.rendererParams.withSearchPanel = true; + spDef = Promise.resolve(cpDef).then(this._createSearchPanel.bind(this, parent)); + } } + var _super = this._super.bind(this); - return Promise.resolve(def).then(function (controlPanel) { + return Promise.all([cpDef, spDef]).then(function ([controlPanel, searchPanel]) { // get the parent of the model if it already exists, as _super will // set the new controller as parent, which we don't want var modelParent = self.model && self.model.getParent(); - var prom = _super(parent); + var prom = _super(parent); prom.then(function (controller) { if (controlPanel) { controlPanel.setParent(controller); } + if (searchPanel) { + searchPanel.setParent(controller); + } if (modelParent) { // if we already add a model, restore its parent self.model.setParent(modelParent); @@ -243,7 +264,8 @@ var AbstractView = Factory.extend({ * * @private * @param {Widget} parent - * @returns {ControlPanelController} + * @returns {Promise<ControlPanelController>} resolved when the controlPanel + * is ready */ _createControlPanel: function (parent) { var self = this; @@ -258,6 +280,36 @@ var AbstractView = Factory.extend({ }, /** * @private + * @param {Widget} parent + * @returns {Promise<SearchPanel>} resolved when the searchPanel is ready + */ + _createSearchPanel: async function (parent) { + 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; + var searchPanel = new this.config.SearchPanel(parent, { + defaultValues: defaultValues, + fields: this.fields, + model: this.loadParams.modelName, + searchDomain: controlPanelDomain, + sections: this.searchPanelParams.sections, + state: this.searchPanelParams.state, + }); + 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] @@ -294,6 +346,7 @@ var AbstractView = Factory.extend({ withBreadcrumbs: 'no_breadcrumbs' in context ? !context.no_breadcrumbs : true, withControlPanel: this.withControlPanel, withSearchBar: inline ? false : this.withSearchBar, + withSearchPanel: this.withSearchPanel, }; }, /** diff --git a/addons/web/static/src/js/views/basic/basic_view.js b/addons/web/static/src/js/views/basic/basic_view.js index 008bbcf8775..fde334d79d0 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -52,8 +52,8 @@ var BasicView = AbstractView.extend({ this.loadParams.fieldsInfo = this.fieldsInfo; this.loadParams.fields = this.fields; this.loadParams.limit = parseInt(this.arch.attrs.limit, 10) || params.limit; - this.loadParams.viewType = this.viewType; this.loadParams.parentID = params.parentID; + this.loadParams.viewType = this.viewType; this.recordID = params.recordID; this.model = params.model; diff --git a/addons/web/static/src/js/views/calendar/calendar_view.js b/addons/web/static/src/js/views/calendar/calendar_view.js index 90db648ffd3..2ee927a4af0 100644 --- a/addons/web/static/src/js/views/calendar/calendar_view.js +++ b/addons/web/static/src/js/views/calendar/calendar_view.js @@ -23,11 +23,11 @@ var CalendarView = AbstractView.extend({ icon: 'fa-calendar', jsLibs: ['/web/static/lib/fullcalendar/js/fullcalendar.js'], cssLibs: ['/web/static/lib/fullcalendar/css/fullcalendar.css'], - config: { + config: _.extend({}, AbstractView.prototype.config, { Model: CalendarModel, Controller: CalendarController, Renderer: CalendarRenderer, - }, + }), viewType: 'calendar', searchMenuTypes: ['filter', 'favorite'], diff --git a/addons/web/static/src/js/views/control_panel/control_panel_view.js b/addons/web/static/src/js/views/control_panel/control_panel_view.js index 0a140e1f901..c15840714db 100644 --- a/addons/web/static/src/js/views/control_panel/control_panel_view.js +++ b/addons/web/static/src/js/views/control_panel/control_panel_view.js @@ -201,7 +201,15 @@ var ControlPanelView = Factory.extend({ */ _parseSearchArch: function (arch) { var self = this; - var preFilters = _.flatten(arch.children.map(function (child) { + // a searchview arch may contain a 'searchpanel' node, but this isn't + // the concern of the ControlPanelView (the SearchPanel will handle it). + // Ideally, this code should whitelist the tags to take into account + // instead of blacklisting the others, but with the current (messy) + // structure of a searchview arch, it's way simpler to do it that way. + var children = arch.children.filter(function (child) { + return child.tag !== 'searchpanel'; + }); + var preFilters = _.flatten(children.map(function (child) { return child.tag !== 'group' ? self._evalArchChild(child) : child.children.map(self._evalArchChild); diff --git a/addons/web/static/src/js/views/graph/graph_view.js b/addons/web/static/src/js/views/graph/graph_view.js index 4a91bd67f19..f8125675d4e 100644 --- a/addons/web/static/src/js/views/graph/graph_view.js +++ b/addons/web/static/src/js/views/graph/graph_view.js @@ -25,11 +25,11 @@ var GraphView = AbstractView.extend({ jsLibs: [ '/web/static/lib/Chart/Chart.js', ], - config: { + config: _.extend({}, AbstractView.prototype.config, { Model: GraphModel, Controller: Controller, Renderer: GraphRenderer, - }, + }), viewType: 'graph', searchMenuTypes: ['filter', 'groupBy', 'timeRange', 'favorite'], 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 bf39906b8e5..a6816b08b39 100644 --- a/addons/web/static/src/js/views/kanban/kanban_controller.js +++ b/addons/web/static/src/js/views/kanban/kanban_controller.js @@ -33,7 +33,6 @@ var KanbanController = BasicController.extend({ kanban_load_records: '_onLoadColumnRecords', column_toggle_fold: '_onToggleColumn', kanban_column_records_toggle_active: '_onToggleActiveRecords', - search_panel_domain_updated: '_onSearchPanelDomainUpdated', }), events: _.extend({}, BasicController.prototype.events, { click: '_onClick', @@ -52,22 +51,6 @@ var KanbanController = BasicController.extend({ this.on_create = params.on_create; this.hasButtons = params.hasButtons; this.quickCreateEnabled = params.quickCreateEnabled; - - // the following attributes are used when there is a searchPanel - this._searchPanel = params.searchPanel; - this.controlPanelDomain = params.controlPanelDomain || []; - this.searchPanelDomain = this._searchPanel ? this._searchPanel.getDomain() : []; - }, - /** - * @override - */ - start: function () { - if (this._searchPanel) { - this.$('.o_content') - .addClass('o_kanban_with_searchpanel') - .prepend(this._searchPanel.$el); - } - return this._super.apply(this, arguments); }, //-------------------------------------------------------------------------- @@ -91,32 +74,6 @@ var KanbanController = BasicController.extend({ } return Promise.resolve(); }, - /** - * Override to add the domain coming from the searchPanel (if any) to the - * domain coming from the controlPanel. - * - * @override - */ - update: function (params) { - if (!this._searchPanel) { - return this._super.apply(this, arguments); - } - var self = this; - if (params.domain) { - this.controlPanelDomain = params.domain; - } - // do not re-render the view as soon as records have been fetched, but - // wait for the searchPanel to be ready as well, such that the view - // isn't re-rendered before the searchPanel - params.noRender = true; - params.domain = this.controlPanelDomain.concat(this.searchPanelDomain); - var superProm = this._super.apply(this, arguments); - var searchPanelProm = this._updateSearchPanel(); - return Promise.all([superProm, searchPanelProm]).then(function () { - // searchPanel has been re-rendered, so re-render the view - return self.renderer.render(); - }); - }, //-------------------------------------------------------------------------- // Private @@ -256,13 +213,6 @@ var KanbanController = BasicController.extend({ this.$buttons.find('.o-kanban-button-new').toggleClass('o_hidden', createHidden); } }, - /** - * @private - * @returns {Promise} - */ - _updateSearchPanel: function () { - return this._searchPanel.update({searchDomain: this.controlPanelDomain}); - }, //-------------------------------------------------------------------------- // Handlers @@ -522,15 +472,6 @@ var KanbanController = BasicController.extend({ /** * @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}); - }, - /** - * @private - * @param {OdooEvent} ev * @param {boolean} [ev.data.openQuickCreate=false] if true, opens the * QuickCreate in the toggled column (it assumes that we are opening it) */ diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js index 5be98e4de52..ca6f8893cd8 100644 --- a/addons/web/static/src/js/views/kanban/kanban_renderer.js +++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js @@ -173,17 +173,6 @@ var KanbanRenderer = BasicRenderer.extend({ this._toggleNoContentHelper(); }, /** - * Allow the rendering to be triggered from outside. This is used for kanban - * views with a searchPanel, to synchronize updates (the view is updated - * with param 'noRender', so that it is not re-rendered before the - * searchPanel). - * - * @returns {$.Promise} - */ - render: function () { - return this._render(); - }, - /** * Updates a given column with its new state. * * @param {string} localID the column id diff --git a/addons/web/static/src/js/views/kanban/kanban_view.js b/addons/web/static/src/js/views/kanban/kanban_view.js index 65e6fed767b..e31bff931d1 100644 --- a/addons/web/static/src/js/views/kanban/kanban_view.js +++ b/addons/web/static/src/js/views/kanban/kanban_view.js @@ -8,8 +8,6 @@ var KanbanController = require('web.KanbanController'); var kanbanExamplesRegistry = require('web.kanban_examples_registry'); var KanbanModel = require('web.KanbanModel'); var KanbanRenderer = require('web.KanbanRenderer'); -var pyUtils = require('web.py_utils'); -var SearchPanel = require('web.SearchPanel'); var utils = require('web.utils'); var _lt = core._lt; @@ -19,12 +17,11 @@ var KanbanView = BasicView.extend({ display_name: _lt("Kanban"), icon: 'fa-th-large', mobile_friendly: true, - config: { + config: _.extend({}, BasicView.prototype.config, { Model: KanbanModel, Controller: KanbanController, Renderer: KanbanRenderer, - SearchPanel: SearchPanel, - }, + }), jsLibs: [], viewType: 'kanban', @@ -32,8 +29,6 @@ var KanbanView = BasicView.extend({ * @constructor */ init: function (viewInfo, params) { - this.searchPanelSections = Object.create(null); - this._super.apply(this, arguments); this.loadParams.limit = this.loadParams.limit || 40; @@ -92,7 +87,6 @@ var KanbanView = BasicView.extend({ this.jsLibs.push('/web/static/lib/jquery.touchSwipe/jquery.touchSwipe.js'); } - this.hasSearchPanel = !_.isEmpty(this.searchPanelSections); }, //-------------------------------------------------------------------------- @@ -100,72 +94,6 @@ var KanbanView = BasicView.extend({ //-------------------------------------------------------------------------- /** - * Override to set the controller as parent of the optional searchPanel - * - * @override - */ - getController: function () { - var self = this; - return this._super.apply(this, arguments).then(function (controller) { - if (self.hasSearchPanel) { - self.controllerParams.searchPanel.setParent(controller); - } - return controller; - }); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Override to create the searchPanel (if necessary) with the domain coming - * from the controlPanel - * - * @override - * @private - */ - _createControlPanel: function (parent) { - if (!this.hasSearchPanel) { - return this._super.apply(this, arguments); - } - var self = this; - return this._super.apply(this, arguments).then(function (controlPanel) { - return self._createSearchPanel(parent).then(function () { - return controlPanel; - }); - }); - }, - /** - * @private - * @param {Widget} parent - * @returns {Promise} resolved when the searchPanel is ready - */ - _createSearchPanel: function (parent) { - var self = this; - var defaultValues = {}; - Object.keys(this.loadParams.context).forEach(function (key) { - var match = /^searchpanel_default_(.*)$/.exec(key); - if (match) { - defaultValues[match[1]] = self.loadParams.context[key]; - } - }); - var controlPanelDomain = this.loadParams.domain; - var searchPanel = new this.config.SearchPanel(parent, { - defaultValues: defaultValues, - fields: this.fields, - model: this.loadParams.modelName, - searchDomain: controlPanelDomain, - sections: this.searchPanelSections, - }); - this.controllerParams.searchPanel = searchPanel; - this.controllerParams.controlPanelDomain = controlPanelDomain; - return searchPanel.appendTo(document.createDocumentFragment()).then(function () { - var searchPanelDomain = searchPanel.getDomain(); - self.loadParams.domain = controlPanelDomain.concat(searchPanelDomain); - }); - }, - /** * @private * @param {Object} viewInfo * @returns {boolean} true iff the quick create feature is not explicitely @@ -181,59 +109,6 @@ var KanbanView = BasicView.extend({ return true; }, /** - * Override to handle nodes with tagname 'searchpanel'. - * - * @override - * @private - */ - _processNode: function (node, fv) { - if (node.tag === 'searchpanel') { - this._processSearchPanelNode(node, fv); - return false; - } - return this._super.apply(this, arguments); - }, - /** - * Populate this.searchPanelSections with category/filter descriptions. - * - * @private - * @param {Object} node - * @param {Object} fv - */ - _processSearchPanelNode: function (node, fv) { - var self = this; - node.children.forEach(function (childNode, index) { - if (childNode.tag !== 'field') { - return; - } - if (childNode.attrs.invisible === "1") { - return; - } - var fieldName = childNode.attrs.name; - var type = childNode.attrs.select === 'multi' ? 'filter' : 'category'; - - var sectionId = _.uniqueId('section_'); - var section = { - color: childNode.attrs.color, - description: childNode.attrs.string || fv.fields[fieldName].string, - fieldName: fieldName, - icon: childNode.attrs.icon, - id: sectionId, - index: index, - type: type, - }; - if (section.type === 'category') { - section.icon = section.icon || 'fa-folder'; - } else if (section.type === 'filter') { - section.disableCounters = !!pyUtils.py_eval(childNode.attrs.disable_counters || '0'); - section.domain = childNode.attrs.domain || '[]'; - section.groupBy = childNode.attrs.groupby; - section.icon = section.icon || 'fa-filter'; - } - self.searchPanelSections[sectionId] = section; - }); - }, - /** * @override * @private */ diff --git a/addons/web/static/src/js/views/pivot/pivot_view.js b/addons/web/static/src/js/views/pivot/pivot_view.js index 272ce7cb709..cf58e1abf4b 100644 --- a/addons/web/static/src/js/views/pivot/pivot_view.js +++ b/addons/web/static/src/js/views/pivot/pivot_view.js @@ -22,11 +22,11 @@ var GROUPABLE_TYPES = controlPanelViewParameters.GROUPABLE_TYPES; var PivotView = AbstractView.extend({ display_name: _lt('Pivot'), icon: 'fa-table', - config: { + config: _.extend({}, AbstractView.prototype.config,{ Model: PivotModel, Controller: PivotController, Renderer: PivotRenderer, - }, + }), viewType: 'pivot', searchMenuTypes: ['filter', 'groupBy', 'timeRange', 'favorite'], diff --git a/addons/web/static/src/js/views/qweb/qweb_view.js b/addons/web/static/src/js/views/qweb/qweb_view.js index 45c7340d5c1..0c858b5eb6c 100644 --- a/addons/web/static/src/js/views/qweb/qweb_view.js +++ b/addons/web/static/src/js/views/qweb/qweb_view.js @@ -178,11 +178,11 @@ var QWebView = AbstractView.extend({ viewType: 'qweb', // groupable? enableTimeRangeMenu: true, - config: { + config: _.extend({}, AbstractView.prototype.config, { Model: Model, Renderer: Renderer, Controller: Controller, - }, + }), /** * init method diff --git a/addons/web/static/src/js/views/kanban/search_panel.js b/addons/web/static/src/js/views/search_panel.js index 536409ba199..55d2f23c0fa 100644 --- a/addons/web/static/src/js/views/kanban/search_panel.js +++ b/addons/web/static/src/js/views/search_panel.js @@ -8,10 +8,60 @@ odoo.define('web.SearchPanel', function (require) { var core = require('web.core'); var Domain = require('web.Domain'); +var pyUtils = require('web.py_utils'); +var viewUtils = require('web.viewUtils'); var Widget = require('web.Widget'); var qweb = core.qweb; +// defaultViewTypes is the list of view types for which the searchpanel is +// present by default (if not explicitly stated in the 'view_types' attribute +// in the arch) +var defaultViewTypes = ['kanban', 'tree']; + +/** + * Given a <searchpanel> arch node, iterate over its children to generate the + * description of each section (being either a category or a filter). + * + * @param {Object} node a <searchpanel> arch node + * @param {Object} fields the fields of the model + * @returns {Object} + */ +function _processSearchPanelNode(node, fields) { + var sections = {}; + node.children.forEach((childNode, index) => { + if (childNode.tag !== 'field') { + return; + } + if (childNode.attrs.invisible === "1") { + return; + } + var fieldName = childNode.attrs.name; + var type = childNode.attrs.select === 'multi' ? 'filter' : 'category'; + + var sectionId = _.uniqueId('section_'); + var section = { + color: childNode.attrs.color, + description: childNode.attrs.string || fields[fieldName].string, + fieldName: fieldName, + icon: childNode.attrs.icon, + id: sectionId, + index: index, + type: type, + }; + if (section.type === 'category') { + section.icon = section.icon || 'fa-folder'; + } else if (section.type === 'filter') { + section.disableCounters = !!pyUtils.py_eval(childNode.attrs.disable_counters || '0'); + section.domain = childNode.attrs.domain || '[]'; + section.groupBy = childNode.attrs.groupby; + section.icon = section.icon || 'fa-filter'; + } + sections[sectionId] = section; + }); + return sections; +} + var SearchPanel = Widget.extend({ className: 'o_search_panel', events: { @@ -29,8 +79,10 @@ var SearchPanel = Widget.extend({ * default, for each filter and category * @param {Object} params.fields * @param {string} params.model - * @param {Object} params.sections * @param {Array[]} params.searchDomain domain coming from controlPanel + * @param {Object} params.sections + * @param {Object} [params.state] state exported by another searchpanel + * instance */ init: function (parent, params) { this._super.apply(this, arguments); @@ -42,6 +94,7 @@ var SearchPanel = Widget.extend({ return section.type === 'filter'; }); + this.initialState = params.state; this.defaultValues = params.defaultValues || {}; this.fields = params.fields; this.model = params.model; @@ -52,10 +105,16 @@ var SearchPanel = Widget.extend({ */ willStart: function () { var self = this; - var loadProm = this._fetchCategories().then(function () { - return self._fetchFilters().then(self._applyDefaultFilterValues.bind(self)); - }); - return Promise.all([loadProm, this._super.apply(this, arguments)]); + var loadCategoriesProm; + if (this.initialState) { + this.filters = this.initialState.filters; + this.categories = this.initialState.categories; + } else { + loadCategoriesProm = this._fetchCategories().then(function () { + return self._fetchFilters().then(self._applyDefaultFilterValues.bind(self)); + }); + } + return Promise.all([loadCategoriesProm, this._super.apply(this, arguments)]); }, /** * @override @@ -70,6 +129,55 @@ var SearchPanel = Widget.extend({ //-------------------------------------------------------------------------- /** + * Parse a given search view arch to extract the searchpanel information + * (i.e. a description of each filter/category). Note that according to the + * 'view_types' attribute on the <searchpanel> node, and the given viewType, + * it may return undefined, meaning that no searchpanel should be rendered + * for the current view. + * + * Note that this is static method, called by AbstractView, *before* + * instantiating the SearchPanel, as depending on what it returns, we may + * or may not instantiate a SearchPanel. + * + * @static + * @params {Object} viewInfo the viewInfo of a search view + * @params {string} viewInfo.arch + * @params {Object} viewInfo.fields + * @params {string} viewType the type of the current view (e.g. 'kanban') + * @returns {Object|undefined} + */ + computeSearchPanelParams: function (viewInfo, viewType) { + var searchPanelSections; + if (viewInfo) { + var arch = viewUtils.parseArch(viewInfo.arch); + viewType = viewType === 'list' ? 'tree' : viewType; + arch.children.forEach(function (node) { + if (node.tag === 'searchpanel') { + var attrs = node.attrs; + var viewTypes = defaultViewTypes; + if (attrs.view_types) { + viewTypes = attrs.view_types.split(','); + } + if (viewTypes.indexOf(viewType) !== -1) { + searchPanelSections = _processSearchPanelNode(node, viewInfo.fields); + } + } + }); + } + return searchPanelSections; + }, + /** + * Export the current state (categories and filters) of the searchpanel. + * + * @returns {Object} + */ + exportState: function () { + return { + categories: this.categories, + filters: this.filters, + }; + }, + /** * @returns {Array[]} the current searchPanel domain based on active * categories and checked filters */ @@ -77,6 +185,18 @@ var SearchPanel = Widget.extend({ return this._getCategoryDomain().concat(this._getFilterDomain()); }, /** + * Import a previously exported state (see exportState). + * + * @param {Object} state + * @param {Object} state.filters. + * @param {Object} state.categories + */ + importState: function (state) { + this.filters = state.filters || this.filters; + this.categories = state.categories || this.categories; + this._render(); + }, + /** * Reload the filters and re-render. Note that we only reload the filters if * the controlPanel domain or searchPanel domain has changed. * @@ -145,10 +265,10 @@ var SearchPanel = Widget.extend({ } }); category.rootIds = _.filter(_.map(values, function (value) { - return value.id; - }), function (valueId) { - var value = category.values[valueId]; - return value.parentId === false; + return value.id; + }), function (valueId) { + var value = category.values[valueId]; + return value.parentId === false; }); // set active value @@ -317,6 +437,7 @@ var SearchPanel = Widget.extend({ */ _getCategoryDomain: function () { var self = this; + function categoryToDomain(domain, categoryId) { var category = self.categories[categoryId]; if (category.activeValueId) { @@ -326,6 +447,7 @@ var SearchPanel = Widget.extend({ } return domain; } + return Object.keys(this.categories).reduce(categoryToDomain, []); }, /** @@ -341,6 +463,7 @@ var SearchPanel = Widget.extend({ */ _getFilterDomain: function () { var self = this; + function getCheckedValueIds(values) { return Object.keys(values).reduce(function (checkedValues, valueId) { if (values[valueId].checked) { @@ -349,6 +472,7 @@ var SearchPanel = Widget.extend({ return checkedValues; }, []); } + function filterToDomain(domain, filterId) { var filter = self.filters[filterId]; if (filter.groups) { @@ -367,6 +491,7 @@ var SearchPanel = Widget.extend({ } return domain; } + return Object.keys(this.filters).reduce(filterToDomain, []); }, /** diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index e35f09a6cc9..1fb20232495 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -367,6 +367,7 @@ var SelectCreateDialog = ViewDialog.extend({ modelName: this.res_model, readonly: true, withBreadcrumbs: false, + withSearchPanel: false, }, this.options.list_view_options)); listView.setController(SelectCreateListController); return listView.getController(this).then(function (controller) { |
