diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/board/static/src/js | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/board/static/src/js')
| -rw-r--r-- | addons/board/static/src/js/action_manager_board_action.js | 33 | ||||
| -rw-r--r-- | addons/board/static/src/js/add_to_board_menu.js | 152 | ||||
| -rw-r--r-- | addons/board/static/src/js/board_view.js | 465 |
3 files changed, 650 insertions, 0 deletions
diff --git a/addons/board/static/src/js/action_manager_board_action.js b/addons/board/static/src/js/action_manager_board_action.js new file mode 100644 index 00000000..05414572 --- /dev/null +++ b/addons/board/static/src/js/action_manager_board_action.js @@ -0,0 +1,33 @@ +odoo.define('board.ActionManager', function (require) { +"use strict"; + +/** + * The purpose of this file is to patch the ActionManager to properly generate + * the flags for the 'ir.actions.act_window' of model 'board.board'. + */ + +var ActionManager = require('web.ActionManager'); + +ActionManager.include({ + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + * @private + */ + _executeWindowAction: function (action) { + if (action.res_model === 'board.board' && action.view_mode === 'form') { + action.target = 'inline'; + _.extend(action.flags, { + hasActionMenus: false, + hasSearchView: false, + headless: true, + }); + } + return this._super.apply(this, arguments); + }, +}); + +}); diff --git a/addons/board/static/src/js/add_to_board_menu.js b/addons/board/static/src/js/add_to_board_menu.js new file mode 100644 index 00000000..42a4a707 --- /dev/null +++ b/addons/board/static/src/js/add_to_board_menu.js @@ -0,0 +1,152 @@ +odoo.define('board.AddToBoardMenu', function (require) { + "use strict"; + + const Context = require('web.Context'); + const Domain = require('web.Domain'); + const DropdownMenuItem = require('web.DropdownMenuItem'); + const FavoriteMenu = require('web.FavoriteMenu'); + const { sprintf } = require('web.utils'); + const { useAutofocus } = require('web.custom_hooks'); + + const { useState } = owl.hooks; + + /** + * '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 + */ + class AddToBoardMenu extends DropdownMenuItem { + constructor() { + super(...arguments); + + this.interactive = true; + this.state = useState({ + name: this.env.action.name || "", + open: false, + }); + + useAutofocus(); + } + + //--------------------------------------------------------------------- + // Private + //--------------------------------------------------------------------- + + /** + * 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.searchModel.get('query'); + 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')) { + context.add({ + comparison: searchQuery.timeRanges, + }); + } + let controllerQueryParams; + this.env.searchModel.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)); + + 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: $(".o_input").val() || "", + open: false, + }); + + const result = await this.rpc({ + route: '/board/add_to_dashboard', + params: { + action_id: this.env.action.id || false, + context_to_save: evalutatedContext, + domain: domain, + view_mode: this.env.view.type, + name: this.state.name, + }, + }); + 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', + }); + } + } + + //--------------------------------------------------------------------- + // Handlers + //--------------------------------------------------------------------- + + /** + * @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; + } + } + + //--------------------------------------------------------------------- + // 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 new file mode 100644 index 00000000..87b5034f --- /dev/null +++ b/addons/board/static/src/js/board_view.js @@ -0,0 +1,465 @@ +odoo.define('board.BoardView', function (require) { +"use strict"; + +var Context = require('web.Context'); +var config = require('web.config'); +var core = require('web.core'); +var dataManager = require('web.data_manager'); +var Dialog = require('web.Dialog'); +var Domain = require('web.Domain'); +var FormController = require('web.FormController'); +var FormRenderer = require('web.FormRenderer'); +var FormView = require('web.FormView'); +var pyUtils = require('web.py_utils'); +var session = require('web.session'); +var viewRegistry = require('web.view_registry'); + +var _t = core._t; +var _lt = core._lt; +var QWeb = core.qweb; + +var BoardController = FormController.extend({ + custom_events: _.extend({}, FormController.prototype.custom_events, { + change_layout: '_onChangeLayout', + enable_dashboard: '_onEnableDashboard', + save_dashboard: '_saveDashboard', + switch_view: '_onSwitchView', + }), + + /** + * @override + */ + init: function (parent, model, renderer, params) { + this._super.apply(this, arguments); + this.customViewID = params.customViewID; + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + getTitle: function () { + return _t("My Dashboard"); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Actually save a dashboard + * + * @returns {Promise} + */ + _saveDashboard: function () { + var board = this.renderer.getBoard(); + var arch = QWeb.render('DashBoard.xml', _.extend({}, board)); + return this._rpc({ + route: '/web/view/edit_custom', + params: { + custom_id: this.customViewID, + arch: arch, + } + }).then(dataManager.invalidate.bind(dataManager)); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {OdooEvent} event + */ + _onChangeLayout: function (event) { + var self = this; + var dialog = new Dialog(this, { + title: _t("Edit Layout"), + $content: QWeb.render('DashBoard.layouts', _.clone(event.data)) + }); + dialog.opened().then(function () { + dialog.$('li').click(function () { + var layout = $(this).attr('data-layout'); + self.renderer.changeLayout(layout); + self._saveDashboard(); + dialog.close(); + }); + }); + dialog.open(); + }, + /** + * 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 + * do_action (which will open the record in a different breadcrumb). + * + * @private + * @param {OdooEvent} event + */ + _onSwitchView: function (event) { + event.stopPropagation(); + this.do_action({ + type: 'ir.actions.act_window', + res_model: event.data.model, + views: [[event.data.formViewID || false, 'form']], + res_id: event.data.res_id, + }); + }, +}); + +var BoardRenderer = FormRenderer.extend({ + custom_events: _.extend({}, FormRenderer.prototype.custom_events, { + update_filters: '_onUpdateFilters', + switch_view: '_onSwitchView', + }), + events: _.extend({}, FormRenderer.prototype.events, { + 'click .oe_dashboard_column .oe_fold': '_onFoldClick', + 'click .oe_dashboard_link_change_layout': '_onChangeLayout', + 'click .oe_dashboard_column .oe_close': '_onCloseAction', + }), + + /** + * @override + */ + init: function (parent, state, params) { + this._super.apply(this, arguments); + this.noContentHelp = params.noContentHelp; + this.actionsDescr = {}; + this._boardSubcontrollers = []; // for board: controllers of subviews + this._boardFormViewIDs = {}; // for board: mapping subview controller to form view id + }, + /** + * Call `on_attach_callback` for each subview + * + * @override + */ + on_attach_callback: function () { + _.each(this._boardSubcontrollers, function (controller) { + if ('on_attach_callback' in controller) { + controller.on_attach_callback(); + } + }); + }, + /** + * Call `on_detach_callback` for each subview + * + * @override + */ + on_detach_callback: function () { + _.each(this._boardSubcontrollers, function (controller) { + if ('on_detach_callback' in controller) { + controller.on_detach_callback(); + } + }); + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @param {string} layout + */ + changeLayout: function (layout) { + var $dashboard = this.$('.oe_dashboard'); + var current_layout = $dashboard.attr('data-layout'); + if (current_layout !== layout) { + var clayout = current_layout.split('-').length, + nlayout = layout.split('-').length, + column_diff = clayout - nlayout; + if (column_diff > 0) { + var $last_column = $(); + $dashboard.find('.oe_dashboard_column').each(function (k, v) { + if (k >= nlayout) { + $(v).find('.oe_action').appendTo($last_column); + } else { + $last_column = $(v); + } + }); + } + $dashboard.toggleClass('oe_dashboard_layout_' + current_layout + ' oe_dashboard_layout_' + layout); + $dashboard.attr('data-layout', layout); + } + }, + /** + * Returns a representation of the current dashboard + * + * @returns {Object} + */ + getBoard: function () { + var self = this; + var board = { + form_title : this.arch.attrs.string, + style : this.$('.oe_dashboard').attr('data-layout'), + columns : [], + }; + this.$('.oe_dashboard_column').each(function () { + var actions = []; + $(this).find('.oe_action').each(function () { + var actionID = $(this).attr('data-id'); + var newAttrs = _.clone(self.actionsDescr[actionID]); + + /* prepare attributes as they should be saved */ + if (newAttrs.modifiers) { + newAttrs.modifiers = JSON.stringify(newAttrs.modifiers); + } + actions.push(newAttrs); + }); + board.columns.push(actions); + }); + return board; + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + * @param {Object} params + * @param {jQueryElement} params.$node + * @param {integer} params.actionID + * @param {Object} params.context + * @param {any[]} params.domain + * @param {string} params.viewType + * @returns {Promise} + */ + _createController: function (params) { + var self = this; + return this._rpc({ + route: '/web/action/load', + params: {action_id: params.actionID} + }) + .then(function (action) { + if (!action) { + // the action does not exist anymore + return Promise.resolve(); + } + var evalContext = new Context(params.context).eval(); + if (evalContext.group_by && evalContext.group_by.length === 0) { + delete evalContext.group_by; + } + // tz and lang are saved in the custom view + // override the language to take the current one + 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; + + // When creating a view, `action.views` is expected to be an array of dicts, while + // '/web/action/load' returns an array of arrays. + action._views = action.views; + action.views = $.map(action.views, function (view) { return {viewID: view[0], type: view[1]}}); + + var viewType = params.viewType || action._views[0][1]; + var view = _.find(action._views, function (descr) { + return descr[1] === viewType; + }) || [false, viewType]; + return self.loadViews(action.res_model, context, [view]) + .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 (View.prototype.searchMenuTypes.includes('comparison')) { + searchQuery.timeRanges = context.comparison || {}; + } + + var view = new View(viewInfo, { + action: action, + hasSelectors: false, + modelName: action.res_model, + searchQuery, + withControlPanel: false, + withSearchPanel: false, + }); + return view.getController(self).then(function (controller) { + self._boardFormViewIDs[controller.handle] = _.first( + _.find(action._views, function (descr) { + return descr[1] === 'form'; + }) + ); + self._boardSubcontrollers.push(controller); + return controller.appendTo(params.$node); + }); + }); + }); + }, + /** + * @private + * @param {Object} node + * @returns {jQueryElement} + */ + _renderTagBoard: function (node) { + var self = this; + // we add the o_dashboard class to the renderer's $el. This means that + // this function has a side effect. This is ok because we assume that + // once we have a '<board>' tag, we are in a special dashboard mode. + this.$el.addClass('o_dashboard'); + + var hasAction = _.detect(node.children, function (column) { + return _.detect(column.children,function (element){ + return element.tag === "action"? element: false; + }); + }); + if (!hasAction) { + return $(QWeb.render('DashBoard.NoContent')); + } + + // We should start with three columns available + node = $.extend(true, {}, node); + + // no idea why master works without this, but whatever + if (!('layout' in node.attrs)) { + node.attrs.layout = node.attrs.style; + } + for (var i = node.children.length; i < 3; i++) { + node.children.push({ + tag: 'column', + attrs: {}, + children: [] + }); + } + + // register actions, alongside a generated unique ID + _.each(node.children, function (column, column_index) { + _.each(column.children, function (action, action_index) { + action.attrs.id = 'action_' + column_index + '_' + action_index; + self.actionsDescr[action.attrs.id] = action.attrs; + }); + }); + + var $html = $('<div>').append($(QWeb.render('DashBoard', {node: node, isMobile: config.device.isMobile}))); + this._boardSubcontrollers = []; // dashboard controllers are reset on re-render + + // render each view + _.each(this.actionsDescr, function (action) { + self.defs.push(self._createController({ + $node: $html.find('.oe_action[data-id=' + action.id + '] .oe_content'), + actionID: _.str.toNumber(action.name), + context: action.context, + domain: Domain.prototype.stringToArray(action.domain, {}), + viewType: action.view_mode, + })); + }); + $html.find('.oe_dashboard_column').sortable({ + connectWith: '.oe_dashboard_column', + handle: '.oe_header', + scroll: false + }).bind('sortstop', function () { + self.trigger_up('save_dashboard'); + }); + + return $html; + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onChangeLayout: function () { + var currentLayout = this.$('.oe_dashboard').attr('data-layout'); + this.trigger_up('change_layout', {currentLayout: currentLayout}); + }, + /** + * @private + * @param {MouseEvent} event + */ + _onCloseAction: function (event) { + var self = this; + var $container = $(event.currentTarget).parents('.oe_action:first'); + Dialog.confirm(this, (_t("Are you sure you want to remove this item?")), { + confirm_callback: function () { + $container.remove(); + self.trigger_up('save_dashboard'); + }, + }); + }, + /** + * @private + * @param {MouseEvent} event + */ + _onFoldClick: function (event) { + var $e = $(event.currentTarget); + var $action = $e.closest('.oe_action'); + var id = $action.data('id'); + var actionAttrs = this.actionsDescr[id]; + + if ($e.is('.oe_minimize')) { + actionAttrs.fold = '1'; + } else { + delete(actionAttrs.fold); + } + $e.toggleClass('oe_minimize oe_maximize'); + $action.find('.oe_content').toggle(); + this.trigger_up('save_dashboard'); + }, + /** + * Let FormController know which form view it should display based on the + * window action of the sub controller that is switching view + * + * @private + * @param {OdooEvent} event + */ + _onSwitchView: function (event) { + event.data.formViewID = this._boardFormViewIDs[event.target.handle]; + }, + /** + * Stops the propagation of 'update_filters' events triggered by the + * controllers instantiated by the dashboard to prevent them from + * interfering with the ActionManager. + * + * @private + * @param {OdooEvent} event + */ + _onUpdateFilters: function (event) { + event.stopPropagation(); + }, +}); + +var BoardView = FormView.extend({ + config: _.extend({}, FormView.prototype.config, { + Controller: BoardController, + Renderer: BoardRenderer, + }), + display_name: _lt('Board'), + + /** + * @override + */ + init: function (viewInfo) { + this._super.apply(this, arguments); + this.controllerParams.customViewID = viewInfo.custom_view_id; + }, +}); + +return BoardView; + +}); + + +odoo.define('board.viewRegistry', function (require) { +"use strict"; + +var BoardView = require('board.BoardView'); + +var viewRegistry = require('web.view_registry'); + +viewRegistry.add('board', BoardView); + +}); |
