Skip to content

Commit

Permalink
[REF] web: move ControlPanel inside controllers/actions
Browse files Browse the repository at this point in the history
This branch introduces a large-scale reorganization of the
component tree generated by the web client.  The short version is
that now, the control panel is a child of the view controller and
no longer a sibling. Graphically (and simplified), we go from
this:

                  webClient
                 /    |    \
               ...   ...  actionManager
                           /         \
                   controlPanel   viewController
                                    /       \

to this:

           	  webClient
                 /    |    \
               ...   ...  actionManager
                               |
                          viewController
                            /   |   \
          ControlPanelController
              /           \
         CPRenderer     CPModel

The motivation is that this work moves the code where it should be.
Before this commit, it was kind of weird to have code in the
controllers to render buttons outside of their root node (in
renderButtons).  Also, the action manager had to take care of
coordinating search view states between view transitions.

So, this work simplifies the code.  It also makes it easier to
extend. We see day after day that Odoo needs to take care of more
complex UI needs, and in many cases, these needs were quite
difficult to implement (we prefer spaghettis in our plates, not in
our code).  The changes in this branch should open the way to
implement these features.

For example,
- it will now be easy to add the possibility of views (for example,
  the search view or a new ControlPanel view) to add custom buttons
  (of type action or object) in the control panel.
- Another need will be to serialize/ restore the state of the
  search view across action boundaries (needed by the dashboard).
- Another example is the possibility for views to customize easily
  the presence/absence of sub menus (filters/groupbys/favorites/
  time range/...)
- Another need is an easier way for views/client action to customize
  their control panel.

This commit also contains a large rewrite of the search view (so it
is more inline with our architecture and easier to maintain).

Part of task 1893568

Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
Co-authored-by: Martin Geubelle <mge@odoo.com>

--
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr

closes #29106
  • Loading branch information
