summaryrefslogtreecommitdiff
path: root/addons/board/static/src/js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/board/static/src/js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/board/static/src/js')
-rw-r--r--addons/board/static/src/js/action_manager_board_action.js33
-rw-r--r--addons/board/static/src/js/add_to_board_menu.js152
-rw-r--r--addons/board/static/src/js/board_view.js465
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);
+
+});