[REF] board: adapt code to search refactoring

This rev. adapts the code of the 'Add to Dashboard' entry of the
Favotires menu, according to the large SearchView refactoring. It
also does some tweaks for the dashboard app due to the move of the
ControlPanel from the ActionManager to the controllers.

Part of task 1893568
This commit is contained in:
Mathieu Duckerts-Antoine
2018-12-07 13:16:10 +00:00
committed by Géry Debongnie
parent 84a1d7bd59
commit 38dc5c18b4
7 changed files with 283 additions and 251 deletions
@@ -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;
});
+9 -28
View File
@@ -120,7 +120,6 @@ var BoardController = FormController.extend({
var BoardRenderer = FormRenderer.extend({
custom_events: _.extend({}, FormRenderer.prototype.custom_events, {
do_action: '_onDoAction',
env_updated: '_onEnvUpdated',
update_filters: '_onUpdateFilters',
switch_view: '_onSwitchView',
}),
@@ -256,6 +255,8 @@ 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];
var view = _.find(action.views, function (descr) {
return descr[1] === viewType;
@@ -266,11 +267,14 @@ var BoardRenderer = FormRenderer.extend({
var View = viewRegistry.get(viewType);
var view = new View(viewInfo, {
action: action,
context: context,
domain: domain,
groupBy: context.group_by || [],
modelName: action.res_model,
hasSelectors: false,
modelName: action.res_model,
searchQuery: {
context: context,
domain: domain,
groupBy: context.group_by || [],
},
withControlPanel: false,
});
return view.getController(self).then(function (controller) {
self._boardFormViewIDs[controller.handle] = _.first(
@@ -377,29 +381,6 @@ var BoardRenderer = FormRenderer.extend({
},
});
},
/**
* Intercepts (without stopping) 'do_action' events to force the
* 'keepSearchView' option to false, as the dashboard action has no search
* view, and thus there is no search view that could be re-used for the
* action to execute (a new one will be created instead).
*
* @private
* @param {OdooEvent} event
*/
_onDoAction: function (event) {
if (event.data.options) {
event.data.options.keepSearchView = false;
}
},
/**
* Stops the propagation of 'env_updated' events triggered by the controllers
* instantiated by the dashboard.
*
* @private
*/
_onEnvUpdated: function (event) {
event.stopPropagation();
},
/**
* @private
* @param {MouseEvent} event
-136
View File
@@ -1,136 +0,0 @@
odoo.define('board.favorite_menu', 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 FavoriteMenu = require('web.FavoriteMenu');
var pyUtils = require('web.py_utils');
var _t = core._t;
var QWeb = core.qweb;
FavoriteMenu.include({
/**
* We manually add the 'add to dashboard' feature in the searchview.
*
* @override
*/
start: function () {
var self = this;
if(this.action_id === undefined) {
return this._super();
}
if (this.action.type === 'ir.actions.act_window') {
this.add_to_dashboard_available = true;
this.$('.o_favorites_menu').append(QWeb.render('SearchView.addtodashboard'));
this.$add_to_dashboard = this.$('.o_add_to_dashboard');
this.$add_dashboard_btn = this.$add_to_dashboard.eq(1).find('button');
this.$add_dashboard_input = this.$add_to_dashboard.eq(0).find('input');
this.$add_dashboard_link = this.$('.o_add_to_dashboard_link');
var title = this.searchview.get_title();
this.$add_dashboard_input.val(title);
this.$add_dashboard_link.click(function (e) {
e.preventDefault();
self._toggleDashboardMenu();
});
this.$add_dashboard_btn.click(this.proxy('_addDashboard'));
}
return this._super();
},
//--------------------------------------------------------------------------
// 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}
*/
_addDashboard: function () {
var self = this;
var search_data = this.searchview.build_search_data();
var context = new Context(this.searchview.dataset.get_context() || []);
var domain = [];
_.each(search_data.contexts, context.add, context);
_.each(search_data.domains, function (d) {
domain.push.apply(domain, Domain.prototype.stringToArray(d));
});
context.add({
group_by: pyUtils.eval('groupbys', search_data.groupbys || [])
});
// 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 am = this.findAncestor(function (a) {
return a instanceof ActionManager;
});
// with options 'keepSearchView', it may happen that the action_id of
// the searchview (received in init) is not the one of the current
// action, which corresponds to the one we want to add to dashboard
var currentAction = am.getCurrentAction();
var controller = am.getCurrentController();
context.add(controller.widget.getContext());
var c = pyUtils.eval('context', context);
for (var k in c) {
if (c.hasOwnProperty(k) && /^search_default_/.test(k)) {
delete c[k];
}
}
this._toggleDashboardMenu(false);
c.dashboard_merge_domains_contexts = false;
var name = self.$add_dashboard_input.val();
return self._rpc({
route: '/board/add_to_dashboard',
params: {
action_id: currentAction.id || false,
context_to_save: c,
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"));
}
});
},
/**
* @override
* @private
*/
_closeMenus: function () {
if (this.add_to_dashboard_available) {
this._toggleDashboardMenu(false);
}
this._super();
},
/**
* @private
* @param {undefined|false} isOpen
*/
_toggleDashboardMenu: function (isOpen) {
this.$add_dashboard_link
.toggleClass('o_closed_menu', !(_.isUndefined(isOpen)) ? !isOpen : undefined)
.toggleClass('o_open_menu', isOpen);
this.$add_to_dashboard.toggle(isOpen);
if (this.$add_dashboard_link.hasClass('o_open_menu')) {
this.$add_dashboard_input.focus();
}
},
});
});
+1 -1
View File
@@ -123,7 +123,7 @@
}
// Override height of graph. min-height doesn't do the trick
.o_graph {
.o_graph_controller {
height: 500px;
}
}
+10
View File
@@ -74,6 +74,16 @@
<iframe width="100%" frameborder="0" t-att-src="url"/>
</t>
<div t-name="AddToBoardMenu">
<button type="button" class="dropdown-item o_add_to_board o_menu_header">Add to my Dashboard</button>
<div t-if="widget.isOpen" class="dropdown-item-text o_add_to_board">
<input class="o_input o_add_to_board_input" type="text"/>
</div>
<div t-if="widget.isOpen" class="dropdown-item-text o_add_to_board">
<button type="button" class="btn btn-primary o_add_to_board_confirm_button">Add</button>
</div>
</div>
<t t-name="SearchView.addtodashboard">
<a href="#" class="dropdown-item o_add_to_dashboard_link o_closed_menu">Add to my Dashboard</a>
<div class="dropdown-item-text o_add_to_dashboard">
+57 -85
View File
@@ -75,7 +75,7 @@ QUnit.test('dashboard basic rendering', function (assert) {
"with a dashboard, the renderer should have the proper css class");
assert.containsOnce(form, '.o_dashboard .o_view_nocontent',
"should have a no content helper");
assert.strictEqual(form.get('title'), "My Dashboard",
assert.strictEqual(form.$('.o_control_panel .breadcrumb-item').text(), "My Dashboard",
"should have the correct title");
form.destroy();
});
@@ -193,6 +193,41 @@ QUnit.test('basic functionality, with one sub action', function (assert) {
form.destroy();
});
QUnit.test('views in the dashboard do not have a control panel', function (assert) {
assert.expect(2);
var form = createView({
View: BoardView,
model: 'board',
data: this.data,
arch: '<form>' +
'<board style="2-1">' +
'<column>' +
'<action context="{}" view_mode="list" string="ABC" name="51" domain="[]"></action>' +
'</column>' +
'</board>' +
'</form>',
mockRPC: function (route) {
if (route === '/web/action/load') {
return $.when({
res_model: 'partner',
views: [[4, 'list'], [5, 'form']],
});
}
return this._super.apply(this, arguments);
},
archs: {
'partner,4,list':
'<tree string="Partner"><field name="foo"/></tree>',
},
});
assert.containsOnce(form, '.o_action .o_list_view');
assert.containsNone(form, '.o_action .o_control_panel');
form.destroy();
});
QUnit.test('can render an action without view_mode attribute', function (assert) {
// The view_mode attribute is automatically set to the 'action' nodes when
// the action is added to the dashboard using the 'Add to dashboard' button
@@ -605,15 +640,13 @@ QUnit.test('subviews are aware of attach in or detach from the DOM', function (a
});
QUnit.test('dashboard intercepts custom events triggered by sub controllers', function (assert) {
assert.expect(4);
assert.expect(1);
// we patch the ListController to force it to trigger the custom events that
// we want the dashboard to intercept (to stop them or to tweak their data)
testUtils.mock.patch(ListController, {
start: function () {
this.trigger_up('update_filters');
this.trigger_up('env_updated');
this.do_action({}, {keepSearchView: true});
},
});
@@ -638,29 +671,18 @@ QUnit.test('dashboard intercepts custom events triggered by sub controllers', fu
'partner,false,list': '<tree string="Partner"/>',
},
intercepts: {
do_action: function (ev) {
assert.strictEqual(ev.data.options.keepSearchView, false,
"the 'keepSearchView' options should have been set to false");
},
env_updated: function (ev) {
assert.strictEqual(ev.target.modelName, 'board',
"env_updated event should be triggered by the dashboard itself");
assert.step('env_updated');
},
update_filters: assert.step.bind(assert, 'update_filters'),
},
});
assert.verifySteps([
'env_updated', // triggered by the dashboard itself
]);
assert.verifySteps([]);
testUtils.mock.unpatch(ListController);
board.destroy();
});
QUnit.test('save actions to dashboard', function (assert) {
assert.expect(4);
assert.expect(3);
var actionManager = createActionManager({
data: this.data,
@@ -689,13 +711,12 @@ QUnit.test('save actions to dashboard', function (assert) {
assert.containsOnce(actionManager, '.o_list_view',
"should display the list view");
assert.strictEqual($('.o_add_to_dashboard_link').length, 1,
"should allow the 'Add to dashboard' feature");
// add this action to dashboard
testUtils.dom.click($('.o_search_options .o_dropdown button:contains(Favorites)'));
testUtils.dom.click($('.o_add_to_dashboard_link'));
testUtils.dom.click($('.o_add_to_dashboard_button'));
testUtils.dom.click($('.o_add_to_board.o_menu_header'));
testUtils.fields.editInput($('input.o_add_to_board_input'), 'a name');
testUtils.dom.click($('.o_add_to_board_confirm_button'));
actionManager.destroy();
});
@@ -737,75 +758,26 @@ QUnit.test('save two searches to dashboard', function (assert) {
var filter_count = 0;
// Add a first filter
$('span.fa-filter').click();
$('.o_add_custom_filter:visible').click();
$('.o_searchview_extended_prop_value .o_input').val('a')
$('.o_apply_filter').click();
testUtils.dom.click(actionManager.$('.o_filters_menu_button'));
testUtils.dom.click(actionManager.$('.o_add_custom_filter'));
actionManager.$('.o_searchview_extended_prop_value .o_input').val('a');
testUtils.dom.click(actionManager.$('.o_apply_filter'));
// Add it to dashboard
$('.o_add_to_dashboard_button').click();
testUtils.dom.click(actionManager.$('.o_favorites_menu_button'));
testUtils.dom.click(actionManager.$('.o_add_to_board'));
testUtils.dom.click(actionManager.$('.o_add_to_board_confirm_button'));
// Remove it
$('.o_facet_remove').click();
testUtils.dom.click(actionManager.$('.o_facet_remove'));
// Add the second filter
$('span.fa-filter').click();
$('span.fa-filter').click();
$('.o_add_custom_filter:visible').click();
$('.o_searchview_extended_prop_value .o_input').val('b')
$('.o_apply_filter').click();
testUtils.dom.click(actionManager.$('.o_filters_menu_button'));
testUtils.dom.click(actionManager.$('.o_add_custom_filter'));
actionManager.$('.o_searchview_extended_prop_value .o_input').val('b');
testUtils.dom.click(actionManager.$('.o_apply_filter'));
// Add it to dashboard
$('.o_add_to_dashboard_button').click();
actionManager.destroy();
});
QUnit.test('save to dashboard actions with flag keepSearchView', function (assert) {
assert.expect(4);
var actionManager = createActionManager({
data: this.data,
archs: {
'partner,false,graph': '<graph><field name="foo"/></graph>',
'partner,false,list': '<list><field name="foo"/></list>',
'partner,false,search': '<search></search>',
},
mockRPC: function (route, args) {
if (route === '/board/add_to_dashboard') {
assert.strictEqual(args.action_id, 2,
"should save the correct action");
assert.strictEqual(args.view_mode, 'graph',
"should save the correct view type");
return $.when(true);
}
return this._super.apply(this, arguments);
},
});
// execute a first action
actionManager.doAction({
id: 1,
res_model: 'partner',
type: 'ir.actions.act_window',
views: [[false, 'list']],
});
// execute another action with flag 'keepSearchView' and add it to dashboard
var options = {keepSearchView: true};
actionManager.doAction({
id: 2,
res_model: 'partner',
type: 'ir.actions.act_window',
views: [[false, 'graph']],
}, options);
assert.containsOnce(actionManager, '.o_graph',
"should display the graph view");
assert.strictEqual($('.o_add_to_dashboard_link').length, 1,
"should allow the 'Add to dashboard' feature (this is the same searchview)");
// add this action to dashboard
testUtils.dom.click($('.o_search_options .o_dropdown button:contains(Favorites)'));
testUtils.dom.click($('.o_add_to_dashboard_link'));
testUtils.dom.click($('.o_add_to_dashboard_button'));
testUtils.dom.click(actionManager.$('.o_favorites_menu_button'));
testUtils.dom.click(actionManager.$('.o_add_to_board'));
testUtils.dom.click(actionManager.$('.o_add_to_board_confirm_button'));
actionManager.destroy();
});
+1 -1
View File
@@ -5,7 +5,7 @@
<link rel="stylesheet" type="text/scss" href="/board/static/src/scss/dashboard.scss"/>
<script type="text/javascript" src="/board/static/src/js/action_manager_board_action.js"></script>
<script type="text/javascript" src="/board/static/src/js/board_view.js"></script>
<script type="text/javascript" src="/board/static/src/js/favorite_menu.js"></script>
<script type="text/javascript" src="/board/static/src/js/add_to_board_menu.js"></script>
</xpath>
</template>