robodoo committed Dec 7, 2018
2 parents f14092c + 1e0b370 commit 4cd379c
Show file tree
Hide file tree
Showing 147 changed files with 7,917 additions and 6,863 deletions.
Expand Up @@ -4,18 +4,16 @@ odoo.define('account.ReconciliationClientAction', function (require) {
var AbstractAction = require('web.AbstractAction');
var ReconciliationModel = require('account.ReconciliationModel');
var ReconciliationRenderer = require('account.ReconciliationRenderer');
var ControlPanelMixin = require('web.ControlPanelMixin');
var Widget = require('web.Widget');
var core = require('web.core');
var _t = core._t;


/**
* Widget used as action for 'account.bank.statement' reconciliation
*/
var StatementAction = AbstractAction.extend(ControlPanelMixin, {
var StatementAction = AbstractAction.extend({
hasControlPanel: true,
title: core._t('Bank Reconciliation'),
template: 'reconciliation',
contentTemplate: 'reconciliation',
custom_events: {
change_mode: '_onAction',
change_filter: '_onAction',
Expand All @@ -34,7 +32,7 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, {
load_more: '_onLoadMore',
reload: 'reload',
},
config: {
config: _.extend({}, AbstractAction.prototype.config, {
// used to instantiate the model
Model: ReconciliationModel.StatementModel,
// used to instantiate the action interface
Expand All @@ -47,7 +45,7 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, {
defaultDisplayQty: 10,
// number of moves lines displayed in 'match' mode
limitMoveLines: 15,
},
}),

/**
* @override
Expand All @@ -65,9 +63,6 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, {
limitMoveLines: params.params && params.params.limitMoveLines || this.config.limitMoveLines,
});
this.widgets = [];
if (!this.action_manager) {
this.set_cp_bus(new Widget());
}
// Adding values from the context is necessary to put this information in the url via the action manager so that
// you can retrieve it if the person shares his url or presses f5
_.each(params.params, function (value, name) {
Expand All @@ -90,13 +85,14 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, {
var self = this;
var def = this.model.load(this.params.context).then(this._super.bind(this));
return def.then(function () {
self.title = self.model.bank_statement_id ? self.model.bank_statement_id.display_name : self.title;
var title = self.model.bank_statement_id && self.model.bank_statement_id.display_name;
self._setTitle(title);
self.renderer = new self.config.ActionRenderer(self, self.model, {
'bank_statement_id': self.model.bank_statement_id,
'valuenow': self.model.valuenow,
'valuemax': self.model.valuemax,
'defaultDisplayQty': self.model.defaultDisplayQty,
'title': self.title,
'title': title,
});
});
},
Expand Down Expand Up @@ -124,9 +120,6 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, {
start: function () {
var self = this;

this.set("title", this.title);
this.update_control_panel({search_view_hidden: true}, {clear: true});

this.renderer.prependTo(self.$('.o_form_sheet'));
this._renderLines();

Expand All @@ -141,6 +134,8 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, {
this.renderer._renderNotifications(this.model.statement.notifications);
this._openFirstLine();
}

return this._super.apply(this, arguments);
},

/**
Expand All @@ -151,7 +146,7 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, {
do_show: function () {
this._super.apply(this, arguments);
if (this.action_manager) {
this.update_control_panel({search_view_hidden: true}, {clear: true});
this.updateControlPanel({clear: true});
this.action_manager.do_push_state({
action: this.params.tag,
active_id: this.params.res_id,
Expand Down Expand Up @@ -354,14 +349,14 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, {
*/
var ManualAction = StatementAction.extend({
title: core._t('Journal Items to Reconcile'),
config: {
config: _.extend({}, StatementAction.prototype.config, {
Model: ReconciliationModel.ManualModel,
ActionRenderer: ReconciliationRenderer.ManualRenderer,
LineRenderer: ReconciliationRenderer.ManualLineRenderer,
params: ['company_ids', 'mode', 'partner_ids', 'account_ids'],
defaultDisplayQty: 30,
limitMoveLines: 15,
},
}),

//--------------------------------------------------------------------------
// Handlers
Expand Down
2 changes: 1 addition & 1 deletion addons/account/static/tests/reconciliation_tests.js
Expand Up @@ -700,7 +700,7 @@ QUnit.module('account', {
assert.ok(clientAction.widgets[1].$('.match div.load-more a:visible').length, "should display the 'load more' button");
assert.equal(clientAction.widgets[1].$('.match div.load-more span').text(), 3, "should display 3 items remaining");
clientAction.widgets[1].$('.match div.load-more a').trigger('click');
assert.containsN(clientAction.widgets[1], '.mv_line', 8, "should load 3 more records"),
assert.containsN(clientAction.widgets[1], '.mv_line', 8, "should load 3 more records");
assert.notOk(clientAction.widgets[1].$('.match div.load-more a:visible').length, "should not display the 'load more' button anymore");

assert.ok(clientAction.widgets[0].$('caption button.btn-secondary:visible').length, "should display the 'validate' button");
Expand Down
15 changes: 8 additions & 7 deletions addons/base_import/static/src/js/import_action.js
Expand Up @@ -2,7 +2,6 @@ odoo.define('base_import.import', function (require) {
"use strict";

var AbstractAction = require('web.AbstractAction');
var ControlPanelMixin = require('web.ControlPanelMixin');
var core = require('web.core');
var session = require('web.session');
var time = require('web.time');
Expand Down Expand Up @@ -72,8 +71,9 @@ function dataFilteredQuery(q) {
q.callback({results: suggestions});
}

var DataImport = AbstractAction.extend(ControlPanelMixin, {
template: 'ImportView',
var DataImport = AbstractAction.extend({
hasControlPanel: true,
contentTemplate: 'ImportView',
opts: [
{name: 'encoding', label: _lt("Encoding:"), value: ''},
{name: 'separator', label: _lt("Separator:"), value: ''},
Expand Down Expand Up @@ -137,21 +137,22 @@ var DataImport = AbstractAction.extend(ControlPanelMixin, {
// import object id
this.id = null;
this.session = session;
action.display_name = _t('Import a File'); // Displayed in the breadcrumbs
this._title = _t('Import a File'); // Displayed in the breadcrumbs
this.do_not_change_match = false;
},
/**
* @override
*/
willStart: function () {
var self = this;
return this._rpc({
var def = this._rpc({
model: this.res_model,
method: 'get_import_templates',
context: this.parent_context,
}).then(function (result) {
self.importTemplates = result;
});
return $.when(this._super.apply(this, arguments), def);
},
start: function () {
var self = this;
Expand All @@ -170,7 +171,7 @@ var DataImport = AbstractAction.extend(ControlPanelMixin, {
var status = {
cp_content: {$buttons: self.$buttons},
};
self.update_control_panel(status);
self.updateControlPanel(status);
})
);
},
Expand All @@ -189,7 +190,7 @@ var DataImport = AbstractAction.extend(ControlPanelMixin, {
this.$buttons.filter('.o_import_import').on('click', this.import.bind(this));
this.$buttons.filter('.o_import_file_reload').on('click', this.loaded_file.bind(this));
this.$buttons.filter('.oe_import_file').on('click', function () {
self.$('.oe_import_file').click();
self.$('.o_content .oe_import_file').click();
});
this.$buttons.filter('.o_import_cancel').on('click', function(e) {
e.preventDefault();
Expand Down
205 changes: 205 additions & 0 deletions addons/board/static/src/js/add_to_board_menu.js
@@ -0,0 +1,205 @@
odoo.define('board.AddToBoardMenu', function (require) {
"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');

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
//--------------------------------------------------------------------------

/**
* Closes the menu and render it.
*
*/
closeMenu: function () {
this.isOpen = false;
this._render();
},

//--------------------------------------------------------------------------
// 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
* @returns {Deferred}
*/
_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: pyUtils.eval('groupbys', searchQuery.groupBys || [])
});

this.trigger_up('get_controller_query_params', {
callback: function (controllerContext) {
context.add(controllerContext);
}
});

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];
}
}
evalutatedContext.dashboard_merge_domains_contexts = false;

var name = this.$input.val();

this.closeMenu();

return self._rpc({
route: '/board/add_to_dashboard',
params: {
action_id: self.action.id || false,
context_to_save: evalutatedContext,
domain: domain,
view_mode: controller.viewType,
name: 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();
}
},
/**
* Hides and displays the submenu which allows adding custom filters.
*
* @private
*/
_toggleMenu: function () {
this.isOpen = !this.isOpen;
this._render();
},

//--------------------------------------------------------------------------
// 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 {jQueryEvent} event
*/
_onMenuHeaderClick: function (event) {
event.preventDefault();
event.stopPropagation();
this._toggleMenu();
},

});

favorites_submenus_registry.add('add_to_board_menu', AddToBoardMenu, 10);

return AddToBoardMenu;

});

0 comments on commit 4cd379c

Please sign in to comment.