diff --git a/addons/account/static/src/js/widgets.js b/addons/account/static/src/js/widgets.js index 852acbc3795..dab4b98860a 100644 --- a/addons/account/static/src/js/widgets.js +++ b/addons/account/static/src/js/widgets.js @@ -539,12 +539,12 @@ var bankStatementReconciliation = Widget.extend({ .call("get_object_reference", ['account', 'action_bank_statement_tree']) .then(function (result) { var action_id = result[1]; - var breadcrumbs = self.action_manager.get_states(); - var state = _.find(breadcrumbs, function(state){ - return state.widget.action && state.widget.action.id === action_id; + var action_stack = self.action_manager.get_action_stack(); + var action = _.find(action_stack, function(action){ + return action.get_action_descr() && action.get_action_descr().id === action_id; }); - if (state) { - self.action_manager.select_state(state, 0); + if (action) { + self.action_manager.select_action(action, 0); } else { self.action_manager.do_action(action_id, { clear_breadcrumbs: true diff --git a/addons/web/static/src/js/action_manager.js b/addons/web/static/src/js/action_manager.js index b212e328433..63f8fceaeb2 100644 --- a/addons/web/static/src/js/action_manager.js +++ b/addons/web/static/src/js/action_manager.js @@ -12,94 +12,144 @@ var session = require('web.session'); var ViewManager = require('web.ViewManager'); var Widget = require('web.Widget'); -var State = core.Class.extend({ - init: function(title, flags) { - this.title = title; - this.flags = flags; +/** + * Class representing the actions of the ActionManager + * Basic implementation for client actions that are functions + */ +var Action = core.Class.extend({ + init: function(action) { + this.action_descr = action; + this.title = action.display_name || action.name; + }, + /** + * This method should restore this previously loaded action + * Calls on_reverse_breadcrumb callback if defined + * @return {Deferred} resolved when widget is enabled + */ + restore: function() { + if (this.__on_reverse_breadcrumb) { + return this.__on_reverse_breadcrumb(); + } + }, + /** + * Destroyer: there is nothing to destroy in the case of a client function + */ + destroy: function() { + }, + /** + * Sets the on_reverse_breadcrumb callback to be called when coming back to that action + * @param {Function} [on_reverse_breadcrumb] the callback + */ + set_on_reverse_breadcrumb: function(on_reverse_breadcrumb) { + this.on_reverse_breadcrumb = on_reverse_breadcrumb; + }, + /** + * @return {Object} the description of the action + */ + get_action_descr: function() { + return this.action_descr; + }, + /** + * @return {Object} dictionnary that will be interpreted to display the breadcrumbs + */ + get_breadcrumbs: function() { + return { title: this.title, action: this }; + }, + /** + * @return {int} the number of views stacked, i.e. 0 for client functions + */ + get_nb_views: function() { + return 0; }, - enable: function() { return $.when(); }, - disable: function() {}, - destroy: function() {}, - get_widget: function() {}, - get_title: function() { return this.title; }, - is_headless: function() { return this.flags.headless; }, - get_action: function() {}, - get_active_view: function() {}, - get_dataset: function() {}, - get_searchview: function() {}, }); - -var WidgetState = State.extend({ - init: function(title, flags, widget) { - this._super(title, flags); +/** + * Specialization of Action for client actions that are Widgets + */ +var WidgetAction = Action.extend({ + /** + * Initializes the WidgetAction + * Sets the title of the widget + */ + init: function(action, widget) { + this._super(action); this.widget = widget; - if (this.widget.get('title')) { - this.title = title; - } else { - this.widget.set('title', title); + if (!this.widget.get('title')) { + this.widget.set('title', this.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; }, - get_dataset: function() { return this.widget.dataset; }, - get_searchview: function() { return this.widget.get_search_view(); }, + /** + * Restores WidgetAction by calling do_show on its widget + */ + restore: function() { + this.widget.do_show(); + return this._super(); + }, + /** + * Destroys the widget + */ 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; }} - }] - }; - } +/** + * Specialization of WidgetAction for window actions (i.e. ViewManagers) + */ +var ViewManagerAction = WidgetAction.extend({ + /** + * Restores a ViewManagerAction + * Switches to the requested view by calling select_view on the ViewManager + * @param {int} [view_index] the index of the view to select + */ + restore: function(view_index) { + var self = this; + var _super = this._super; + return this.widget.select_view(view_index).then(function() { + _super.call(self); + }); + }, + /** + * @return {Array} array of Objects that will be interpreted to display the breadcrumbs + */ + get_breadcrumbs: function() { + var self = this; + return this.widget.view_stack.map(function (view, index) { + return { + title: view.controller.get('title') || self.title, + index: index, + action: self, + }; + }); + }, + /** + * @return {int} the number of views stacked in the ViewManager + */ + get_nb_views: function() { + return this.widget.view_stack.length; + }, }); var ActionManager = Widget.extend({ template: "ActionManager", init: function(parent) { this._super(parent); + this.action_stack = []; this.inner_action = null; this.inner_widget = null; this.webclient = parent; this.dialog = null; this.dialog_widget = null; - 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 + // Instantiate a unique main ControlPanel used by widgets of actions in this.action_stack 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); + // clicking on a part of the breadcrumbs. Call select_action for this breadcrumb. + this.main_control_panel.on("on_breadcrumb_click", this, function(action, index) { + this.select_action(action, index); }); // Append the main control panel to the DOM (inside the ActionManager jQuery element) @@ -112,90 +162,76 @@ var ActionManager = Widget.extend({ this.dialog = null; }, /** - * Add a new state to the action manager + * Add a new action to 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 + * widget: typically, widgets added are openerp.web.ViewManager. The action manager + * uses the stack of actions to handle the breadcrumbs. + * action_descr: new action description + * options.on_reverse_breadcrumb: will be called when breadcrumb is clicked on + * options.clear_breadcrumbs: boolean, if true, action stack is destroyed */ - push_state: function(widget, action, options) { - var self = this, - to_destroy, - old_widget = this.inner_widget, - old_state = this.get_current_state(); + push_action: function(widget, action_descr, options) { + var self = this; + var to_destroy; + var old_widget = this.inner_widget; + var old_action = this.inner_action; options = options || {}; + // Empty action_stack if requested if (options.clear_breadcrumbs) { - to_destroy = this.states; - this.states = []; + to_destroy = this.action_stack; + this.action_stack = []; } - var new_state, - title = action.display_name || action.name; - if (widget instanceof Widget) { - new_state = new WidgetState(title, action.flags, widget); + + // Instantiate the new action + var new_action; + if (widget instanceof ViewManager) { + new_action = new ViewManagerAction(action_descr, widget); + } else if (widget instanceof Widget) { + new_action = new WidgetAction(action_descr, widget); } else { - new_state = new FunctionState(title, action.flags); + new_action = new Action(action_descr); } - this.states.push(new_state); - this.get_current_state().__on_reverse_breadcrumb = options.on_reverse_breadcrumb; - this.inner_action = action; + // Set on_reverse_breadcrumb callback on previous inner_action + if (old_action) { + old_action.set_on_reverse_breadcrumb(options.on_reverse_breadcrumb); + } + + // Update action_stack + this.action_stack.push(new_action); + this.inner_action = new_action; this.inner_widget = widget; - // 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') { - this.main_control_panel.set_state(new_state, old_state); - if (old_state) { - // Save the previous state control_panel content to restore it later - old_state.cp_content = this.main_control_panel.get_content(); - } - } - // Append the inner_widget and hide the old one return $.when(this.inner_widget.appendTo(this.$el)).done(function () { if (old_widget) { old_widget.$el.hide(); } if (options.clear_breadcrumbs) { - self.clear_states(to_destroy); + self.clear_action_stack(to_destroy); } }); }, get_breadcrumbs: function () { - 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') || state.get_title(), - index: index, - widget: state, - }; - }); - } else { - return { title: state.get_title(), widget: state }; - } + return _.flatten(_.map(this.action_stack, function (action) { + return action.get_breadcrumbs(); }), true); }, get_title: function () { - if (this.states.length === 1) { + if (this.action_stack.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 state = this.states[0]; - if (state.widget instanceof ViewManager && state.widget.view_stack.length === 1) { - return state.get_title(); + var action = this.action_stack[0]; + if (action.get_breadcrumbs().length === 1) { + return action.title; } } return _.pluck(this.get_breadcrumbs(), 'title').join(' / '); }, - get_states: function () { - return this.states; - }, - get_current_state: function() { - return _.last(this.states); + get_action_stack: function () { + return this.action_stack; }, get_inner_action: function() { return this.inner_action; @@ -204,54 +240,38 @@ var ActionManager = Widget.extend({ return this.inner_widget; }, history_back: function() { - 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_state(state, nbr_views - 2); - } - } - 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); + var nb_views = this.inner_action.get_nb_views(); + if (nb_views > 1) { + // Stay on this action, but select the previous view + return this.select_action(this.inner_action, nb_views - 2); + } + if (this.action_stack.length > 1) { + // Select the previous action + var action = this.action_stack[this.action_stack.length - 2]; + nb_views = action.get_nb_views(); + return this.select_action(action, nb_views - 1); } return $.Deferred().reject(); }, - select_state: function(state, index) { - var self = this; + select_action: function(action, index) { if (this.webclient.has_uncommitted_changes()) { return $.Deferred().reject(); } - // 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 (mainly restore the searchview - // for the ViewManager to be able to do select_view, i.e. switch_mode) - // Storing in old_state is not necessary here - var old_state = this.get_current_state(); - this.main_control_panel.set_state(state, old_state); - var state_index = this.states.indexOf(state), - def = $.when(state.widget.select_view && state.widget.select_view(index)); + // Set the new inner_widget and clear the action_stack + this.inner_widget = action.widget; + var action_index = this.action_stack.indexOf(action); + this.clear_action_stack(this.action_stack.splice(action_index + 1)); - this.clear_states(this.states.splice(state_index + 1)); - this.inner_widget = state.widget; - return def.done(function () { - if (self.inner_widget.do_show) { - self.inner_widget.do_show(); - } - }); + return action.restore(index); }, - clear_states: function(states) { - _.map(states || this.states, function(state) { - state.destroy(); + clear_action_stack: function(action_stack) { + _.map(action_stack || this.action_stack, function(action) { + action.destroy(); }); - if (!states) { - this.states = []; + if (!action_stack) { + this.action_stack = []; + this.inner_action = null; this.inner_widget = null; } }, @@ -261,47 +281,48 @@ var ActionManager = Widget.extend({ } state = state || {}; if (this.inner_action) { - if (this.inner_action._push_me === false) { + var inner_action_descr = this.inner_action.get_action_descr(); + if (inner_action_descr._push_me === false) { // this action has been explicitly marked as not pushable return; } state.title = this.get_title(); - if(this.inner_action.type == 'ir.actions.act_window') { - state.model = this.inner_action.res_model; + if(inner_action_descr.type == 'ir.actions.act_window') { + state.model = inner_action_descr.res_model; } - if (this.inner_action.menu_id) { - state.menu_id = this.inner_action.menu_id; + if (inner_action_descr.menu_id) { + state.menu_id = inner_action_descr.menu_id; } - if (this.inner_action.id) { - state.action = this.inner_action.id; - } else if (this.inner_action.type == 'ir.actions.client') { - state.action = this.inner_action.tag; + if (inner_action_descr.id) { + state.action = inner_action_descr.id; + } else if (inner_action_descr.type == 'ir.actions.client') { + state.action = inner_action_descr.tag; var params = {}; - _.each(this.inner_action.params, function(v, k) { + _.each(inner_action_descr.params, function(v, k) { if(_.isString(v) || _.isNumber(v)) { params[k] = v; } }); state = _.extend(params || {}, state); } - if (this.inner_action.context) { - var active_id = this.inner_action.context.active_id; + if (inner_action_descr.context) { + var active_id = inner_action_descr.context.active_id; if (active_id) { state.active_id = active_id; } - var active_ids = this.inner_action.context.active_ids; + var active_ids = inner_action_descr.context.active_ids; if (active_ids && !(active_ids.length === 1 && active_ids[0] === active_id)) { // We don't push active_ids if it's a single element array containing the active_id // This makes the url shorter in most cases. - state.active_ids = this.inner_action.context.active_ids.join(','); + state.active_ids = inner_action_descr.context.active_ids.join(','); } } } this.webclient.do_push_state(state); }, do_load_state: function(state, warm) { - var self = this, - action_loaded; + var self = this; + var action_loaded; if (state.action) { if (_.isString(state.action) && core.action_registry.contains(state.action)) { var action_client = { @@ -338,8 +359,8 @@ var ActionManager = Widget.extend({ action_loaded = this.do_action(state.action, { additional_context: add_context }); $.when(action_loaded || null).done(function() { self.webclient.menu.is_bound.done(function() { - if (self.inner_action && self.inner_action.id) { - self.webclient.menu.open_action(self.inner_action.id); + if (self.inner_action && self.inner_action.get_action_descr().id) { + self.webclient.menu.open_action(self.inner_action.get_action_descr().id); } }); }); @@ -478,15 +499,15 @@ var ActionManager = Widget.extend({ }, null_action: function() { this.dialog_stop(); - this.clear_states(); + this.clear_action_stack(); }, /** * * @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 {*} */ @@ -544,7 +565,7 @@ var ActionManager = Widget.extend({ } widget = executor.widget(); this.dialog_stop(executor.action); - return this.push_state(widget, executor.action, options); + return this.push_action(widget, executor.action, options); }, ir_actions_act_window: function (action, options) { var self = this; @@ -562,7 +583,6 @@ var ActionManager = Widget.extend({ if (!ClientWidget) { return self.do_warn("Action Error", "Could not find client action '" + action.tag + "'."); } - if (!(ClientWidget.prototype instanceof Widget)) { var next; if ((next = ClientWidget(this, action))) { diff --git a/addons/web/static/src/js/control_panel.js b/addons/web/static/src/js/control_panel.js index 768f7179511..2064ada44b9 100644 --- a/addons/web/static/src/js/control_panel.js +++ b/addons/web/static/src/js/control_panel.js @@ -2,16 +2,10 @@ odoo.define('web.ControlPanel', function (require) { "use strict"; var core = require('web.core'); -var SearchView = require('web.SearchView'); var Widget = require('web.Widget'); -var QWeb = core.qweb; - var ControlPanel = Widget.extend({ template: 'ControlPanel', - events: { - "click .oe-cp-switch-buttons button": "on_switch_buttons_click", - }, /** * @param {String} [template] the QWeb template to render the ControlPanel. * By default, the template 'ControlPanel' will be used @@ -23,13 +17,8 @@ var ControlPanel = Widget.extend({ } this.bus = new core.Bus(); - this.bus.on("setup_search_view", this, this.setup_search_view); - this.bus.on("render_switch_buttons", this, this.render_switch_buttons); this.bus.on("update", this, this.update); this.bus.on("update_breadcrumbs", this, this.update_breadcrumbs); - - this.state = null; - this.searchview = null; }, start: function() { // Retrieve ControlPanel jQuery nodes @@ -54,36 +43,6 @@ var ControlPanel = Widget.extend({ get_bus: function() { return this.bus; }, - /** - * @return {Object} the current State of the ControlPanel - */ - get_state: function() { - return this.state; - }, - /** - * 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) - * @param {Object} [state] the new state - */ - set_state: function(state) { - this.state = state; - // Set the new state searchview - this.searchview = state.get_searchview(); - }, - /** - * Returns the content of the ControlPanel without detaching anything from it - * @return {Object} dictionnary containing the detached jQuery elements - */ - get_content: function() { - return { - '$buttons': this.$buttons.contents(), - '$switch_buttons': this.$switch_buttons.contents(), - '$pager': this.$pager.contents(), - '$sidebar': this.$sidebar.contents(), - '$searchview': this.$searchview.contents(), - '$searchview_buttons': this.$searchview_buttons.contents(), - }; - }, /** * Hides (or shows) the ControlPanel in headless (resp. non-headless) mode * Also detaches or attaches its contents to clean the DOM @@ -99,15 +58,14 @@ var ControlPanel = Widget.extend({ }, /** * Detaches the content of the ControlPanel - * @return {Object} dictionnary containing the detached jQuery elements */ _detach_content: function() { - var cp_content = this.get_content(); - _.each(cp_content, function($content) { - $content.detach(); - }); - - return cp_content; + this.$buttons.contents().detach(); + this.$switch_buttons.contents().detach(); + this.$pager.contents().detach(); + this.$sidebar.contents().detach(); + this.$searchview.contents().detach(); + this.$searchview_buttons.contents().detach(); }, /** * Attaches content to the ControlPanel @@ -139,50 +97,20 @@ var ControlPanel = Widget.extend({ // again in the ControlPanel will removed them from there they should be this._detach_content(); this._attach_content(status.cp_content); - if (status.active_view) this.update_switch_buttons(status.active_view); + if (status.active_view_selector) this.update_switch_buttons(status.active_view_selector); if (status.searchview) this.update_search_view(status.searchview, status.search_view_hidden); if (status.breadcrumbs) this.update_breadcrumbs(status.breadcrumbs); } }, - /** - * Renders the switch buttons and call set_switch_buttons on the src - * Does not append the switch buttons to the DOM - * @param {Object} [src] the source requesting the switch_buttons - * @param {Array} [views] the array of views - */ - render_switch_buttons: function(src, views) { - if (views.length > 1) { - var self = this; - - // Render switch buttons but do not append them to the DOM as this will - // be done later, simultaneously to all other ControlPanel elements - var $switch_buttons = $(QWeb.render('ControlPanel.switch-buttons', {views: views})); - - // Create bootstrap tooltips - _.each(views, function(view) { - self.$('.oe-cp-switch-' + view.type).tooltip(); - }); - } - - // Set the buttons jQuery elements in the Widget which made the request - src.set_switch_buttons($switch_buttons); - }, - /** - * Triggers an event when switch-buttons are clicked on - */ - on_switch_buttons_click: function(event) { - var view_type = $(event.target).data('view-type'); - this.trigger('switch_view', view_type); - }, /** * Removes active class on all switch-buttons and adds it to the one of the active view - * @param {Object} [active_view] the active_view + * @param {Object} [active_view_selector] the selector of the div to activate */ - update_switch_buttons: function(active_view) { - _.each(this.$switch_buttons.contents('button'), function(button) { + update_switch_buttons: function(active_view_selector) { + _.each(this.$switch_buttons.find('button'), function(button) { $(button).removeClass('active'); }); - this.$('.oe-cp-switch-' + active_view.type).addClass('active'); + this.$(active_view_selector).addClass('active'); }, /** * Updates the breadcrumbs @@ -206,48 +134,12 @@ var ControlPanel = Widget.extend({ .toggleClass('active', is_last); if (!is_last) { $bc.click(function () { - self.trigger("on_breadcrumb_click", bc.widget, bc.index); + self.trigger("on_breadcrumb_click", bc.action, bc.index); }); } return $bc; } }, - /** - * Sets up the search view and calls set_search_view on the widget requesting it - * - * @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(src, action, dataset, flags) { - var view_id = (action && action.search_view_id && action.search_view_id[0]) || false; - - var search_defaults = {}; - - var context = action ? action.context : []; - _.each(context, function (value, key) { - var match = /^search_default_(.*)$/.exec(key); - if (match) { - search_defaults[match[1]] = value; - } - }); - - var options = { - hidden: flags.search_view === false, - disable_custom_filters: flags.search_disable_custom_filters, - $buttons: $("
"), - action: action, - }; - - // Instantiate the SearchView, but do not append it nor its buttons to the DOM as this will - // be done later, simultaneously to all other ControlPanel elements - var searchview = new SearchView(this, dataset, view_id, search_defaults, options); - searchview.appendTo($("
")).then(function() { - // Set the SearchView in the widget which made the request - src.set_search_view(searchview); - }); - }, /** * Updates the SearchView's visibility and extend the breadcrumbs area if the SearchView is not visible * @param {openerp.web.SearchView} [searchview] the searchview Widget diff --git a/addons/web/static/src/js/view_manager.js b/addons/web/static/src/js/view_manager.js index 7a141dcba05..de604245554 100644 --- a/addons/web/static/src/js/view_manager.js +++ b/addons/web/static/src/js/view_manager.js @@ -5,8 +5,10 @@ var core = require('web.core'); var data = require('web.data'); var Model = require('web.Model'); var pyeval = require('web.pyeval'); +var SearchView = require('web.SearchView'); var Widget = require('web.Widget'); +var QWeb = core.qweb; var _t = core._t; var ViewManager = Widget.extend({ @@ -46,10 +48,10 @@ var ViewManager = Widget.extend({ this.cp_bus = cp_bus; _.each(views, function (view) { - var view_type = view[1] || view.view_type, - View = core.view_registry.get(view_type, true), - view_label = View ? View.prototype.display_name: (void 'nope'), - view_descr = { + var view_type = view[1] || view.view_type; + var View = core.view_registry.get(view_type, true); + var view_label = View ? View.prototype.display_name: (void 'nope'); + var view_descr = { controller: null, options: view.options || {}, view_id: view[0] || view.view_id, @@ -77,8 +79,8 @@ var ViewManager = Widget.extend({ */ start: function() { var self = this; - var default_view = this.get_default_view(), - default_options = this.flags[default_view] && this.flags[default_view].options; + var default_view = this.get_default_view(); + var default_options = this.flags[default_view] && this.flags[default_view].options; this._super(); @@ -97,89 +99,24 @@ var ViewManager = Widget.extend({ if (this.cp_bus) { this.control_elements = {}; if (this.flags.search_view) { - // Ask the ControlPanel to setup the 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); - }); + this.search_view_loaded = this.setup_search_view(); } if (this.flags.views_switcher) { - // Ask the ControlPanel to render the switch-buttons - self.cp_bus.trigger('render_switch_buttons', self, self.view_order); + this.render_switch_buttons(); } } // Switch to the default_view to load it - this.main_view_loaded = this.switch_mode(default_view, null, default_options); + var main_view_loaded = this.switch_mode(default_view, null, default_options); - return $.when(self.main_view_loaded, this.search_view_loaded); - }, - /** - * Executed by the ControlPanel when the searchview requested by this ViewManager is loaded - * @param {Widget} [searchview] the SearchView - */ - set_search_view: function(searchview) { - this.searchview = searchview; - _.extend(this.control_elements, { - $searchview: searchview.$el, - $searchview_buttons: searchview.$buttons.contents(), - }); - this.search_view_loaded.resolve(); - }, - /** - * Executed by the ControlPanel when the switch_buttons requested by this ViewManager are rendered - * @param {jQuery} [$switch_buttons] the rendered switch_buttons - */ - set_switch_buttons: function($switch_buttons) { - _.extend(this.control_elements, { - $switch_buttons: $switch_buttons, - }); - }, - get_search_view: function() { - return this.searchview; - }, - /** - * Executed on event "search_data" thrown by the SearchView - */ - search: function(domains, contexts, groupbys) { - var self = this, - controller = this.active_view.controller, // the 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(); - }); - }); + return $.when(main_view_loaded, this.search_view_loaded); }, get_default_view: function() { return this.flags.default_view || this.view_order[0].type; }, switch_mode: function(view_type, no_store, view_options) { - var self = this, - view = this.views[view_type]; + var self = this; + var view = this.views[view_type]; if (!view) { return $.Deferred().reject(); @@ -226,7 +163,7 @@ var ViewManager = Widget.extend({ // Render the control elements of the active view (buttons, sidebar, pager) var view_elements = self.render_view_control_elements(); var status = { - active_view: self.active_view, + active_view_selector: '.oe-cp-switch-' + self.active_view.type, breadcrumbs: self.action_manager && self.action_manager.get_breadcrumbs(), cp_content: _.extend({}, self.control_elements, view_elements), hidden: self.flags.headless, @@ -239,17 +176,17 @@ var ViewManager = Widget.extend({ }); }, create_view: function(view, view_options) { - var self = this, - View = this.registry.get(view.type), - options = _.clone(view.options), - view_loaded = $.Deferred(); + var self = this; + var View = this.registry.get(view.type); + var options = _.clone(view.options); + var 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), - $container = view.$container; + var controller = new View(this, this.dataset, view.view_id, options); + var $container = view.$container; $container.hide(); view.controller = controller; @@ -281,6 +218,32 @@ var ViewManager = Widget.extend({ this.view_stack.splice(index); return this.switch_mode(view_type); }, + /** + * Renders the switch buttons and adds listeners on them but does not append them to the DOM + * Sets $switch_buttons in control_elements to send to the ControlPanel + * @param {Object} [src] the source requesting the switch_buttons + * @param {Array} [views] the array of views + */ + render_switch_buttons: function() { + if (this.flags.views_switcher && this.view_order.length > 1) { + var self = this; + + // Render switch buttons but do not append them to the DOM as this will + // be done later, simultaneously to all other ControlPanel elements + this.control_elements.$switch_buttons = $(QWeb.render('ViewManager.switch-buttons', {views: self.view_order})); + + // Create bootstrap tooltips + _.each(this.views, function(view) { + self.control_elements.$switch_buttons.siblings('.oe-cp-switch-' + view.type).tooltip(); + }); + + // Add onclick event listener + this.control_elements.$switch_buttons.siblings('button').click(function() { + var view_type = $(event.target).data('view-type'); + self.switch_mode(view_type); + }); + } + }, /** * Renders the control elements (buttons, sidebar, pager) of the current view * This must be done when active_search is resolved (for KanbanViews) @@ -315,6 +278,83 @@ var ViewManager = Widget.extend({ get_view_id: function(view_type) { return this.views[view_type] && this.views[view_type].view_id || false; }, + /** + * Sets up the current viewmanager's search view. + * Sets $searchview and $searchview_buttons in control_elements to send to the ControlPanel + * + * @param {Number|false} view_id the view to use or false for a default one + * @returns {jQuery.Deferred} search view startup deferred + */ + setup_search_view: function() { + var self = this; + if (this.searchview) { + this.searchview.destroy(); + } + + var view_id = (this.action && this.action.search_view_id && this.action.search_view_id[0]) || false; + + var search_defaults = {}; + + var context = this.action ? this.action.context : []; + _.each(context, function (value, key) { + var match = /^search_default_(.*)$/.exec(key); + if (match) { + search_defaults[match[1]] = value; + } + }); + + + var options = { + hidden: this.flags.search_view === false, + disable_custom_filters: this.flags.search_disable_custom_filters, + $buttons: $("
"), + action: this.action, + }; + // Instantiate the SearchView, but do not append it nor its buttons to the DOM as this will + // be done later, simultaneously to all other ControlPanel elements + this.searchview = new SearchView(this, this.dataset, view_id, search_defaults, options); + + this.searchview.on('search_data', this, this.search.bind(this)); + return $.when(this.searchview.appendTo($("
"))).done(function() { + self.control_elements.$searchview = self.searchview.$el; + self.control_elements.$searchview_buttons = self.searchview.$buttons.contents(); + }); + }, + /** + * Executed on event "search_data" thrown by the SearchView + */ + search: function(domains, contexts, groupbys) { + var self = this; + var controller = this.active_view.controller; // the correct view must be loaded here + var action_context = this.action.context || {}; + var 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(); + }); + }); + }, do_push_state: function(state) { if (this.action_manager) { state.view_type = this.active_view.type; diff --git a/addons/web/static/src/js/widgets/debug_manager.js b/addons/web/static/src/js/widgets/debug_manager.js index 4fffdab9294..0bd7a772425 100644 --- a/addons/web/static/src/js/widgets/debug_manager.js +++ b/addons/web/static/src/js/widgets/debug_manager.js @@ -26,9 +26,9 @@ if (core.debug) { this.$dropdown = this.$(".js_debug_dropdown"); }, /** - * Updates its attributes according to the current state of the ActionManager + * Updates its attributes according to the inner_widget of the ActionManager */ - _update_state: function() { + _update: function() { this.view_manager = odoo.__DEBUG__.services['web.web_client'].action_manager.get_inner_widget(); if (!this.view_manager instanceof ViewManager) { return; } this.dataset = this.view_manager.dataset; @@ -38,7 +38,7 @@ if (core.debug) { return true; }, /** - * Renders dropdown according to the current state + * Renders the DebugManager dropdown */ render_dropdown: function() { var self = this; @@ -46,8 +46,8 @@ if (core.debug) { // Empty the previously rendered dropdown this.$dropdown.empty(); - // Attempt to retrieve the current state of the ActionManager - if (!this._update_state()) { + // Attempt to retrieve the inner_widget of the ActionManager + if (!this._update()) { // Disable the button when not available console.warn("DebugManager is not available"); return; diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 25f490e86ed..a630510f058 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -495,12 +495,6 @@
- - -