flectra/addons/web/static/src/js/chrome/control_panel.js

280 lines
10 KiB
JavaScript

flectra.define('web.ControlPanelMixin', function (require) {
"use strict";
/**
* Mixin allowing widgets to communicate with the ControlPanel. Widgets needing a
* ControlPanel should use this mixin and call update_control_panel(cp_status) where
* cp_status contains information for the ControlPanel to update itself.
*
* Note that the API is slightly awkward. Hopefully we will improve this when
* we get the time to refactor the control panel.
*
* For example, here is what a typical client action would need to do to add
* support for a control panel with some buttons::
*
* var ControlPanelMixin = require('web.ControlPanelMixin');
*
* var SomeClientAction = Widget.extend(ControlPanelMixin, {
* ...
* start: function () {
* this._renderButtons();
* this._updateControlPanel();
* ...
* },
* do_show: function () {
* ...
* this._updateControlPanel();
* },
* _renderButtons: function () {
* this.$buttons = $(QWeb.render('SomeTemplate.Buttons'));
* this.$buttons.on('click', ...);
* },
* _updateControlPanel: function () {
* this.update_control_panel({
* breadcrumbs: this.action_manager.get_breadcrumbs(),
* cp_content: {
* $buttons: this.$buttons,
* },
* });
*/
var ControlPanelMixin = {
need_control_panel: true,
/**
* @param {web.Bus} [cp_bus] Bus to communicate with the ControlPanel
*/
set_cp_bus: function(cp_bus) {
this.cp_bus = cp_bus;
},
/**
* Triggers 'update' on the cp_bus to update the ControlPanel according to cp_status
* @param {Object} [cp_status] see web.ControlPanel.update() for a description
* @param {Object} [options] see web.ControlPanel.update() for a description
*/
update_control_panel: function(cp_status, options) {
this.cp_bus.trigger("update", cp_status || {}, options || {});
},
};
return ControlPanelMixin;
});
flectra.define('web.ControlPanel', function (require) {
"use strict";
var Bus = require('web.Bus');
var data = require('web.data');
var Widget = require('web.Widget');
var ControlPanel = Widget.extend({
template: 'ControlPanel',
/**
* @param {String} [template] the QWeb template to render the ControlPanel.
* By default, the template 'ControlPanel' will be used
*/
init: function(parent, template) {
this._super(parent);
if (template) {
this.template = template;
}
this.bus = new Bus(this);
this.bus.on("update", this, this.update);
},
/**
* Renders the control panel and creates a dictionnary of its exposed elements
* @return {jQuery.Deferred}
*/
start: function() {
// Exposed jQuery nodesets
this.nodes = {
$breadcrumbs: this.$('.breadcrumb'),
$buttons: this.$('.o_cp_buttons'),
$pager: this.$('.o_cp_pager'),
$searchview: this.$('.o_cp_searchview'),
$searchview_buttons: this.$('.o_search_options'),
$sidebar: this.$('.o_cp_sidebar'),
$switch_buttons: this.$('.o_cp_switch_buttons'),
};
// Prevent the search dropdowns to close when clicking inside them
this.$el.on('click.bs.dropdown', '.o_search_options .dropdown-menu', function (e) {
e.stopPropagation();
});
// By default, hide the ControlPanel and remove its contents from the DOM
this._toggle_visibility(false);
return this._super();
},
destroy: function() {
this._clear_breadcrumbs_handlers();
return this._super.apply(this, arguments);
},
/**
* @return {Object} the Bus the ControlPanel is listening on
*/
get_bus: function() {
return this.bus;
},
/**
* Updates the content and displays the ControlPanel
* @param {Object} [status.active_view] the current active view
* @param {Array} [status.breadcrumbs] the breadcrumbs to display (see _render_breadcrumbs() for
* precise description)
* @param {Object} [status.cp_content] dictionnary containing the new ControlPanel jQuery elements
* @param {Boolean} [status.hidden] true if the ControlPanel should be hidden
* @param {openerp.web.SearchView} [status.searchview] the searchview widget
* @param {Boolean} [status.search_view_hidden] true if the searchview is hidden, false otherwise
* @param {Boolean} [options.clear] set to true to clear from control panel
* elements that are not in status.cp_content
*/
update: function(status, options) {
this._toggle_visibility(!status.hidden);
// Don't update the ControlPanel in headless mode as the views have
// inserted themselves the buttons where they want, so inserting them
// again in the ControlPanel will remove them from where they should be
if (!status.hidden) {
options = _.defaults({}, options, {
clear: true, // clear control panel by default
});
var new_cp_content = status.cp_content || {};
// Render the breadcrumbs
if (status.breadcrumbs) {
this._clear_breadcrumbs_handlers();
this.$breadcrumbs = this._render_breadcrumbs(status.breadcrumbs);
new_cp_content.$breadcrumbs = this.$breadcrumbs;
}
// Detach control_panel old content and attach new elements
if (options.clear) {
this._detach_content(this.nodes);
// Show the searchview buttons area, which might have been hidden by
// the searchview, as client actions may insert elements into it
this.nodes.$searchview_buttons.show();
} else {
this._detach_content(_.pick(this.nodes, _.keys(new_cp_content)));
}
this._attach_content(new_cp_content);
// Update the searchview and switch buttons
this._update_search_view(status.searchview, status.search_view_hidden);
if (status.active_view_selector) {
this._update_switch_buttons(status.active_view_selector);
}
}
},
/**
* Private function that hides (or shows) the ControlPanel in headless (resp. non-headless) mode
* Also detaches or attaches its contents to clean the DOM
* @param {Boolean} [visible] true to show the control panel, false to hide it
*/
_toggle_visibility: function(visible) {
this.do_toggle(visible);
if (!visible && !this.$content) {
this.$content = this.$el.contents().detach();
} else if (this.$content) {
this.$content.appendTo(this.$el);
this.$content = null;
}
},
/**
* Private function that detaches the content of the ControlPanel
* @param {Object} [elements_to_detach] subset of this.nodes to detach
*/
_detach_content: function(elements_to_detach) {
_.each(elements_to_detach, function($nodeset) {
$nodeset.contents().detach();
});
},
/**
* Private function that attaches content to the ControlPanel
* @param {Object} [content] dictionnary of jQuery elements to attach, whose keys
* are jQuery nodes identifiers in this.nodes
*/
_attach_content: function(content) {
var self = this;
_.each(content, function($nodeset, $element) {
if ($nodeset && self.nodes[$element]) {
self.nodes[$element].append($nodeset);
}
});
},
/**
* Private function that removes active class on all switch-buttons and adds
* it to the one of the active view
* @param {Object} [active_view_selector] the selector of the div to activate
*/
_update_switch_buttons: function(active_view_selector) {
_.each(this.nodes.$switch_buttons.find('button'), function(button) {
$(button).removeClass('active');
});
this.$(active_view_selector).addClass('active');
},
/**
* Private function that renders the breadcrumbs
* @param {Array} [breadcrumbs] list of objects containing the following keys:
* - action: the action to execute when clicking on this part of the breadcrumbs
* - index: the index in the breadcrumbs (starting at 0)
* - title: what to display in the breadcrumbs
* @return {Array} list of breadcrumbs' li jQuery elements
*/
_render_breadcrumbs: function (breadcrumbs) {
var self = this;
return breadcrumbs.map(function (bc, index) {
return self._render_breadcrumbs_li(bc, index, breadcrumbs.length);
});
},
/**
* Private function that renders a breadcrumbs' li Jquery element
*/
_render_breadcrumbs_li: function (bc, index, length) {
var self = this;
var is_last = (index === length-1);
var li_content = bc.title && _.escape(bc.title.trim()) || data.noDisplayContent;
var $bc = $('<li>')
.append(is_last ? li_content : $('<a>').html(li_content))
.toggleClass('active', is_last);
if (!is_last) {
$bc.click(function () {
self.trigger("on_breadcrumb_click", bc.action, bc.index);
});
}
return $bc;
},
/**
* Private function that removes event handlers attached on the currently
* displayed breadcrumbs.
*/
_clear_breadcrumbs_handlers: function () {
if (this.$breadcrumbs) {
_.each(this.$breadcrumbs, function ($bc) {
$bc.off();
});
}
},
/**
* Private function that updates the SearchView's visibility and extend the
* breadcrumbs area if the SearchView is not visible
* @param {openerp.web.SearchView} [searchview] the searchview Widget
* @param {Boolean} [is_hidden] visibility of the searchview
*/
_update_search_view: function(searchview, is_hidden) {
if (searchview) {
// Set the $buttons div (in the DOM) of the searchview as the $buttons
// have been appended to a jQuery node not in the DOM at SearchView initialization
searchview.$buttons = this.nodes.$searchview_buttons;
searchview.toggle_visibility(!is_hidden);
}
this.nodes.$searchview.toggle(!is_hidden);
this.$el.toggleClass('o_breadcrumb_full', !!is_hidden);
},
});
return ControlPanel;
});