+
-
diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css
index 5626ccec148..5181e22a262 100644
--- a/addons/web/static/src/css/base.css
+++ b/addons/web/static/src/css/base.css
@@ -923,60 +923,74 @@
}
.openerp .oe_application {
height: 100%;
- -webkit-flex-grow: 1;
- flex-grow: 1;
- -ms-flex-negative: 1;
- display: inline-block\9;
- overflow: auto\9;
- width: -webkit-calc(100% - 220px);
- width: calc(100% - 220px);
+ width: 100%;
+ display: -webkit-box;
+ display: -moz-box;
+ display: -ms-flexbox;
+ display: -webkit-flex;
+ display: flex;
+ -ms-flex-direction: column;
+ -webkit-flex-direction: column;
+ flex-direction: column;
}
.openerp .oe_application .oe_application {
width: 100%;
}
.openerp .oe-view-manager {
- width: 100%;
- height: 100%;
- display: -webkit-flex;
- display: flex;
- -webkit-flex-direction: column;
- flex-direction: column;
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 auto;
+ -ms-flex: 1 1 auto;
+ flex: 1 1 auto;
+ overflow: auto;
+ position: relative;
}
.openerp .oe-view-manager .oe-view-manager-content {
- overflow: auto;
- -webkit-flex-grow: 1;
- flex-grow: 1;
- position: relative;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ left: 0;
}
.openerp .oe-view-manager .oe-view-manager-content a {
color: #7C7BAD;
}
.openerp .oe-view-manager .oe-view-manager-content > div {
- position: absolute;
- position: static\9;
- top: 0;
- bottom: 0;
- right: 0;
- left: 0;
+ height: 100%;
+ position: static;
display: none;
}
+.openerp .oe-view-manager .oe-view-manager-content .oe-view-manager-content {
+ position: static;
+}
.openerp .oe-view-manager .oe-view-manager-content .oe-view-manager-content > div {
- position: relative;
display: block;
}
.openerp .oe-view-manager .oe-view-manager-debug {
margin-right: 5px;
}
+.openerp .oe-o2m-control-panel {
+ width: 100%;
+}
+.openerp .oe-o2m-control-panel .oe-cp-buttons div {
+ display: inline-block;
+ padding: 5px 5px;
+}
+.openerp .oe-o2m-control-panel .oe-cp-pager div {
+ display: inline-block;
+ padding: 5px 5px;
+}
.openerp .oe-control-panel {
background-color: #f0eeee;
border-bottom: 1px solid #afafb6;
- -webkit-flex-shrink: 0;
- flex-shrink: 0;
- width: 100%;
- -webkit-user-select: none;
- -moz-user-select: none;
+ -webkit-box-flex: 0;
+ -webkit-flex: 0 0 auto;
+ -ms-flex: 0 0 auto;
+ flex: 0 0 auto;
user-select: none;
}
+.openerp .oe-control-panel > .container-fluid {
+ width: 100%;
+}
.openerp .oe-control-panel .oe-button-column {
height: 30px;
}
@@ -1092,7 +1106,8 @@
}
.openerp .oe-control-panel .oe-cp-sidebar .oe_sidebar_delete_item {
padding: 0;
- display: inline-block;
+ position: absolute;
+ right: 10px;
}
.openerp .oe-control-panel .oe-cp-sidebar .dropdown-menu li a {
width: 100%;
@@ -1110,9 +1125,6 @@
.openerp .oe-control-panel .oe-pager-buttons {
min-height: 30px;
}
-.openerp .oe_view_manager_inline > .oe-control-panel-content, .openerp .oe_view_manager_inlineview > .oe-control-panel-content {
- display: none;
-}
.openerp .o-modal-header > div {
margin-left: 45%;
}
@@ -2010,7 +2022,7 @@
margin: 4px 7px;
}
.openerp .oe_form .oe-view-manager-content {
- overflow: visible;
+ overflow: hidden;
}
.openerp .oe_form_editable .oe_form .oe_form_field_integer input {
width: 6em;
@@ -3206,7 +3218,7 @@ body.oe_single_form .oe_single_form_container {
.modal .oe_act_window.modal-body {
padding: 0;
}
-.modal .oe-view-manager-content > div {
+.modal .oe-view-manager-content {
position: static !important;
}
diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass
index 63ef0cd5bdb..a4250f78d91 100644
--- a/addons/web/static/src/css/base.sass
+++ b/addons/web/static/src/css/base.sass
@@ -801,54 +801,64 @@ $sheet-padding: 16px
text-decoration: underline
.oe_application
height: 100%
- -webkit-flex-grow: 1
- flex-grow: 1
- -ms-flex-negative: 1
- display: inline-block\9
- overflow: auto\9
- width: -webkit-calc(100% - 220px)
- width: calc(100% - 220px)
+ width: 100%
+ display: -webkit-box
+ display: -moz-box
+ display: -ms-flexbox
+ display: -webkit-flex
+ display: flex
+ -ms-flex-direction: column
+ -webkit-flex-direction: column
+ flex-direction: column
.oe_application
width: 100%
// }}}
// ViewManager common {{{
.oe-view-manager
- width: 100%
- height: 100%
- display: -webkit-flex
- display: flex
- -webkit-flex-direction: column
- flex-direction: column
+ -webkit-box-flex: 1
+ -webkit-flex: 1 1 auto
+ -ms-flex: 1 1 auto
+ flex: 1 1 auto
+ overflow: auto
+ position: relative
.oe-view-manager-content
- overflow: auto
- -webkit-flex-grow: 1
- flex-grow: 1
- position: relative
+ position: absolute
+ top: 0
+ bottom: 0
+ right: 0
+ left: 0
a
color: $link-color
> div
- position: absolute
- position: static\9
- top: 0
- bottom: 0
- right: 0
- left: 0
+ height: 100%
+ position: static
display: none
.oe-view-manager-content
+ position: static
> div
- position: relative
display: block
.oe-view-manager-debug
margin-right: 5px
+ .oe-o2m-control-panel
+ width: 100%
+ .oe-cp-buttons
+ div
+ display: inline-block
+ padding: 5px 5px
+ .oe-cp-pager
+ div
+ display: inline-block
+ padding: 5px 5px
.oe-control-panel
background-color: rgb(240, 238, 238)
border-bottom: 1px solid #afafb6
- -webkit-flex-shrink: 0
- flex-shrink: 0
- width: 100%
- -webkit-user-select: none
- -moz-user-select: none
+ -webkit-box-flex: 0
+ -webkit-flex: 0 0 auto
+ -ms-flex: 0 0 auto
+ flex: 0 0 auto
user-select: none
+ > .container-fluid
+ width: 100%
.oe-button-column
height: 30px
.dropdown-menu
@@ -950,9 +960,6 @@ $sheet-padding: 16px
.oe-pager-buttons
min-height: 30px
- .oe_view_manager_inline, .oe_view_manager_inlineview
- > .oe-control-panel-content
- display: none
// }}}
// FormPopup {{{
@@ -1673,8 +1680,7 @@ $sheet-padding: 16px
margin: 4px 7px
// Override ViewManager overflow auto for One2Many fields
.oe-view-manager-content
- overflow: visible
-
+ overflow: hidden
.oe_form_editable
.oe_form
.oe_form_field_integer input
@@ -2641,7 +2647,7 @@ body.oe_single_form
margin: 0 4px 0 0
.oe_act_window.modal-body
padding: 0
- .oe-view-manager-content > div
+ .oe-view-manager-content
position: static !important
@media (min-width: 768px)
diff --git a/addons/web/static/src/js/action_manager.js b/addons/web/static/src/js/action_manager.js
index 61686dc4e7e..8c560830493 100644
--- a/addons/web/static/src/js/action_manager.js
+++ b/addons/web/static/src/js/action_manager.js
@@ -1,6 +1,7 @@
odoo.define('web.ActionManager', function (require) {
"use strict";
+var ControlPanel = require('web.ControlPanel');
var core = require('web.core');
var crash_manager = require('web.crash_manager');
var data = require('web.data');
@@ -11,7 +12,63 @@ var session = require('web.session');
var ViewManager = require('web.ViewManager');
var Widget = require('web.Widget');
-var CompoundContext = data.CompoundContext;
+var State = core.Class.extend({
+ init: function(title, flags) {
+ this.title = title;
+ this.flags = flags;
+ },
+ enable: function() { return $.when(); },
+ disable: function() {},
+ destroy: function() {},
+ get_widget: function() {},
+ get_title: function() { return this.title; },
+ get_flags: function() { return this.flags; },
+ get_action: function() {},
+ get_active_view: function() {},
+});
+
+var WidgetState = State.extend({
+ init: function(title, flags, widget) {
+ this._super(title, flags);
+
+ this.widget = widget;
+ if (this.widget.get('title')) {
+ this.title = title;
+ } else {
+ this.widget.set('title', title);
+ }
+ this.widget.on("do_search", this, function() {
+ // active_search = this.control_panel.activate_search(view.created);
+ // call activate search on search view
+ // domain context and groupby computed are important
+ });
+ },
+ set_cp_content: function(content) { this.cp_content = content; },
+ get_cp_content: function() { return this.cp_content; },
+ get_action: function() { return this.widget.action; },
+ get_active_view: function() { return this.widget.active_view; },
+ destroy: function() {
+ if (this.cp_content && this.cp_content.searchview) {
+ this.cp_content.searchview.destroy();
+ }
+ this.widget.destroy();
+ },
+ get_widget: function() {
+ return this.widget;
+ },
+});
+
+var FunctionState = State.extend({
+ init: function(title, flags) {
+ this._super(title, flags);
+
+ this.widget = {
+ view_stack: [{
+ controller: { get: function () { return this.title; }}
+ }]
+ };
+ }
+});
var ActionManager = Widget.extend({
template: "ActionManager",
@@ -22,9 +79,28 @@ var ActionManager = Widget.extend({
this.webclient = parent;
this.dialog = null;
this.dialog_widget = null;
- this.widgets = [];
+ this.states = [];
this.on('history_back', this, this.proxy('history_back'));
},
+ start: function() {
+ this._super();
+
+ // Instantiate the unique main control panel used by every widget in this.states
+ this.main_control_panel = new ControlPanel(this);
+ // Listen to event "switch_view" trigerred on the control panel when clicking
+ // on switch buttons. Forward this event to the current inner_widget
+ this.main_control_panel.on("switch_view", this, function(view_type) {
+ this.inner_widget.trigger("switch_view", view_type);
+ });
+ // Listen to event "on_breadcrumb_click" trigerred on the control panel when
+ // clicking on a part of the breadcrumbs. Call select_state for this breadcrumb.
+ this.main_control_panel.on("on_breadcrumb_click", this, function(state, index) {
+ this.select_state(state, index);
+ });
+
+ // Append the main control panel to the DOM (inside the ActionManager jQuery element)
+ this.main_control_panel.appendTo(this.$el);
+ },
dialog_stop: function (reason) {
if (this.dialog) {
this.dialog.destroy(reason);
@@ -32,125 +108,150 @@ var ActionManager = Widget.extend({
this.dialog = null;
},
/**
- * Add a new widget to the action manager
+ * Add a new state to the action manager
*
- * widget: typically, widgets added are instance.web.ViewManager. The action manager
+ * widget: typically, widgets added are web.ViewManager. The action manager
* uses this list of widget to handle the breadcrumbs.
* action: new action
* options.on_reverse_breadcrumb: will be called when breadcrumb is selected
* options.clear_breadcrumbs: boolean, if true, current widgets are destroyed
- * options.replace_breadcrumb: boolean, if true, replace current breadcrumb
*/
- push_widget: function(widget, action, options) {
+ push_state: function(widget, action, options) {
var self = this,
to_destroy,
- old_widget = this.inner_widget;
+ old_widget = this.inner_widget,
+ old_state = this.get_current_state();
options = options || {};
if (options.clear_breadcrumbs) {
- to_destroy = this.widgets;
- this.widgets = [];
- } else if (options.replace_breadcrumb) {
- to_destroy = _.last(this.widgets);
- this.widgets = _.initial(this.widgets);
+ to_destroy = this.states;
+ this.states = [];
}
+ var new_state,
+ title = action.display_name || action.name;
if (widget instanceof Widget) {
- var title = widget.get('title') || action.display_name || action.name;
- widget.set('title', title);
- this.widgets.push(widget);
+ new_state = new WidgetState(title, action.flags, widget);
} else {
- this.widgets.push({
- view_stack: [{
- controller: {get: function () {return action.display_name || action.name; }},
- }],
- destroy: function () {},
- });
+ new_state = new FunctionState(title, action.flags);
}
- _.last(this.widgets).__on_reverse_breadcrumb = options.on_reverse_breadcrumb;
+ this.states.push(new_state);
+
+ this.get_current_state().__on_reverse_breadcrumb = options.on_reverse_breadcrumb;
this.inner_action = action;
this.inner_widget = widget;
- return $.when(this.inner_widget.appendTo(this.$el)).done(function () {
- if ((action.target !== 'inline') && (!action.flags.headless) && widget.$header) {
- widget.$header.show();
+
+ // Sets the main ControlPanel state
+ // AAB: temporary restrict the use of main control panel to act_window actions
+ if (action.type === 'ir.actions.act_window') {
+ // if (old_state) old_state.disable();
+ // new_state.enable();
+ this.main_control_panel.set_state(new_state, old_state);
+ // Expose the ControlPanel nodes to the inner_widget only if the ControlPanel is
+ // displayed so that the inner_widget don't insert stuff in hidden nodes
+ var new_state_flags = new_state.get_flags();
+ if (!new_state_flags.headless) {
+ this.inner_widget.set_external_nodes(this.main_control_panel.get_cp_nodes());
}
+ }
+
+ return $.when(this.inner_widget.appendTo(this.$el)).done(function () {
if (old_widget) {
old_widget.$el.hide();
}
if (options.clear_breadcrumbs) {
- self.clear_widgets(to_destroy);
+ self.clear_states(to_destroy);
}
});
},
get_breadcrumbs: function () {
- return _.flatten(_.map(this.widgets, function (widget) {
- if (widget instanceof ViewManager) {
- return widget.view_stack.map(function (view, index) {
+ return _.flatten(_.map(this.states, function (state) {
+ if (state.widget instanceof ViewManager) {
+ return state.widget.view_stack.map(function (view, index) {
return {
- title: view.controller.get('title') || widget.title,
+ title: view.controller.get('title') || state.get_title(),
index: index,
- widget: widget,
+ widget: state,
};
});
} else {
- return {title: widget.get('title'), widget: widget };
+ return { title: state.get_title(), widget: state };
}
}), true);
},
get_title: function () {
- if (this.widgets.length === 1) {
+ if (this.states.length === 1) {
// horrible hack to display the action title instead of "New" for the actions
// that use a form view to edit something that do not correspond to a real model
// for example, point of sale "Your Session" or most settings form,
- var widget = this.widgets[0];
- if (widget instanceof ViewManager && widget.view_stack.length === 1) {
- return widget.title;
+ var state = this.states[0];
+ if (state.widget instanceof ViewManager && state.widget.view_stack.length === 1) {
+ return state.get_title();
}
}
return _.pluck(this.get_breadcrumbs(), 'title').join(' / ');
},
- get_widgets: function () {
- return this.widgets.slice(0);
+ get_states: function () {
+ return this.states;
+ },
+ get_current_state: function() {
+ return _.last(this.states);
+ },
+ get_inner_action: function() {
+ return this.inner_action;
+ },
+ get_inner_widget: function() {
+ return this.inner_widget;
},
history_back: function() {
- var widget = _.last(this.widgets);
- if (widget instanceof ViewManager) {
- var nbr_views = widget.view_stack.length;
+ var state = this.get_current_state();
+ if (state.widget instanceof ViewManager) {
+ var nbr_views = state.widget.view_stack.length;
if (nbr_views > 1) {
- return this.select_widget(widget, nbr_views - 2);
+ return this.select_state(state, nbr_views - 2);
}
}
- if (this.widgets.length > 1) {
- widget = this.widgets[this.widgets.length - 2];
- var index = widget.view_stack && widget.view_stack.length - 1;
- return this.select_widget(widget, index);
+ if (this.states.length > 1) {
+ state = this.states[this.states.length - 2];
+ var index = state.widget.view_stack && state.widget.view_stack.length - 1;
+ return this.select_state(state, index);
}
return $.Deferred().reject();
},
- select_widget: function(widget, index) {
+ select_state: function(state, index) {
var self = this;
if (this.webclient.has_uncommitted_changes()) {
return $.Deferred().reject();
}
- var widget_index = this.widgets.indexOf(widget),
- def = $.when(widget.select_view && widget.select_view(index));
+ // Client widget (-> put in ClientState?)
+ if (state.__on_reverse_breadcrumb) {
+ state.__on_reverse_breadcrumb();
+ }
+ // Set the control panel new state
+ // Put in VMState?
+ // Inform the ControlPanel that the current state changed
+ self.main_control_panel.set_state(state, this.get_current_state());
+
+ var state_index = this.states.indexOf(state),
+ def = $.when(state.widget.select_view && state.widget.select_view(index));
+
+ self.clear_states(self.states.splice(state_index + 1));
+ var last_state = _.last(self.states);
+ self.inner_widget = last_state.widget;
+ // AAB: preceeding two lines probably equal to
+ // self.inner_widget = state;
return def.done(function () {
- if (widget.__on_reverse_breadcrumb) {
- widget.__on_reverse_breadcrumb();
- }
- _.each(self.widgets.splice(widget_index + 1), function (w) {
- w.destroy();
- });
- self.inner_widget = _.last(self.widgets);
if (self.inner_widget.do_show) {
self.inner_widget.do_show();
}
});
},
- clear_widgets: function(widgets) {
- _.invoke(widgets || this.widgets, 'destroy');
- if (!widgets) {
- this.widgets = [];
+ clear_states: function(states) {
+ _.map(states || this.states, function(state) {
+ state.destroy();
+ });
+ if (!states) {
+ this.states = [];
this.inner_widget = null;
}
},
@@ -202,7 +303,7 @@ var ActionManager = Widget.extend({
var self = this,
action_loaded;
if (state.action) {
- if (_.isString(state.action) && core.action_registry.contains(state.action)) {
+ if (_.isString(state.action) && core.action_regisry.contains(state.action)) {
var action_client = {
type: "ir.actions.client",
tag: state.action,
@@ -300,7 +401,7 @@ var ActionManager = Widget.extend({
if (action === false) {
action = { type: 'ir.actions.act_window_close' };
- } else if (_.isString(action) && core.action_registry.contains(action)) {
+ } else if (_.isString(action) && core.action_regisry.contains(action)) {
var action_client = { type: "ir.actions.client", tag: action, params: {} };
return this.do_action(action_client, options);
} else if (_.isNumber(action) || _.isString(action)) {
@@ -318,7 +419,7 @@ var ActionManager = Widget.extend({
core.bus.trigger('action', action);
// Ensure context & domain are evaluated and can be manipulated/used
- var ncontext = new CompoundContext(options.additional_context, action.context || {});
+ var ncontext = new data.CompoundContext(options.additional_context, action.context || {});
action.context = pyeval.eval('context', ncontext);
if (action.context.active_id || action.context.active_ids) {
// Here we assume that when an `active_id` or `active_ids` is used
@@ -335,39 +436,56 @@ var ActionManager = Widget.extend({
console.error("No type for action", action);
return $.Deferred().reject();
}
+
var type = action.type.replace(/\./g,'_');
- var popup = action.target === 'new';
- var inline = action.target === 'inline' || action.target === 'inlineview';
- var form = _.str.startsWith(action.view_mode, 'form');
- action.flags = _.defaults(action.flags || {}, {
- views_switcher : !popup && !inline,
- search_view : !popup && !inline,
- action_buttons : !popup && !inline,
- sidebar : !popup && !inline,
- pager : (!popup || !form) && !inline,
- display_title : !popup,
- headless: (popup || inline) && form,
- search_disable_custom_filters: action.context && action.context.search_disable_custom_filters
- });
action.menu_id = options.action_menu_id;
action.context.params = _.extend({ 'action' : action.id }, action.context.params);
if (!(type in this)) {
console.error("Action manager can't handle action of type " + action.type, action);
return $.Deferred().reject();
}
+
+ // Special case for Dashboards, this should definitively be done upstream
+ if (action.res_model === 'board.board' && action.view_mode === 'form') {
+ action.target = 'inline';
+ _.extend(action.flags, {
+ headless: true,
+ views_switcher: false,
+ display_title: false,
+ search_view: false,
+ pager: false,
+ sidebar: false,
+ action_buttons: false
+ });
+ } else {
+ var popup = action.target === 'new';
+ var inline = action.target === 'inline' || action.target === 'inlineview';
+ var form = _.str.startsWith(action.view_mode, 'form');
+ action.flags = _.defaults(action.flags || {}, {
+ views_switcher : !popup && !inline,
+ search_view : !popup && !inline,
+ action_buttons : !popup && !inline,
+ sidebar : !popup && !inline,
+ pager : (!popup || !form) && !inline,
+ display_title : !popup,
+ headless: (popup || inline) && form,
+ search_disable_custom_filters: action.context && action.context.search_disable_custom_filters
+ });
+ }
+
return this[type](action, options);
},
null_action: function() {
this.dialog_stop();
- this.clear_widgets();
+ this.clear_states();
},
/**
*
* @param {Object} executor
* @param {Object} executor.action original action
- * @param {Function
} executor.widget function used to fetch the widget instance
+ * @param {Function} executor.widget function used to fetch the widget instance
* @param {String} executor.klass CSS class to add on the dialog root, if action.target=new
- * @param {Function} executor.post_process cleanup called after a widget has been added as inner_widget
+ * @param {Function} executor.post_process cleanup called after a widget has been added as inner_widget
* @param {Object} options
* @return {*}
*/
@@ -425,14 +543,13 @@ var ActionManager = Widget.extend({
}
widget = executor.widget();
this.dialog_stop(executor.action);
- return this.push_widget(widget, executor.action, options);
+ return this.push_state(widget, executor.action, options);
},
ir_actions_act_window: function (action, options) {
var self = this;
-
return this.ir_actions_common({
widget: function () {
- return new ViewManager(self, null, null, null, action);
+ return new ViewManager(self, null, null, null, action, self.main_control_panel.get_bus());
},
action: action,
klass: 'oe_act_window',
@@ -440,7 +557,7 @@ var ActionManager = Widget.extend({
},
ir_actions_client: function (action, options) {
var self = this;
- var ClientWidget = core.action_registry.get(action.tag);
+ var ClientWidget = core.action_registry.get_object(action.tag);
if (!ClientWidget) {
return self.do_warn("Action Error", "Could not find client action '" + action.tag + "'.");
}
@@ -454,7 +571,11 @@ var ActionManager = Widget.extend({
}
return this.ir_actions_common({
- widget: function () { return new ClientWidget(self, action); },
+ widget: function () {
+ // AAB: temporary fix: Hide main control panel as client actions do not use it
+ self.main_control_panel.do_hide();
+ return new ClientWidget(self, action);
+ },
action: action,
klass: 'oe_act_client',
}, options).then(function () {
diff --git a/addons/web/static/src/js/control_panel.js b/addons/web/static/src/js/control_panel.js
index d5c016522f5..b71aefa60f1 100644
--- a/addons/web/static/src/js/control_panel.js
+++ b/addons/web/static/src/js/control_panel.js
@@ -5,12 +5,13 @@ var core = require('web.core');
var Dialog = require('web.Dialog');
var formats = require('web.formats');
var framework = require('web.framework');
-var Model = require('web.Model');
-var pyeval = require('web.pyeval');
var SearchView = require('web.SearchView');
var utils = require('web.utils');
var Widget = require('web.Widget');
+var QWeb = core.qweb;
+var _t = core._t;
+
var ControlPanel = Widget.extend({
template: 'ControlPanel',
events: {
@@ -27,58 +28,118 @@ var ControlPanel = Widget.extend({
this.template = template;
}
- this.view_manager = parent;
- this.action_manager = this.view_manager.action_manager;
- this.action = this.view_manager.action;
- this.dataset = this.view_manager.dataset;
- this.active_view = this.view_manager.active_view;
- this.views = this.view_manager.views;
- this.flags = this.view_manager.flags;
- this.title = this.view_manager.title; // needed for Favorites of searchview
- this.view_order = this.view_manager.view_order;
- this.multiple_views = (this.view_order.length > 1);
+ this.bus = new core.Bus();
+ this.bus.on("setup_search_view", this, this.setup_search_view);
+ this.bus.on("update", this, this.update);
+ this.bus.on("update_breadcrumbs", this, this.update_breadcrumbs);
+ this.bus.on("render_buttons", this, this.render_buttons);
+ this.bus.on("render_switch_buttons", this, this.render_switch_buttons);
+
+ this.searchview = null;
+
+ this.flags = null;
+ this.dataset = null;
+ this.active_view = null;
+ this.title = null; // Needed for Favorites of searchview
},
start: function() {
- var self = this;
-
- // Retrieve control panel elements
+ // Retrieve ControlPanel jQuery nodes
this.$control_panel = this.$('.oe-control-panel-content');
this.$breadcrumbs = this.$('.oe-view-title');
- this.$switch_buttons = this.$('.oe-cp-switch-buttons button');
+ this.$buttons = this.$('.oe-cp-buttons');
+ this.$switch_buttons = this.$('.oe-cp-switch-buttons');
this.$title_col = this.$control_panel.find('.oe-cp-title');
this.$search_col = this.$control_panel.find('.oe-cp-search-view');
- // AAB: Use sidebar and pager of the ControlPanel only if it is displayed, otherwise set them
- // to undefined to that the view uses its own elements to sidebar and pager, as follows:
- // this.$sidebar = !this.flags.headless && this.flags.sidebar ? this.$('.oe-cp-sidebar') : undefined,
- // this.$pager = !this.flags.headless ? this.$('.oe-cp-pager') : undefined;
- // But rather use the following definition to keep behavior as it is for now (i.e. it does not
- // display the pager in one2many list views)
- this.$sidebar = this.flags.sidebar ? this.$('.oe-cp-sidebar') : undefined;
+ this.$searchview = this.$(".oe-cp-search-view");
+ this.$searchview_buttons = this.$('.oe-search-options');
this.$pager = this.$('.oe-cp-pager');
-
- // Hide the ControlPanel in headless mode
- if (this.flags.headless) {
- this.$control_panel.hide();
- }
-
- _.each(this.views, function (view) {
- // Expose control panel elements to the views so that they can insert stuff in them
- view.options = _.extend(view.options, {
- $buttons : !self.flags.headless ? self.$('.oe-' + view.type + '-buttons') : undefined,
- $sidebar : self.$sidebar,
- $pager : self.$pager,
- }, self.flags, self.flags[view.type], view.options);
- // Show $buttons as views will put their own buttons inside it and show/hide them
- if (view.options.$buttons) view.options.$buttons.show();
- self.$('.oe-cp-switch-' + view.type).tooltip();
- });
-
- // Create the searchview
- this.search_view_loaded = this.setup_search_view();
+ this.$sidebar = this.$('.oe-cp-sidebar');
return this._super();
},
/**
+ * @return {Object} Dictionnary of ControlPanel nodes
+ */
+ get_cp_nodes: function() {
+ return {
+ $buttons: this.$buttons,
+ $switch_buttons: this.$switch_buttons,
+ $sidebar: this.$sidebar,
+ $pager: this.$pager
+ };
+ },
+ get_bus: function() {
+ return this.bus;
+ },
+ // Sets the state of the controlpanel (in the case of a viewmanager, set_state must be
+ // called before switch_mode for the controlpanel and the viewmanager to be synchronized)
+ set_state: function(state, old_state) {
+ // Detach control panel elements in which sub-elements are inserted by other widgets
+ var old_content = {
+ '$buttons': this.$buttons.contents().detach(),
+ '$switch_buttons': this.$switch_buttons.contents().detach(),
+ '$pager': this.$pager.contents().detach(),
+ '$sidebar': this.$sidebar.contents().detach(),
+ 'searchview': this.searchview, // AAB: do better with searchview
+ '$searchview': this.$searchview.contents().detach(),
+ '$searchview_buttons': this.$searchview_buttons.contents().detach(),
+ };
+ if (old_state) {
+ // Store them to re-attach them if we come back to that state (e.g. using breadcrumbs)
+ old_state.set_cp_content(old_content);
+ }
+
+ this.state = state;
+ this.flags = state.get_flags();
+
+ this.flags = state.widget.flags;
+ this.active_view = state.widget.active_view;
+ this.dataset = state.widget.dataset;
+ this.title = state.widget.title; // needed for Favorites of searchview
+
+ // Hide the ControlPanel in headless mode
+ this.$el.toggle(!this.flags.headless);
+
+ var content = state.get_cp_content();
+ if (content) {
+ // This state has already been rendered once
+ content.$buttons.appendTo(this.$buttons);
+ content.$switch_buttons.appendTo(this.$switch_buttons);
+ content.$pager.appendTo(this.$pager);
+ content.$sidebar.appendTo(this.$sidebar);
+ this.searchview = content.searchview;
+ content.$searchview.appendTo(this.$searchview);
+ content.$searchview_buttons.appendTo(this.$searchview_buttons);
+ }
+ },
+ get_state: function() {
+ return this.state;
+ },
+ render_buttons: function(views) {
+ var self = this;
+
+ var buttons_divs = QWeb.render('ControlPanel.buttons', {views: views});
+ $(buttons_divs).appendTo(this.$buttons);
+
+ // Show each div as views will put their own buttons inside it and show/hide them
+ _.each(views, function(view) {
+ self.$('.oe-' + view.type + '-buttons').show();
+ });
+ },
+ render_switch_buttons: function(views) {
+ if (views.length > 1) {
+ var self = this;
+
+ var switch_buttons = QWeb.render('ControlPanel.switch-buttons', {views: views});
+ $(switch_buttons).appendTo(this.$switch_buttons);
+
+ // Create tooltips
+ _.each(views, function(view) {
+ self.$('.oe-cp-switch-' + view.type).tooltip();
+ });
+ }
+ },
+ /**
* Triggers an event when switch-buttons are clicked on
*/
on_switch_buttons_click: function(event) {
@@ -87,22 +148,34 @@ var ControlPanel = Widget.extend({
},
/**
* Updates its status according to the active_view
+ * @param {Object} [active_view] the current active view
+ * @param {Boolean} [search_view_hidden] true if the searchview is hidden, false otherwise
+ * @param {Array} [breadcrumbs] the breadcrumbs to display
*/
- update: function(active_view) {
- this.active_view = active_view;
+ update: function(active_view, search_view_hidden, breadcrumbs) {
+ this.active_view = active_view; // this.active_view only used for debug view
- this.update_search_view();
- this.update_breadcrumbs();
+ this.update_switch_buttons(active_view);
+ this.update_search_view(search_view_hidden);
+ this.update_breadcrumbs(breadcrumbs);
this.render_debug_view();
-
- // Update switch-buttons
- this.$switch_buttons.removeClass('active');
- this.$('.oe-cp-switch-' + this.active_view.type).addClass('active');
},
- update_breadcrumbs: function () {
+ /**
+ * Removes active class on all switch-buttons and adds it to the one of the active view
+ * @param {Object} [active_view] the active_view
+ */
+ update_switch_buttons: function(active_view) {
+ _.each(this.$switch_buttons.contents('button'), function(button) {
+ $(button).removeClass('active');
+ });
+ this.$('.oe-cp-switch-' + active_view.type).addClass('active');
+ },
+ /**
+ * Updates the breadcrumbs
+ **/
+ update_breadcrumbs: function (breadcrumbs) {
var self = this;
- if (!this.action_manager) return;
- var breadcrumbs = this.action_manager.get_breadcrumbs();
+
if (!breadcrumbs.length) return;
var $breadcrumbs = breadcrumbs.map(function (bc, index) {
@@ -119,27 +192,27 @@ var ControlPanel = Widget.extend({
.toggleClass('active', is_last);
if (!is_last) {
$bc.click(function () {
- self.action_manager.select_widget(bc.widget, bc.index);
+ self.trigger("on_breadcrumb_click", bc.widget, bc.index);
});
}
return $bc;
}
},
/**
- * Sets up the search view.
+ * Sets up the search view and calls set_search_view on the widget requesting it
*
- * @returns {jQuery.Deferred} search view startup deferred
+ * @param {Object} [src] the widget requesting a search_view
+ * @param {Object} [action] the action (required to instantiated the SearchView)
+ * @param {Object} [dataset] the dataset (required to instantiated the SearchView)
+ * @param {Object} [flags] a dictionnary of Booleans
*/
- setup_search_view: function() {
- if (this.searchview) {
- this.searchview.destroy();
- }
-
- var view_id = (this.action && this.action.search_view_id && this.action.search_view_id[0]) || false;
+ setup_search_view: function(src, action, dataset, flags) {
+ var self = this;
+ var view_id = (action && action.search_view_id && action.search_view_id[0]) || false;
var search_defaults = {};
- var context = this.action ? this.action.context : [];
+ var context = action ? action.context : [];
_.each(context, function (value, key) {
var match = /^search_default_(.*)$/.exec(key);
if (match) {
@@ -148,67 +221,29 @@ var ControlPanel = Widget.extend({
});
var options = {
- hidden: this.flags.search_view === false,
- disable_custom_filters: this.flags.search_disable_custom_filters,
- $buttons: this.$('.oe-search-options'),
- action: this.action,
+ hidden: flags.search_view === false,
+ disable_custom_filters: flags.search_disable_custom_filters,
+ $buttons: this.$searchview_buttons,
+ action: action,
};
- this.searchview = new SearchView(this, this.dataset, view_id, search_defaults, options);
- this.searchview.on('search_data', this, this.search.bind(this));
- return this.searchview.appendTo(this.$(".oe-cp-search-view:first"));
+ // Instantiate the SearchView and append it to the DOM
+ this.searchview = new SearchView(this, dataset, view_id, search_defaults, options);
+ var search_view_loaded = this.searchview.appendTo(this.$searchview);
+ // Sets the SearchView in the widget which made the request
+ src.set_search_view(self.searchview, search_view_loaded);
},
- update_search_view: function() {
+ /**
+ * Updates the SearchView's visibility and extend the breadcrumbs area if the SearchView is not visible
+ * @param {Boolean} [is_hidden] visibility of the searchview
+ **/
+ update_search_view: function(is_hidden) {
if (this.searchview) {
- var is_hidden = this.active_view.controller.searchable === false;
this.searchview.toggle_visibility(!is_hidden);
this.$title_col.toggleClass('col-md-6', !is_hidden).toggleClass('col-md-12', is_hidden);
this.$search_col.toggle(!is_hidden);
}
},
- search: function(domains, contexts, groupbys) {
- var self = this,
- controller = this.active_view.controller,
- action_context = this.action.context || {},
- view_context = controller.get_context();
- pyeval.eval_domains_and_contexts({
- domains: [this.action.domain || []].concat(domains || []),
- contexts: [action_context, view_context].concat(contexts || []),
- group_by_seq: groupbys || []
- }).done(function (results) {
- if (results.error) {
- self.active_search.resolve();
- throw new Error(
- _.str.sprintf(_t("Failed to evaluate search criterions")+": \n%s",
- JSON.stringify(results.error)));
- }
- self.dataset._model = new Model(
- self.dataset.model, results.context, results.domain);
- var groupby = results.group_by.length ?
- results.group_by :
- action_context.group_by;
- if (_.isString(groupby)) {
- groupby = [groupby];
- }
- if (!controller.grouped && !_.isEmpty(groupby)){
- self.dataset.set_sort([]);
- }
- $.when(controller.do_search(results.domain, results.context, groupby || [])).then(function() {
- self.active_search.resolve();
- });
- });
- },
- activate_search: function(view_created_def) {
- this.active_search = $.Deferred();
- if (this.searchview &&
- this.flags.auto_search &&
- this.active_view.controller.searchable !== false) {
- $.when(this.search_view_loaded,view_created_def).done(this.searchview.do_search);
- } else {
- this.active_search.resolve();
- }
- return this.active_search;
- },
on_debug_changed: function (evt) {
var self = this,
params = $(evt.target).data(),
@@ -231,7 +266,7 @@ var ControlPanel = Widget.extend({
var ids = current_view.get_selected_ids();
if (ids.length === 1) {
this.dataset.call('get_metadata', [ids]).done(function(result) {
- var dialog = new Dialog(this, {
+ new Dialog(this, {
title: _.str.sprintf(_t("Metadata (%s)"), self.dataset.model),
size: 'medium',
buttons: {
diff --git a/addons/web/static/src/js/view_manager.js b/addons/web/static/src/js/view_manager.js
index 0474f0e127b..94af0912907 100644
--- a/addons/web/static/src/js/view_manager.js
+++ b/addons/web/static/src/js/view_manager.js
@@ -1,21 +1,13 @@
odoo.define('web.ViewManager', function (require) {
"use strict";
-var ControlPanel = require('web.ControlPanel');
var core = require('web.core');
var data = require('web.data');
-var Dialog = require('web.Dialog');
-var formats = require('web.formats');
-var framework = require('web.framework');
var Model = require('web.Model');
var pyeval = require('web.pyeval');
-var utils = require('web.utils');
-var SearchView = require('web.SearchView');
-var session = require('web.session');
var Widget = require('web.Widget');
var _t = core._t;
-var QWeb = core.qweb;
var ViewManager = Widget.extend({
template: "ViewManager",
@@ -23,25 +15,14 @@ var ViewManager = Widget.extend({
* @param {Object} [dataset] null object (... historical reasons)
* @param {Array} [views] List of [view_id, view_type]
* @param {Object} [flags] various boolean describing UI state
+ * @param {Object} [cp_bus] Bus to allow communication with ControlPanel
*/
- init: function(parent, dataset, views, flags, action) {
+ init: function(parent, dataset, views, flags, action, cp_bus) {
if (action) {
flags = action.flags || {};
if (!('auto_search' in flags)) {
flags.auto_search = action.auto_search !== false;
}
- if (action.res_model === 'board.board' && action.view_mode === 'form') {
- action.target = 'inline';
- // Special case for Dashboards
- _.extend(flags, {
- views_switcher : false,
- display_title : false,
- search_view : false,
- pager : false,
- sidebar : false,
- action_buttons : false
- });
- }
this.action = action;
this.action_manager = parent;
dataset = new data.DataSetSearch(this, action.res_model, action.context, action.domain);
@@ -62,6 +43,7 @@ var ViewManager = Widget.extend({
this.active_view = null;
this.registry = core.view_registry;
this.title = this.action && this.action.name;
+ this.cp_bus = cp_bus;
_.each(views, function (view) {
var view_type = view[1] || view.view_type,
@@ -81,10 +63,8 @@ var ViewManager = Widget.extend({
self.views[view_type] = view_descr;
});
- // Instantiate ControlPanel
- this.control_panel = new ControlPanel(self);
- // Listen to event 'switch_view' trigerred when clicking on switch-buttons
- this.control_panel.on('switch_view', this, function(view_type) {
+ // Listen to event 'switch_view' indicating that the VM must now display view wiew_type
+ this.on('switch_view', this, function(view_type) {
if (view_type === 'form' && this.active_view && this.active_view.type === 'form') {
this._display_view(view_type);
} else {
@@ -114,17 +94,85 @@ var ViewManager = Widget.extend({
this.$el.addClass("oe_view_manager_" + ((this.action && this.action.target) || 'current'));
- // Insert ControlPanel in the DOM
- var cp_loaded = this.control_panel.prependTo(this.$el);
- var main_view_loaded = this.switch_mode(default_view, null, default_options);
+ if (this.cp_bus) {
+ // Tell the ControlPanel to setup its search view
+ this.search_view_loaded = $.Deferred();
+ this.cp_bus.trigger('setup_search_view', this, this.action, this.dataset, this.flags);
+ $.when(this.search_view_loaded).then(function() {
+ self.searchview.on('search_data', self, self.search);
+ });
- return $.when(main_view_loaded, cp_loaded);
+ // Tell the ControlPanel to render and append the (switch-)buttons to the DOM
+ this.cp_bus.trigger('render_buttons', this.views);
+ this.cp_bus.trigger('render_switch_buttons', this.view_order);
+ }
+
+ _.each(this.views, function (view) {
+ // Expose buttons, sidebar and pager elements to the views so that they can insert stuff in them
+ view.options = _.extend(view.options, {
+ $buttons : self.$ext_buttons ? self.$ext_buttons.find('.oe-' + view.type + '-buttons') : undefined,
+ $sidebar : self.$ext_sidebar,
+ $pager : self.$ext_pager,
+ }, self.flags, self.flags[view.type], view.options);
+ });
+
+ // Switch to the default_view to load it
+ this.main_view_loaded = this.switch_mode(default_view, null, default_options);
+
+ return $.when(self.main_view_loaded, this.search_view_loaded);
},
/**
- * Needed for dashboard.js to add Favorites to Dashboard
+ * Sets the external nodes in which the ViewManager and its views should insert elements
+ * @param {Object} [nodes] a dictionnary of jQuery nodes
*/
- get_searchview: function() {
- return this.control_panel.searchview;
+ set_external_nodes: function(nodes) {
+ this.$ext_buttons = nodes.$buttons;
+ this.$ext_sidebar = nodes.$sidebar;
+ this.$ext_pager = nodes.$pager;
+ },
+ /**
+ * Executed by the ControlPanel when the searchview requested by this ViewManager is loaded
+ * @param {Widget} [searchview] the SearchView
+ * @param {Deferred} [search_view_loaded_def] will be resolved when the SearchView is loaded
+ */
+ set_search_view: function(searchview, search_view_loaded_def) {
+ this.searchview = searchview;
+ this.search_view_loaded = search_view_loaded_def;
+ },
+ /**
+ * Executed on event "search_data" thrown by the SearchView
+ */
+ search: function(domains, contexts, groupbys) {
+ var self = this,
+ controller = this.active_view.controller, // AAB: Correct view must be loaded here
+ action_context = this.action.context || {},
+ view_context = controller.get_context();
+ pyeval.eval_domains_and_contexts({
+ domains: [this.action.domain || []].concat(domains || []),
+ contexts: [action_context, view_context].concat(contexts || []),
+ group_by_seq: groupbys || []
+ }).done(function (results) {
+ if (results.error) {
+ self.active_search.resolve();
+ throw new Error(
+ _.str.sprintf(_t("Failed to evaluate search criterions")+": \n%s",
+ JSON.stringify(results.error)));
+ }
+ self.dataset._model = new Model(
+ self.dataset.model, results.context, results.domain);
+ var groupby = results.group_by.length
+ ? results.group_by
+ : action_context.group_by;
+ if (_.isString(groupby)) {
+ groupby = [groupby];
+ }
+ if (!controller.grouped && !_.isEmpty(groupby)){
+ self.dataset.set_sort([]);
+ }
+ $.when(controller.do_search(results.domain, results.context, groupby || [])).then(function() {
+ self.active_search.resolve();
+ });
+ });
},
get_default_view: function() {
return this.flags.default_view || this.view_order[0].type;
@@ -147,15 +195,22 @@ var ViewManager = Widget.extend({
if (this.active_view.controller) this.active_view.controller.do_hide();
if (this.active_view.$container) this.active_view.$container.hide();
}
- this.active_view = this.control_panel.active_view = view;
+ this.active_view = view;
if (!view.created) {
view.created = this.create_view.bind(this)(view, view_options);
}
- // Tell the ControlPanel to call do_search on its searchview
- var active_search = this.control_panel.activate_search(view.created);
- return $.when(view.created, active_search).done(function () {
+ // Call do_search on the searchview to compute domains, contexts and groupbys
+ if (this.searchview &&
+ this.flags.auto_search &&
+ view.controller.searchable !== false) {
+ this.active_search = $.Deferred();
+ $.when(this.search_view_loaded, view.created).done(function() {
+ self.searchview.do_search();
+ });
+ }
+ return $.when(view.created, this.active_search).done(function () {
self._display_view(view_options);
self.trigger('switch_mode', view_type, no_store, view_options);
});
@@ -165,17 +220,21 @@ var ViewManager = Widget.extend({
this.active_view.$container.show();
$.when(this.active_view.controller.do_show(view_options)).done(function () {
// Tell the ControlPanel to update its elemnts
- self.control_panel.update(self.active_view);
+ if (self.cp_bus) {
+ var search_view_hidden = self.active_view.controller.searchable === false;
+ var breadcrumbs = self.action_manager.get_breadcrumbs();
+ self.cp_bus.trigger("update", self.active_view, search_view_hidden, breadcrumbs);
+ }
});
},
create_view: function(view, view_options) {
var self = this,
- View = this.registry.get_object(view.type),
+ View = this.registry.get(view.type),
options = _.clone(view.options),
view_loaded = $.Deferred();
- if (view.type === "form" && ((this.action && (this.action.target === 'new' || this.action.target === 'inline')) ||
- (view_options && view_options.mode === 'edit'))) {
+ if (view.type === "form" && ((this.action && (this.action.target === 'new' || this.action.target === 'inline'))
+ || (view_options && view_options.mode === 'edit'))) {
options.initial_mode = 'edit';
}
var controller = new View(this, this.dataset, view.view_id, options),
@@ -193,7 +252,10 @@ var ViewManager = Widget.extend({
if (self.action_manager) self.action_manager.trigger('history_back');
});
controller.on("change:title", this, function() {
- self.control_panel.update_breadcrumbs();
+ if (self.cp_bus) {
+ var breadcrumbs = self.action_manager.get_breadcrumbs();
+ self.cp_bus.trigger("update_breadcrumbs", breadcrumbs);
+ }
});
controller.on('view_loaded', this, function () {
view_loaded.resolve();
diff --git a/addons/web/static/src/js/views/search_menus.js b/addons/web/static/src/js/views/search_menus.js
index 54d83ba8f78..0860c8aa553 100644
--- a/addons/web/static/src/js/views/search_menus.js
+++ b/addons/web/static/src/js/views/search_menus.js
@@ -37,7 +37,7 @@ return Widget.extend({
this.$save_name = this.$('.oe-save-name');
this.$inputs = this.$save_name.find('input');
this.$divider = this.$('.divider');
- this.$inputs.eq(0).val(this.searchview.getParent().title);
+ this.$inputs.eq(0).val(this.searchview.get_title());
var $shared_filter = this.$inputs.eq(1),
$default_filter = this.$inputs.eq(2);
$shared_filter.click(function () {$default_filter.prop('checked', false);});
diff --git a/addons/web/static/src/js/views/search_view.js b/addons/web/static/src/js/views/search_view.js
index af18bdea9ac..6e78e69b466 100644
--- a/addons/web/static/src/js/views/search_view.js
+++ b/addons/web/static/src/js/views/search_view.js
@@ -376,6 +376,7 @@ var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{
this.query = undefined;
this.dataset = dataset;
this.view_id = view_id;
+ this.title = options.action && options.action.name;
this.search_fields = [];
this.filters = [];
this.groupbys = [];
@@ -411,6 +412,9 @@ var SearchView = Widget.extend(/** @lends instance.web.SearchView# */{
.toggleClass('fa-caret-up', this.visible_filters);
return this.alive($.when(this._super(), this.alive(load_view).then(this.view_loaded.bind(this))));
},
+ get_title: function() {
+ return this.title;
+ },
view_loaded: function (r) {
var custom_filters_ready;
this.fields_view_get = r;
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 3758aec3366..d148ce2f17a 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -225,7 +225,6 @@
- |
|
@@ -470,42 +469,45 @@
-