diff --git a/addons/barcodes/static/src/js/barcode_form_view.js b/addons/barcodes/static/src/js/barcode_form_view.js index 08463f2b997..82ca913d752 100644 --- a/addons/barcodes/static/src/js/barcode_form_view.js +++ b/addons/barcodes/static/src/js/barcode_form_view.js @@ -88,60 +88,69 @@ FormController.include({ /** * @private */ - _barcodePagerFirst: function () { - var self = this; - return this.mutex.exec(function () {}).then(function () { - if (!self.pager) { - self.do_warn(_t('Error: Pager not available')); - return; - } - self.pager.updateState({ - current_min: 1, - }, {notifyChange: true}); - }); + _barcodePagerFirst: async function () { + return this._updatePage(() => 1); }, /** * @private */ - _barcodePagerLast: function () { - var self = this; - return this.mutex.exec(function () {}).then(function () { - if (!self.pager) { - self.do_warn(_t('Error: Pager not available')); - return; - } - var state = self.model.get(self.handle, {raw: true}); - self.pager.updateState({ - current_min: state.count, - }, {notifyChange: true}); - }); + _barcodePagerLast: async function () { + return this._updatePage((min, state) => state.count); }, /** * @private */ _barcodePagerNext: function () { - var self = this; - return this.mutex.exec(function () {}).then(function () { - if (!self.pager) { - self.do_warn(_t('Error: Pager not available')); - return; + return this._updatePage((min, state) => { + min += 1; + if (min > state.count) { + min = 1; } - self.pager.next(); + return min; }); }, /** * @private */ _barcodePagerPrevious: function () { - var self = this; - return this.mutex.exec(function () {}).then(function () { - if (!self.pager) { - self.do_warn(_t('Error: Pager not available')); - return; + return this._updatePage((min, state) => { + min -= 1; + if (min < 1) { + min = state.count; } - self.pager.previous(); + return min; }); }, + /** + * Change the current minimum value of the pager using provided function. + * This function will be given the current minimum and state and must return + * the updated value. + * + * @private + * @param {Function(currentMin: Number, state: Object)} updater + */ + _updatePage: async function (updater) { + await this.mutex.exec(() => {}); + const state = this.model.get(this.handle, { raw: true }); + const pagingInfo = this._getPagingInfo(state); + if (!pagingInfo) { + return this.do_warn(_t('Error: Pager not available')); + } + const currentMinimum = updater(pagingInfo.currentMinimum, state); + const limit = pagingInfo.limit; + const reloadParams = state.groupedBy && state.groupedBy.length ? { + groupsLimit: limit, + groupsOffset: currentMinimum - 1, + } : { + limit, + offset: currentMinimum - 1, + }; + await this.reload(reloadParams); + // reset the scroll position to the top on page changed only + if (state.limit === limit) { + this.trigger_up('scrollTo', { top: 0 }); + } + }, /** * Returns true iff the given barcode matches the given record (candidate). * diff --git a/addons/board/static/src/js/action_manager_board_action.js b/addons/board/static/src/js/action_manager_board_action.js index 3036ac0f328..05414572311 100644 --- a/addons/board/static/src/js/action_manager_board_action.js +++ b/addons/board/static/src/js/action_manager_board_action.js @@ -21,8 +21,8 @@ ActionManager.include({ if (action.res_model === 'board.board' && action.view_mode === 'form') { action.target = 'inline'; _.extend(action.flags, { + hasActionMenus: false, hasSearchView: false, - hasSidebar: false, headless: true, }); } diff --git a/addons/board/static/src/js/add_to_board_menu.js b/addons/board/static/src/js/add_to_board_menu.js index 57e98ca87d7..dd5158c6d50 100644 --- a/addons/board/static/src/js/add_to_board_menu.js +++ b/addons/board/static/src/js/add_to_board_menu.js @@ -1,211 +1,154 @@ odoo.define('board.AddToBoardMenu', function (require) { -"use strict"; + "use strict"; -var ActionManager = require('web.ActionManager'); -var Context = require('web.Context'); -var core = require('web.core'); -var Domain = require('web.Domain'); -var favorites_submenus_registry = require('web.favorites_submenus_registry'); -var pyUtils = require('web.py_utils'); -var Widget = require('web.Widget'); + const Context = require('web.Context'); + const Domain = require('web.Domain'); + const DropdownMenuItem = require('web.DropdownMenuItem'); + const FavoriteMenu = require('web.FavoriteMenu'); + const pyUtils = require('web.py_utils'); + const { sprintf } = require('web.utils'); + const { useAutofocus } = require('web.custom_hooks'); -var _t = core._t; -var QWeb = core.qweb; - -var AddToBoardMenu = Widget.extend({ - events: _.extend({}, Widget.prototype.events, { - 'click .o_add_to_board.o_menu_header': '_onMenuHeaderClick', - 'click .o_add_to_board_confirm_button': '_onAddToBoardConfirmButtonClick', - 'click .o_add_to_board_input': '_onAddToBoardInputClick', - 'keyup .o_add_to_board_input': '_onKeyUp', - }), - /** - * @override - * @param {Object} params - * @param {Object} params.action an ir.actions description - */ - init: function (parent, params) { - this._super(parent); - this.action = params.action; - this.isOpen = false; - }, - /** - * @override - */ - start: function () { - if (this.action.id && this.action.type === 'ir.actions.act_window') { - this._render(); - } - return this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + const { useState } = owl.hooks; /** - * Closes the menu and render it. + * 'Add to board' menu * + * Component consisiting of a toggle button, a text input and an 'Add' button. + * The first button is simply used to toggle the component and will determine + * whether the other elements should be rendered. + * The input will be given the name (or title) of the view that will be added. + * Finally, the last button will send the name as well as some of the action + * properties to the server to add the current view (and its context) to the + * user's dashboard. + * This component is only available in actions of type 'ir.actions.act_window'. + * @extends DropdownMenuItem */ - closeMenu: function () { - this.isOpen = false; - this._render(); - }, + class AddToBoardMenu extends DropdownMenuItem { + constructor() { + super(...arguments); - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- + this.interactive = true; + this.state = useState({ + name: this.env.action.name || "", + open: false, + }); - /** - * This is the main function for actually saving the dashboard. This method - * is supposed to call the route /board/add_to_dashboard with proper - * information. - * - * @private - * @returns {Promise} - */ - _addToBoard: function () { - var self = this; - var searchQuery; - // TO DO: for now the domains in query are evaluated. - // This should be changed I think. - this.trigger_up('get_search_query', { - callback: function (query) { - searchQuery = query; - } - }); - // TO DO: replace direct reference to action manager, controller, and currentAction in code below - - // AAB: trigger_up an event that will be intercepted by the controller, - // as soon as the controller is the parent of the control panel - var actionManager = this.findAncestor(function (ancestor) { - return ancestor instanceof ActionManager; - }); - var controller = actionManager.getCurrentController(); - - var context = new Context(this.action.context); - context.add(searchQuery.context); - context.add({ - group_by: searchQuery.groupBy, - orderedBy: searchQuery.orderedBy, - }); - - this.trigger_up('get_controller_query_params', { - callback: function (controllerQueryParams) { - var queryContext = controllerQueryParams.context; - var allContext = _.extend( - _.omit(controllerQueryParams, ['context']), - queryContext - ); - context.add(allContext); - } - }); - - var domain = new Domain(this.action.domain || []); - domain = Domain.prototype.normalizeArray(domain.toArray().concat(searchQuery.domain)); - - var evalutatedContext = pyUtils.eval('context', context); - for (var key in evalutatedContext) { - if (evalutatedContext.hasOwnProperty(key) && /^search_default_/.test(key)) { - delete evalutatedContext[key]; - } + useAutofocus(); } - evalutatedContext.dashboard_merge_domains_contexts = false; - var name = this.$input.val(); + //--------------------------------------------------------------------- + // Private + //--------------------------------------------------------------------- - this.closeMenu(); + /** + * This is the main function for actually saving the dashboard. This method + * is supposed to call the route /board/add_to_dashboard with proper + * information. + * @private + */ + async _addToBoard() { + const searchQuery = this.env.controlPanelModel.getQuery(); + const context = new Context(this.env.action.context); + context.add(searchQuery.context); + context.add({ + group_by: searchQuery.groupBy, + orderedBy: searchQuery.orderedBy, + }); + if (searchQuery.timeRanges && searchQuery.timeRanges.hasOwnProperty('fieldName')) { + const { fieldName: field, range, comparisonRange } = searchQuery.timeRanges; + context.add({ + time_ranges : { field, range, comparisonRange }, + }); + } + let controllerQueryParams; + this.env.controlPanelModel.trigger('get-controller-query-params', params => { + controllerQueryParams = params || {}; + }); + controllerQueryParams.context = controllerQueryParams.context || {}; + const queryContext = controllerQueryParams.context; + delete controllerQueryParams.context; + context.add(Object.assign(controllerQueryParams, queryContext)); - return self._rpc({ + const domainArray = new Domain(this.env.action.domain || []); + const domain = Domain.prototype.normalizeArray(domainArray.toArray().concat(searchQuery.domain)); + + const evalutatedContext = context.eval(); + for (const key in evalutatedContext) { + if (evalutatedContext.hasOwnProperty(key) && /^search_default_/.test(key)) { + delete evalutatedContext[key]; + } + } + evalutatedContext.dashboard_merge_domains_contexts = false; + + Object.assign(this.state, { + name: this.env.action.name || "", + open: false, + }); + + const result = await this.rpc({ route: '/board/add_to_dashboard', params: { - action_id: self.action.id || false, + action_id: this.env.action.id || false, context_to_save: evalutatedContext, domain: domain, - view_mode: controller.viewType, - name: name, + view_mode: this.env.view.type, + name: this.state.name, }, - }) - .then(function (r) { - if (r) { - self.do_notify( - _.str.sprintf(_t("'%s' added to dashboard"), name), - _t('Please refresh your browser for the changes to take effect.') - ); - } else { - self.do_warn(_t("Could not add filter to dashboard")); - } }); - }, - /** - * Renders and focuses the unique input if it is visible. - * - * @private - */ - _render: function () { - var $el = QWeb.render('AddToBoardMenu', {widget: this}); - this._replaceElement($el); - if (this.isOpen) { - this.$input = this.$('.o_add_to_board_input'); - this.$input.val(this.action.name); - this.$input.focus(); + if (result) { + this.env.services.notification.notify({ + title: sprintf(this.env._t("'%s' added to dashboard"), this.state.name), + message: this.env._t("Please refresh your browser for the changes to take effect."), + type: 'warning', + }); + } else { + this.env.services.notification.notify({ + message: this.env._t("Could not add filter to dashboard"), + type: 'danger', + }); + } } - }, - /** - * Hides and displays the submenu which allows adding custom filters. - * - * @private - */ - _toggleMenu: function () { - this.isOpen = !this.isOpen; - this._render(); - }, - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- + //--------------------------------------------------------------------- + // Handlers + //--------------------------------------------------------------------- - /** - * @private - * @param {jQueryEvent} event - */ - _onAddToBoardInputClick: function (event) { - event.preventDefault(); - event.stopPropagation(); - this.$input.focus(); - }, - /** - * @private - * @param {jQueryEvent} event - */ - _onAddToBoardConfirmButtonClick: function (event) { - event.preventDefault(); - event.stopPropagation(); - this._addToBoard(); - }, - /** - * @private - * @param {jQueryEvent} event - */ - _onKeyUp: function (event) { - if (event.which === $.ui.keyCode.ENTER) { - this._addToBoard(); + /** + * @private + * @param {KeyboardEvent} ev + */ + _onInputKeydown(ev) { + switch (ev.key) { + case 'Enter': + ev.preventDefault(); + this._addToBoard(); + break; + case 'Escape': + // Gives the focus back to the component. + ev.preventDefault(); + ev.target.blur(); + break; + } } - }, - /** - * @private - * @param {jQueryEvent} event - */ - _onMenuHeaderClick: function (event) { - event.preventDefault(); - event.stopPropagation(); - this._toggleMenu(); - }, - -}); - -favorites_submenus_registry.add('add_to_board_menu', AddToBoardMenu, 10); - -return AddToBoardMenu; + //--------------------------------------------------------------------- + // Static + //--------------------------------------------------------------------- + + /** + * @param {Object} env + * @returns {boolean} + */ + static shouldBeDisplayed(env) { + return env.action.type === 'ir.actions.act_window'; + } + } + + AddToBoardMenu.props = {}; + AddToBoardMenu.template = 'AddToBoardMenu'; + + FavoriteMenu.registry.add('add-to-board-menu', AddToBoardMenu, 10); + + return AddToBoardMenu; }); diff --git a/addons/board/static/src/js/board_view.js b/addons/board/static/src/js/board_view.js index ffdcfbeb7ed..fab64783073 100644 --- a/addons/board/static/src/js/board_view.js +++ b/addons/board/static/src/js/board_view.js @@ -42,10 +42,7 @@ var BoardController = FormController.extend({ * @override */ getTitle: function () { - if (this.inDashboard) { - return _t("My Dashboard"); - } - return this._super.apply(this, arguments); + return _t("My Dashboard"); }, //-------------------------------------------------------------------------- @@ -93,12 +90,6 @@ var BoardController = FormController.extend({ }); dialog.open(); }, - /** - * @private - */ - _onEnableDashboard: function () { - this.inDashboard = true; - }, /** * We need to intercept switch_view event coming from sub views, because we * don't actually want to switch view in dashboard, we want to do a @@ -255,6 +246,7 @@ var BoardRenderer = FormRenderer.extend({ var rawContext = new Context(action.context, evalContext, {lang: session.user_context.lang}); var context = pyUtils.eval('context', rawContext, evalContext); var domain = params.domain || pyUtils.eval('domain', action.domain || '[]', action.context); + action.context = context; action.domain = domain; var viewType = params.viewType || action.views[0][1]; @@ -265,17 +257,32 @@ var BoardRenderer = FormRenderer.extend({ .then(function (viewsInfo) { var viewInfo = viewsInfo[viewType]; var View = viewRegistry.get(viewType); + + const searchQuery = { + context: context, + domain: domain, + groupBy: typeof context.group_by === 'string' && context.group_by ? + [context.group_by] : + context.group_by || [], + orderedBy: context.orderedBy || [], + }; + + if (['pivot', 'dashboard', 'graph', 'cohort'].includes(viewType)) { + if (context.time_ranges) { + const { field: fieldName, range, comparisonRange } = context.time_ranges; + searchQuery.timeRanges = { fieldName, range, comparisonRange }; + } else { + searchQuery.timeRanges = {}; + } + } + var view = new View(viewInfo, { action: action, hasSelectors: false, modelName: action.res_model, - searchQuery: { - context: context, - domain: domain, - groupBy: typeof context.group_by === 'string' && context.group_by ? [context.group_by] : context.group_by || [], - orderedBy: context.orderedBy || [], - }, + searchQuery, withControlPanel: false, + withSearchPanel: false, }); return view.getController(self).then(function (controller) { self._boardFormViewIDs[controller.handle] = _.first( @@ -300,7 +307,6 @@ var BoardRenderer = FormRenderer.extend({ // this function has a side effect. This is ok because we assume that // once we have a '' tag, we are in a special dashboard mode. this.$el.addClass('o_dashboard'); - this.trigger_up('enable_dashboard'); var hasAction = _.detect(node.children, function (column) { return _.detect(column.children,function (element){ diff --git a/addons/board/static/src/scss/dashboard.scss b/addons/board/static/src/scss/dashboard.scss index 518849cde0a..69d1511761b 100644 --- a/addons/board/static/src/scss/dashboard.scss +++ b/addons/board/static/src/scss/dashboard.scss @@ -151,7 +151,8 @@ // Favorites menu in control panel .o_add_to_dashboard { display: none; // hidden by default - @include o-search-options-dropdown-custom-item; + max-width: 250px; + width: auto; } @include media-breakpoint-down(sm) { diff --git a/addons/board/static/src/xml/board.xml b/addons/board/static/src/xml/board.xml index bd2ec9fe605..c81892aca07 100644 --- a/addons/board/static/src/xml/board.xml +++ b/addons/board/static/src/xml/board.xml @@ -80,15 +80,27 @@