diff --git a/addons/base_import/static/src/js/import.js b/addons/base_import/static/src/js/import.js index 8843cde167c..991dbee7462 100644 --- a/addons/base_import/static/src/js/import.js +++ b/addons/base_import/static/src/js/import.js @@ -47,13 +47,18 @@ function jsonp(form, attributes, callback) { // if true, the 'Import', 'Export', etc... buttons will be shown ListView.prototype.defaults.import_enabled = true; ListView.include({ - load_list: function () { + /** + * Extend the render_buttons function of ListView by adding an event listener + * on the import button. + * @return {jQuery} the rendered buttons + */ + render_buttons: function() { var self = this; var add_button = false; - if (!this.$buttons) { + if (!this.$buttons) { // Ensures that this is only done once add_button = true; } - this._super.apply(this, arguments); + this._super.apply(this, arguments); // Sets this.$buttons if(add_button) { this.$buttons.on('click', '.oe_list_button_import', function() { self.do_action({ @@ -75,6 +80,7 @@ ListView.include({ return false; }); } + return this.$buttons; } }); diff --git a/addons/board/static/src/js/dashboard.js b/addons/board/static/src/js/dashboard.js index b2c91b23c5e..72c4eee1eca 100644 --- a/addons/board/static/src/js/dashboard.js +++ b/addons/board/static/src/js/dashboard.js @@ -204,7 +204,7 @@ var DashBoard = form_common.FormWidget.extend({ } action.flags = { - search_view : false, + search_view : true, // Required to get the records sidebar : false, views_switcher : false, action_buttons : false, diff --git a/addons/google_drive/static/src/js/gdrive.js b/addons/google_drive/static/src/js/gdrive.js index 87de791b480..f7d33a53979 100644 --- a/addons/google_drive/static/src/js/gdrive.js +++ b/addons/google_drive/static/src/js/gdrive.js @@ -6,7 +6,7 @@ var Model = require('web.Model'); var Sidebar = require('web.Sidebar'); Sidebar.include({ - start: function () { + init: function () { var self = this; var ids; this._super.apply(this, arguments); diff --git a/addons/share/static/src/js/share.js b/addons/share/static/src/js/share.js index 2fec003e9ff..0b8923d60ae 100644 --- a/addons/share/static/src/js/share.js +++ b/addons/share/static/src/js/share.js @@ -61,10 +61,9 @@ function has_share(yes, no) { /* Extend the Sidebar to add Share and Embed links in the 'More' menu */ Sidebar.include({ - - start: function() { + init: function(parent) { var self = this; - this._super(this); + this._super(parent); has_share(function() { self.add_items('other', [ { label: _t('Share'), diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 9ff0c1d103f..740b8b452fe 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -361,21 +361,6 @@ background: #dc5f59; font-weight: bold; } -.openerp .oe_form_dirty button.oe_highlight_on_dirty { - background-color: #c74a44; - background-image: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630)); - background-image: -webkit-linear-gradient(top, #dc5f59, #b33630); - background-image: -moz-linear-gradient(top, #dc5f59, #b33630); - background-image: -ms-linear-gradient(top, #dc5f59, #b33630); - background-image: -o-linear-gradient(top, #dc5f59, #b33630); - background-image: linear-gradient(to bottom, #dc5f59, #b33630); - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} -.openerp .oe_form_dirty button.oe_highlight_on_dirty:hover { - background: #ED6F6A; -} .openerp .oe_warning_redirect { border: none !important; padding: 0 !important; @@ -1081,9 +1066,6 @@ .openerp .oe-control-panel .oe-cp-buttons { display: inline-block; } -.openerp .oe-control-panel .oe-cp-buttons > div { - display: none; -} .openerp .oe-control-panel .oe-cp-sidebar { display: inline-block; float: right; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index d5011ea2abb..665fc6dcdc4 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -370,11 +370,6 @@ $sheet-padding: 16px color: white background: #dc5f59 font-weight: bold - button.oe_highlight_on_dirty - @include vertical-gradient(#dc5f59, #b33630) - @include box-shadow(none) - &:hover - background: #ED6F6A .oe_warning_redirect border: none !important padding: 0 !important @@ -922,8 +917,6 @@ $sheet-padding: 16px content: "\f036" .oe-cp-buttons display: inline-block - > div - display: none .oe-cp-sidebar display: inline-block float: right diff --git a/addons/web/static/src/js/action_manager.js b/addons/web/static/src/js/action_manager.js index 5ae35308bfc..b212e328433 100644 --- a/addons/web/static/src/js/action_manager.js +++ b/addons/web/static/src/js/action_manager.js @@ -22,7 +22,7 @@ var State = core.Class.extend({ destroy: function() {}, get_widget: function() {}, get_title: function() { return this.title; }, - get_flags: function() { return this.flags; }, + is_headless: function() { return this.flags.headless; }, get_action: function() {}, get_active_view: function() {}, get_dataset: function() {}, @@ -147,17 +147,14 @@ var ActionManager = Widget.extend({ // 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()); + 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(); @@ -175,7 +172,7 @@ var ActionManager = Widget.extend({ title: view.controller.get('title') || state.get_title(), index: index, widget: state, - }; + }; }); } else { return { title: state.get_title(), widget: state }; @@ -233,17 +230,16 @@ var ActionManager = Widget.extend({ } // 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()); - + // 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)); - 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; + 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(); @@ -269,7 +265,7 @@ var ActionManager = Widget.extend({ // this action has been explicitly marked as not pushable return; } - state.title = this.get_title(); + state.title = this.get_title(); if(this.inner_action.type == 'ir.actions.act_window') { state.model = this.inner_action.res_model; } @@ -307,7 +303,7 @@ var ActionManager = Widget.extend({ var self = this, action_loaded; if (state.action) { - if (_.isString(state.action) && core.action_regisry.contains(state.action)) { + if (_.isString(state.action) && core.action_registry.contains(state.action)) { var action_client = { type: "ir.actions.client", tag: state.action, @@ -402,10 +398,9 @@ var ActionManager = Widget.extend({ action_menu_id: null, additional_context: {}, }); - if (action === false) { action = { type: 'ir.actions.act_window_close' }; - } else if (_.isString(action) && core.action_regisry.contains(action)) { + } else if (_.isString(action) && core.action_registry.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)) { @@ -476,6 +471,8 @@ var ActionManager = Widget.extend({ search_disable_custom_filters: action.context && action.context.search_disable_custom_filters }); } + // Do not permit popups to communicate with the main control panel + options.cp_bus = !popup && this.main_control_panel.get_bus(); return this[type](action, options); }, @@ -552,8 +549,8 @@ var ActionManager = Widget.extend({ 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, self.main_control_panel.get_bus()); + widget: function () { + return new ViewManager(self, null, null, null, action, options.cp_bus); }, action: action, klass: 'oe_act_window', @@ -576,7 +573,7 @@ var ActionManager = Widget.extend({ return this.ir_actions_common({ widget: function () { - // AAB: temporary fix: Hide main control panel as client actions do not use it + // Hide main control panel as client actions do not use it self.main_control_panel.do_hide(); return new ClientWidget(self, action); }, diff --git a/addons/web/static/src/js/control_panel.js b/addons/web/static/src/js/control_panel.js index d452c70120e..768f7179511 100644 --- a/addons/web/static/src/js/control_panel.js +++ b/addons/web/static/src/js/control_panel.js @@ -24,131 +24,156 @@ 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.bus.on("render_buttons", this, this.render_buttons); - this.bus.on("render_switch_buttons", this, this.render_switch_buttons); + this.state = null; this.searchview = null; - - this.flags = null; - this.title = null; // Needed for Favorites of searchview }, start: function() { // Retrieve ControlPanel jQuery nodes - this.$control_panel = this.$('.oe-control-panel-content'); + this.$title_col = this.$('.oe-cp-title'); this.$breadcrumbs = this.$('.oe-view-title'); - 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'); - this.$searchview = this.$(".oe-cp-search-view"); + this.$searchview = this.$('.oe-cp-search-view'); this.$searchview_buttons = this.$('.oe-search-options'); - this.$pager = this.$('.oe-cp-pager'); + this.$buttons = this.$('.oe-cp-buttons'); this.$sidebar = this.$('.oe-cp-sidebar'); + this.$pager = this.$('.oe-cp-pager'); + this.$switch_buttons = this.$('.oe-cp-switch-buttons'); + + // By default, hide the ControlPanel and remove its contents from the DOM + this.$el.hide(); + this.contents = this.$el.contents().detach(); return this._super(); }, /** - * @return {Object} Dictionnary of ControlPanel nodes + * @return {Object} the Bus the ControlPanel is listening on */ - 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) + * @return {Object} the current State of the ControlPanel */ - 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.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) { + /** + * 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 + */ + toggle_visibility: function(hidden) { + this.$el.toggle(!hidden); + if (hidden && !this.contents) { + this.contents = this.$el.contents().detach(); + } else if (this.contents) { + this.contents.appendTo(this.$el); + this.contents = null; + } + }, + /** + * 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; + }, + /** + * Attaches content to the ControlPanel + * @param {Object} [content] dictionnary of jQuery elements to attach, whose keys + * are jQuery nodes identifiers + */ + _attach_content: function(content) { 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(); + _.each(content, function($nodeset, $element) { + if ($nodeset && self[$element]) { + $nodeset.appendTo(self[$element]); + } }); }, - render_switch_buttons: function(views) { + /** + * Updates the content and display of the ControlPanel + * @param {Object} [status.active_view] the current active view + * @param {Array} [status.breadcrumbs] the breadcrumbs to display + * @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 + */ + update: function(status) { + this.toggle_visibility(status.hidden); + if (!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 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.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; - var switch_buttons = QWeb.render('ControlPanel.switch-buttons', {views: views}); - $(switch_buttons).appendTo(this.$switch_buttons); + // 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 tooltips + // 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); }, - /** - * 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, search_view_hidden, breadcrumbs) { - this.update_switch_buttons(active_view); - this.update_search_view(search_view_hidden); - this.update_breadcrumbs(breadcrumbs); - }, /** * Removes active class on all switch-buttons and adds it to the one of the active view * @param {Object} [active_view] the active_view @@ -196,7 +221,6 @@ var ControlPanel = Widget.extend({ * @param {Object} [flags] a dictionnary of Booleans */ 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 = {}; @@ -212,26 +236,29 @@ var ControlPanel = Widget.extend({ var options = { hidden: flags.search_view === false, disable_custom_filters: flags.search_disable_custom_filters, - $buttons: this.$searchview_buttons, + $buttons: $("
"), action: action, }; - // 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); + // 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 * @param {Boolean} [is_hidden] visibility of the searchview **/ - update_search_view: function(is_hidden) { - if (this.searchview) { - 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); - } + update_search_view: function(searchview, is_hidden) { + // 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.$searchview_buttons; + searchview.toggle_visibility(!is_hidden); + this.$title_col.toggleClass('col-md-6', !is_hidden).toggleClass('col-md-12', is_hidden); }, }); diff --git a/addons/web/static/src/js/framework/view.js b/addons/web/static/src/js/framework/view.js index 145989ad7aa..4ef61233a9d 100644 --- a/addons/web/static/src/js/framework/view.js +++ b/addons/web/static/src/js/framework/view.js @@ -18,7 +18,6 @@ var View = Widget.extend({ this.ViewManager = parent; this.dataset = dataset; this.view_id = view_id; - this.$buttons = options && options.$buttons; this.set_default_options(options); }, start: function () { @@ -158,9 +157,6 @@ var View = Widget.extend({ }, do_show: function () { var self = this; - if (this.$buttons) { - this.$buttons.show(); - } this.$el.show(); setTimeout(function () { self.$el.parent().addClass('in'); @@ -169,15 +165,13 @@ var View = Widget.extend({ core.bus.trigger('view_shown', this); }, do_hide: function () { - if (this.$buttons) { - this.$buttons.hide(); - } this.$el.parent().removeClass('in'); this.$el.hide(); }, is_active: function () { return this.ViewManager.active_view.controller === this; - }, /** + }, + /** * Wraps fn to only call it if the current view is the active one. If the * current view is not active, doesn't call fn. * @@ -201,6 +195,33 @@ var View = Widget.extend({ }, do_load_state: function (state, warm) { + }, + /** + * This function should render the buttons of the view, set this.$buttons to + * the produced jQuery element and define some listeners on it. + * This function should be called after start(). + * @param {jQuery} [$node] a jQuery node where the rendered buttons should be inserted + * $node may be undefined, in which case the View can insert the buttons somewhere else + */ + render_buttons: function($node) { + }, + /** + * This function should instantiate and render the sidebar of the view, set this.sidebar to + * the instantiated Sidebar Widget and possibly add custom items in it. + * This function should be called after start(). + * @param {jQuery} [$node] a jQuery node where the sidebar should be inserted + * $node may be undefined, in which case the View can insert the sidebar somewhere else + */ + render_sidebar: function($node) { + }, + /** + * This function should render the pager of the view, set this.$pager to + * the produced jQuery element and define some listeners on it. + * This function should be called after start(). + * @param {jQuery} [$node] a jQuery node where the pager should be inserted + * $node may be undefined, in which case the View can insert the pager somewhere else + */ + render_pager: function($node) { }, /** * Switches to a specific view type diff --git a/addons/web/static/src/js/view_manager.js b/addons/web/static/src/js/view_manager.js index 2438555e7a8..7a141dcba05 100644 --- a/addons/web/static/src/js/view_manager.js +++ b/addons/web/static/src/js/view_manager.js @@ -95,49 +95,46 @@ var ViewManager = Widget.extend({ this.$el.addClass("oe_view_manager_" + ((this.action && this.action.target) || 'current')); 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); - }); - - // 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); + 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); + }); + } + if (this.flags.views_switcher) { + // Ask the ControlPanel to render the switch-buttons + self.cp_bus.trigger('render_switch_buttons', self, self.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); }, - /** - * Sets the external nodes in which the ViewManager and its views should insert elements - * @param {Object} [nodes] a dictionnary of jQuery nodes - */ - 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) { + set_search_view: function(searchview) { this.searchview = searchview; - this.search_view_loaded = search_view_loaded_def; + _.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; @@ -147,7 +144,7 @@ var ViewManager = Widget.extend({ */ search: function(domains, contexts, groupbys) { var self = this, - controller = this.active_view.controller, // AAB: Correct view must be loaded here + 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({ @@ -205,7 +202,7 @@ var ViewManager = Widget.extend({ } // Call do_search on the searchview to compute domains, contexts and groupbys - if (this.searchview && + if (this.search_view_loaded && this.flags.auto_search && view.controller.searchable !== false) { this.active_search = $.Deferred(); @@ -220,13 +217,24 @@ var ViewManager = Widget.extend({ }, _display_view: function (view_options) { var self = this; + var view_controller = this.active_view.controller; + + // Show the view this.active_view.$container.show(); - $.when(this.active_view.controller.do_show(view_options)).done(function () { - // Tell the ControlPanel to update its elemnts + $.when(view_controller.do_show(view_options)).done(function () { 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); + // 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, + breadcrumbs: self.action_manager && self.action_manager.get_breadcrumbs(), + cp_content: _.extend({}, self.control_elements, view_elements), + hidden: self.flags.headless, + searchview: self.searchview, + search_view_hidden: view_controller.searchable === false, + }; + // Tell the ControlPanel to update its elements + self.cp_bus.trigger('update', status); } }); }, @@ -255,7 +263,7 @@ var ViewManager = Widget.extend({ if (self.action_manager) self.action_manager.trigger('history_back'); }); controller.on("change:title", this, function() { - if (self.cp_bus) { + if (self.cp_bus && self.action_manager) { var breadcrumbs = self.action_manager.get_breadcrumbs(); self.cp_bus.trigger("update_breadcrumbs", breadcrumbs); } @@ -273,6 +281,34 @@ var ViewManager = Widget.extend({ this.view_stack.splice(index); return this.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) + * Fills this.active_view.control_elements dictionnary with the rendered elements + */ + render_view_control_elements: function() { + if (!this.active_view.control_elements) { + var view_controller = this.active_view.controller; + var elements = {}; + if (!this.flags.headless) { + elements = { + $buttons: !this.flags.footer_to_buttons ? $("
") : undefined, + $sidebar: $("
"), + $pager: $("
"), + }; + } + view_controller.render_buttons(elements.$buttons); + view_controller.render_sidebar(elements.$sidebar); + view_controller.render_pager(elements.$pager); + // Remove the unnecessary outer div + elements = _.mapObject(elements, function($node) { + return $node && $node.contents(); + }); + // Store the rendered elements in the active_view to allow restoring them later + this.active_view.control_elements = elements; + } + return this.active_view.control_elements; + }, /** * @returns {Number|Boolean} the view id of the given type, false if not found */ diff --git a/addons/web/static/src/js/views/form_view.js b/addons/web/static/src/js/views/form_view.js index 63981ec0578..511d6b7c31f 100644 --- a/addons/web/static/src/js/views/form_view.js +++ b/addons/web/static/src/js/views/form_view.js @@ -81,10 +81,11 @@ var FormView = View.extend(common.FieldManagerMixin, { self.set({actual_mode: self.options.initial_mode}); this.has_been_loaded.done(function() { self._build_onchange_specs(); - self.on("change:actual_mode", self, self.check_actual_mode); self.check_actual_mode(); - self.on("change:actual_mode", self, self.init_pager); - self.init_pager(); + self.on("change:actual_mode", self, self.check_actual_mode); + self.on("change:actual_mode", self, self.toggle_buttons); + self.on("change:actual_mode", self, self.toggle_sidebar); + self.on("change:actual_mode", self, self.do_update_pager); }); self.on("load_record", self, self.load_record); core.bus.on('clear_uncommitted_changes', this, function(e) { @@ -128,43 +129,12 @@ var FormView = View.extend(common.FieldManagerMixin, { self.__clicked_inside = true; }); - this.$buttons = $(QWeb.render("FormView.buttons", {'widget':self})); - if (this.options.$buttons) { - this.$buttons.appendTo(this.options.$buttons); - } else { - this.$el.find('.oe_form_buttons').replaceWith(this.$buttons); - } - this.$buttons.on('click', '.oe_form_button_create', - this.guard_active(this.on_button_create)); - this.$buttons.on('click', '.oe_form_button_edit', - this.guard_active(this.on_button_edit)); - this.$buttons.on('click', '.oe_form_button_save', - this.guard_active(this.on_button_save)); - this.$buttons.on('click', '.oe_form_button_cancel', - this.guard_active(this.on_button_cancel)); - if (this.options.footer_to_buttons) { - this.$el.find('footer').appendTo(this.$buttons); - } - - this.$sidebar = this.options.$sidebar || this.$el.find('.oe_form_sidebar'); - if (!this.sidebar && this.options.$sidebar) { - this.sidebar = new Sidebar(this); - this.sidebar.appendTo(this.$sidebar); - if (this.fields_view.toolbar) { - this.sidebar.add_toolbar(this.fields_view.toolbar); - } - this.sidebar.add_items('other', _.compact([ - self.is_action_enabled('delete') && { label: _t('Delete'), callback: self.on_button_delete }, - self.is_action_enabled('create') && { label: _t('Duplicate'), callback: self.on_button_duplicate } - ])); - } - this.has_been_loaded.resolve(); // Add bounce effect on button 'Edit' when click on readonly page view. this.$el.find(".oe_form_group_row,.oe_form_field,label,h1,.oe_title,.oe_notebook_page, .oe_list_content").on('click', function (e) { if(self.get("actual_mode") == "view") { - var $button = self.options.$buttons.find(".oe_form_button_edit"); + var $button = self.$buttons.find(".oe_form_button_edit"); $button.openerpBounce(); e.stopPropagation(); core.bus.trigger('click', e); @@ -181,6 +151,121 @@ var FormView = View.extend(common.FieldManagerMixin, { this.trigger('form_view_loaded', data); return $.when(); }, + /** + * Render the buttons according to the FormView.buttons template and add listeners on it. + * Set this.$buttons with the produced jQuery element + * @param {jQuery} [$node] a jQuery node where the rendered buttons should be inserted + * $node may be undefined, in which case the FormView inserts them into this.options.$buttons + * or into a div of its template + */ + render_buttons: function($node) { + this.$buttons = $(QWeb.render("FormView.buttons", {'widget': this})); + + // Show or hide the buttons according to the view mode + this.toggle_buttons(); + this.$buttons.on('click', '.oe_form_button_create', + this.guard_active(this.on_button_create)); + this.$buttons.on('click', '.oe_form_button_edit', + this.guard_active(this.on_button_edit)); + this.$buttons.on('click', '.oe_form_button_save', + this.guard_active(this.on_button_save)); + this.$buttons.on('click', '.oe_form_button_cancel', + this.guard_active(this.on_button_cancel)); + + if (this.options.footer_to_buttons) { + this.$el.find('footer').appendTo(this.$buttons); + } + + $node = $node || this.options.$buttons; + if ($node) { + this.$buttons.appendTo($node); + } else { + this.$('.oe_form_buttons').replaceWith(this.$buttons); + } + }, + /** + * Instantiate and render the sidebar if a sidebar is requested + * Sets this.sidebar + * @param {jQuery} [$node] a jQuery node where the sidebar should be inserted + * $node may be undefined, in which case the FormView inserts the sidebar in a + * div of its template + **/ + render_sidebar: function($node) { + if (!this.sidebar && this.options.sidebar) { + this.sidebar = new Sidebar(this); + if (this.fields_view.toolbar) { + this.sidebar.add_toolbar(this.fields_view.toolbar); + } + this.sidebar.add_items('other', _.compact([ + this.is_action_enabled('delete') && { label: _t('Delete'), callback: this.on_button_delete }, + this.is_action_enabled('create') && { label: _t('Duplicate'), callback: this.on_button_duplicate } + ])); + + $node = $node || this.$('.oe_form_sidebar'); + this.sidebar.appendTo($node); + + // Show or hide the sidebar according to the view mode + this.toggle_sidebar(); + } + }, + /** + * Render the pager according to the FormView.pager template and add listeners on it. + * Set this.$pager with the produced jQuery element + * @param {jQuery} [$node] a jQuery node where the rendered pager should be inserted + * $node may be undefined, in which case the FormView inserts the pager into this.options.$pager + * or into a div of its template + */ + render_pager: function($node) { + if (this.options.pager) { + var self = this; + if (this.$pager) { + this.$pager.remove(); + } + if (this.get("actual_mode") === "create") { + return; + } + this.$pager = $(QWeb.render("FormView.pager", {'widget': self})); + this.$pager.on('click','a[data-pager-action]',function() { + var $el = $(this); + if ($el.attr("disabled")) { + return; + } + var action = $el.data('pager-action'); + var def = $.when(self.execute_pager_action(action)); + $el.attr("disabled"); + def.always(function() { + $el.removeAttr("disabled"); + }); + }); + this.do_update_pager(); + + $node = $node || this.options.$pager; + if ($node) { + this.$pager.appendTo($node); + } else { + this.$('.oe_form_pager').replaceWith(this.$pager); + } + } + }, + /** + * Show or hide the buttons according to the actual_mode + */ + toggle_buttons: function() { + var view_mode = this.get("actual_mode") === "view"; + if (this.$buttons) { + this.$buttons.find('.oe_form_buttons_view').toggle(view_mode); + this.$buttons.find('.oe_form_buttons_edit').toggle(!view_mode); + } + }, + /** + * Show or hide the sidebar according to the actual_mode + */ + toggle_sidebar: function() { + var view_mode = this.get("actual_mode") === "view"; + if (this.sidebar) { + this.sidebar.$el.toggle(view_mode); + } + }, widgetFocused: function() { // Clear click flag if used to focus a widget this.__clicked_inside = false; @@ -223,17 +308,11 @@ var FormView = View.extend(common.FieldManagerMixin, { do_show: function (options) { var self = this; options = options || {}; - if (this.sidebar) { - this.sidebar.$el.show(); - } - if (this.$buttons) { - this.$buttons.show(); - } this.$el.show().css({ opacity: '0', filter: 'alpha(opacity = 0)' }); - this.$el.add(this.$buttons).removeClass('oe_form_dirty'); + this.$el.removeClass('oe_form_dirty'); var shown = this.has_been_loaded; if (options.reload !== false) { @@ -260,15 +339,6 @@ var FormView = View.extend(common.FieldManagerMixin, { core.bus.trigger('form_view_shown', self); }); }, - do_hide: function () { - if (this.sidebar) { - this.sidebar.$el.hide(); - } - if (this.$pager) { - this.$pager.hide(); - } - this._super(); - }, load_record: function(record) { var self = this, set_values = []; if (!record) { @@ -304,7 +374,7 @@ var FormView = View.extend(common.FieldManagerMixin, { } else { self.do_push_state({}); } - self.$el.add(self.$buttons).removeClass('oe_form_dirty'); + self.$el.removeClass('oe_form_dirty'); self.autofocus(); }); }, @@ -328,7 +398,7 @@ var FormView = View.extend(common.FieldManagerMixin, { this.trigger("view_content_has_changed"); }, do_notify_change: function() { - this.$el.add(this.$buttons).addClass('oe_form_dirty'); + this.$el.addClass('oe_form_dirty'); }, execute_pager_action: function(action) { if (this.can_be_discarded()) { @@ -352,37 +422,16 @@ var FormView = View.extend(common.FieldManagerMixin, { } return $.when(); }, - init_pager: function() { - var self = this; - if (this.$pager) - this.$pager.remove(); - if (this.get("actual_mode") === "create") - return; - this.$pager = $(QWeb.render("FormView.pager", {'widget':self})); - if (this.options.$pager) { - this.$pager.appendTo(this.options.$pager); - } else { - this.$el.find('.oe_form_pager').replaceWith(this.$pager); - } - this.$pager.on('click','a[data-pager-action]',function() { - var $el = $(this); - if ($el.attr("disabled")) - return; - var action = $el.data('pager-action'); - var def = $.when(self.execute_pager_action(action)); - $el.attr("disabled"); - def.always(function() { - $el.removeAttr("disabled"); - }); - }); - this.do_update_pager(); - }, do_update_pager: function(hide_index) { - this.$pager.toggle(this.dataset.ids.length > 1); - if (hide_index) { - $(".oe_form_pager_state", this.$pager).html(""); - } else { - $(".oe_form_pager_state", this.$pager).html(_.str.sprintf(_t("%d / %d"), this.dataset.index + 1, this.dataset.ids.length)); + if (this.$pager) { + // Hide the pager in create mode or when there is only one record + var pager_visible = (this.get("actual_mode") !== "create") && (this.dataset.ids.length > 1); + this.$pager.toggle(pager_visible); + if (hide_index === true) { + this.$pager.find(".oe_form_pager_state").html(""); + } else { + this.$pager.find(".oe_form_pager_state").html(_.str.sprintf(_t("%d / %d"), this.dataset.index + 1, this.dataset.ids.length)); + } } }, @@ -512,7 +561,7 @@ var FormView = View.extend(common.FieldManagerMixin, { return this.onchanges_mutex.def; } catch(e) { console.error(e); - crash_manager.show_message(e); + crashmanager.show_message(e); return $.Deferred().reject(); } }, @@ -542,7 +591,7 @@ var FormView = View.extend(common.FieldManagerMixin, { return $.Deferred().resolve(); } catch(e) { console.error(e); - crash_manager.show_message(e); + crashmanager.show_message(e); return $.Deferred().reject(); } }, @@ -642,14 +691,8 @@ var FormView = View.extend(common.FieldManagerMixin, { var self = this; if(this.get("actual_mode") === "view") { self.$el.removeClass('oe_form_editable').addClass('oe_form_readonly'); - self.$buttons.find('.oe_form_buttons_edit').hide(); - self.$buttons.find('.oe_form_buttons_view').show(); - self.$sidebar.show(); } else { self.$el.removeClass('oe_form_readonly').addClass('oe_form_editable'); - self.$buttons.find('.oe_form_buttons_edit').show(); - self.$buttons.find('.oe_form_buttons_view').hide(); - self.$sidebar.hide(); this.autofocus(); } }, @@ -979,9 +1022,8 @@ var FormView = View.extend(common.FieldManagerMixin, { open_defaults_dialog: function () { var self = this; var display = function (field, value) { - var FieldSelection = core.form_widget_registry.get('selection'), - FieldMany2One = core.form_widget_registry.get('many2one'); - + var FieldSelection = core.form_widget_registry.get('selection'); + var FieldMany2One = core.form_widget_registry.get('many2one'); if (!value) { return value; } if (field instanceof FieldSelection) { return _(field.get('values')).find(function (option) { diff --git a/addons/web/static/src/js/views/graph_view.js b/addons/web/static/src/js/views/graph_view.js index 2ab42516ba2..fd0120b6f2f 100644 --- a/addons/web/static/src/js/views/graph_view.js +++ b/addons/web/static/src/js/views/graph_view.js @@ -31,17 +31,25 @@ var GraphView = View.extend({ var load_fields = this.model.call('fields_get', []) .then(this.prepare_fields.bind(this)); - return $.when(this._super(), load_fields).then(this.render_buttons.bind(this)); + return $.when(this._super(), load_fields); }, - render_buttons: function () { - // Render buttons only in non-headless mode as buttons are in the header - if (!this.options.headless) { + /** + * Render the buttons according to the GraphView.buttons and + * add listeners on it. + * Set this.$buttons with the produced jQuery element + * @param {jQuery} [$node] a jQuery node where the rendered buttons should be inserted + * $node may be undefined, in which case the GraphView does nothing + */ + render_buttons: function ($node) { + if ($node) { var context = {measures: _.pairs(_.omit(this.measures, '__count__'))}; - this.$buttons.html(QWeb.render('GraphView.buttons', context)); + this.$buttons = $(QWeb.render('GraphView.buttons', context)); this.$measure_list = this.$buttons.find('.oe-measure-list'); this.update_measure(); this.$buttons.find('button').tooltip(); this.$buttons.click(this.on_button_click.bind(this)); + + this.$buttons.appendTo($node); } }, update_measure: function () { diff --git a/addons/web/static/src/js/views/list_view.js b/addons/web/static/src/js/views/list_view.js index 9207f17b610..292c2d13874 100644 --- a/addons/web/static/src/js/views/list_view.js +++ b/addons/web/static/src/js/views/list_view.js @@ -239,7 +239,6 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { * * Sets up showing/hiding the top-level [Delete] button based on records being selected or not * * Sets up event handlers for action buttons and per-row deletion button * * Hooks global callback for clicking on a row - * * Sets up its sidebar, if any * * @param {Object} data wrapped fields_view_get result * @param {Object} data.fields_view fields_view_get result (processed) @@ -288,28 +287,81 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { 'selected', [selection.ids, selection.records]); }); - // Add button - if (!this.$buttons) { - this.$buttons = $(QWeb.render("ListView.buttons", {'widget':self})); - if (this.options.$buttons) { - this.$buttons.appendTo(this.options.$buttons); - } else { - this.$el.find('.oe_list_buttons').replaceWith(this.$buttons); - } - this.$buttons.find('.oe_list_add') - .click(this.proxy('do_add_record')) - .prop('disabled', this.grouped); + //Sort + var default_order = this.fields_view.arch.attrs.default_order, + unsorted = !this.dataset._sort.length; + if (unsorted && default_order && !this.grouped) { + this.dataset.set_sort(default_order.split(',')); } - // Pager - if (!this.$pager) { - this.$pager = $(QWeb.render("ListView.pager", {'widget':self})).hide(); - if (this.options.$pager) { - this.$pager.appendTo(this.options.$pager); - } else { - this.$el.find('.oe_list_pager').replaceWith(this.$pager); + if(this.dataset._sort.length){ + if(this.dataset._sort[0].indexOf('-') == -1){ + this.$el.find('th[data-id=' + this.dataset._sort[0] + ']').addClass("sortdown"); + }else { + this.$el.find('th[data-id=' + this.dataset._sort[0].split('-')[1] + ']').addClass("sortup"); } + } + this.trigger('list_view_loaded', data, this.grouped); + }, + /** + * Render the buttons according to the ListView.buttons template and + * add listeners on it. + * Set this.$buttons with the produced jQuery element + * @param {jQuery} [$node] a jQuery node where the rendered buttons should be inserted + * $node may be undefined, in which case the ListView inserts them into this.options.$buttons + * or into a div of its template + */ + render_buttons: function($node) { + if (!this.$buttons) { + this.$buttons = $(QWeb.render("ListView.buttons", {'widget': this})); + this.$buttons.find('.oe_list_add').click(this.proxy('do_add_record')); + + $node = $node || this.options.$buttons; + if ($node) { + this.$buttons.appendTo($node); + } else { + this.$('.oe_list_buttons').replaceWith(this.$buttons); + } + } + }, + /** + * Instantiate and render the sidebar. + * Sets this.sidebar + * @param {jQuery} [$node] a jQuery node where the sidebar should be inserted + * $node may be undefined, in which case the ListView inserts the sidebar in + * this.options.$sidebar or in a div of its template + **/ + render_sidebar: function($node) { + if (!this.sidebar && this.options.sidebar) { + this.sidebar = new Sidebar(this); + if (this.fields_view.toolbar) { + this.sidebar.add_toolbar(this.fields_view.toolbar); + } + this.sidebar.add_items('other', _.compact([ + { label: _t("Export"), callback: this.on_sidebar_export }, + this.is_action_enabled('delete') && { label: _t('Delete'), callback: this.do_delete_selected } + ])); + + $node = $node || this.options.$sidebar || this.$('.oe_list_sidebar'); + this.sidebar.appendTo($node); + + // Hide the sidebar by default (it will be shown as soon as a record is selected) + this.sidebar.do_hide(); + } + }, + /** + * Render the pager according to the ListView.pager template and add listeners on it. + * Set this.$pager with the produced jQuery element + * @param {jQuery} [$node] a jQuery node where the rendered pager should be inserted + * $node may be undefined, in which case the ListView inserts the pager into this.options.$pager + * or into a div of its template + */ + render_pager: function($node) { + if (!this.$pager && this.options.pager) { + var self = this; + + this.$pager = $(QWeb.render("ListView.pager", {'widget': self})); this.$pager .on('click', 'a[data-pager-action]', function () { var $this = $(this); @@ -357,34 +409,15 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { }) .val(self._limit || 'NaN'); }); - } + this.configure_pager(this.dataset); - // Sidebar - if (!this.sidebar && this.options.$sidebar) { - this.sidebar = new Sidebar(this); - this.sidebar.appendTo(this.options.$sidebar); - this.sidebar.add_items('other', _.compact([ - { label: _t("Export"), callback: this.on_sidebar_export }, - self.is_action_enabled('delete') && { label: _t('Delete'), callback: this.do_delete_selected } - ])); - this.sidebar.add_toolbar(this.fields_view.toolbar); - this.sidebar.$el.hide(); - } - //Sort - var default_order = this.fields_view.arch.attrs.default_order, - unsorted = !this.dataset._sort.length; - if (unsorted && default_order && !this.grouped) { - this.dataset.set_sort(default_order.split(',')); - } - - if(this.dataset._sort.length){ - if(this.dataset._sort[0].indexOf('-') == -1){ - this.$el.find('th[data-id=' + this.dataset._sort[0] + ']').addClass("sortdown"); - }else { - this.$el.find('th[data-id=' + this.dataset._sort[0].split('-')[1] + ']').addClass("sortup"); + $node = $node || this.options.$pager; + if ($node) { + this.$pager.appendTo($node); + } else { + this.$('.oe_list_pager').replaceWith(this.$pager); } } - this.trigger('list_view_loaded', data, this.grouped); }, sort_by_column: function (e) { e.stopPropagation(); @@ -418,25 +451,32 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { if (dataset._length) { this.dataset._length = dataset._length; } + if (this.$pager) { + if (this.grouped) { + // page count is irrelevant on grouped page, replace by limit + this.$pager.find('.oe-pager-buttons').hide(); + this.$pager.find('.oe_list_pager_state').text(this.limit() ? this.limit() : '∞'); + } else { + var total = dataset.size(); + var limit = this.limit() || total; + this.$pager.find('.oe-pager-buttons').toggle(total > limit); + this.$pager.find('.oe_pager_value').toggle(total !== 0); + var spager = '-'; + if (total) { + var range_start = this.page * limit + 1; + var range_stop = range_start - 1 + limit; + if (this.records.length) { + range_stop = range_start - 1 + this.records.length; + } + if (range_stop > total) { + range_stop = total; + } + spager = _.str.sprintf(_t("%d-%d of %d"), range_start, range_stop, total); + } - var total = dataset.size(); - var limit = this.limit() || total; - this.$pager.find('.oe-pager-buttons').toggle(total > limit); - this.$pager.find('.oe_pager_value').toggle(total !== 0); - var spager = '-'; - if (total) { - var range_start = this.page * limit + 1; - var range_stop = range_start - 1 + limit; - if (this.records.length) { - range_stop = range_start - 1 + this.records.length; + this.$pager.find('.oe_list_pager_state').text(spager); } - if (range_stop > total) { - range_stop = total; - } - spager = _.str.sprintf(_t("%d-%d of %d"), range_start, range_stop, total); } - - this.$pager.find('.oe_list_pager_state').text(spager); }, /** * Sets up the listview's columns: merges view and fields data, move @@ -488,18 +528,10 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { }, do_show: function () { this._super(); - if (this.$pager) { - this.$pager.show(); - } - }, - do_hide: function () { if (this.sidebar) { - this.sidebar.$el.hide(); + // Hide the sidebar by default (will be shown once a record is selected) + this.sidebar.do_hide(); } - if (this.$pager) { - this.$pager.hide(); - } - this._super(); }, /** * Reloads the list view based on the current settings (dataset & al) @@ -522,7 +554,7 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { this.records.reset(); var reloaded = $.Deferred(); reloaded.then(function () { - if (!self.grouped) self.$pager.show(); + self.configure_pager(self.dataset); }); this.$el.find('.oe_list_content').append( this.groups.render(function () { @@ -656,7 +688,7 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { if (!ids.length) { this.dataset.index = 0; if (this.sidebar) { - this.sidebar.$el.hide(); + this.sidebar.do_hide(); } this.compute_aggregates(); return; @@ -664,8 +696,7 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { this.dataset.index = _(this.dataset.ids).indexOf(ids[0]); if (this.sidebar) { - this.options.$sidebar.show(); - this.sidebar.$el.show(); + this.sidebar.do_show(); } this.compute_aggregates(_(records).map(function (record) { @@ -915,10 +946,12 @@ var ListView = View.extend( /** @lends instance.web.ListView# */ { this.$el.prepend( $('
').html(this.options.action.help) ); - var $buttons = this.$buttons; - this.$el.find('.oe_view_nocontent').click(function() { - $buttons.width($buttons.width() + 1).openerpBounce(); - }); + if (this.$buttons) { + var $buttons = this.$buttons; + this.$el.find('.oe_view_nocontent').click(function() { + $buttons.width($buttons.width() + 1).openerpBounce(); + }); + } } }); core.view_registry.add('list', ListView); @@ -1637,9 +1670,6 @@ ListView.Groups = Class.extend( /** @lends instance.web.ListView.Groups# */{ .filter(function (column) { return column.tag === 'field';}) .pluck('name').value(), function (groups) { - // page count is irrelevant on grouped page, replace by limit - self.view.$pager.find('.oe-pager-buttons').hide(); - self.view.$pager.find('.oe_list_pager_state').text(self.view._limit ? self.view._limit : '∞'); $el[0].appendChild( self.render_groups(groups)); if (post_render) { post_render(); } diff --git a/addons/web/static/src/js/views/list_view_editable.js b/addons/web/static/src/js/views/list_view_editable.js index 08f43164471..47f5d6aab43 100644 --- a/addons/web/static/src/js/views/list_view_editable.js +++ b/addons/web/static/src/js/views/list_view_editable.js @@ -286,15 +286,6 @@ ListView.include(/** @lends instance.web.ListView# */{ if (this.editable()) { this.$el.addClass('oe_list_editable'); - // FIXME: any hook available to ensure this is only done once? - this.$buttons - .off('click', '.oe_list_save') - .on('click', '.oe_list_save', this.proxy('save_edition')) - .off('click', '.oe_list_discard') - .on('click', '.oe_list_discard', function (e) { - e.preventDefault(); - self.cancel_edition(); - }); var editor_ready = this.editor.prependTo(this.$el) .done(this.proxy('setup_events')); @@ -305,6 +296,30 @@ ListView.include(/** @lends instance.web.ListView# */{ return result; }, + /** + * Extend the render_buttons function of ListView by adding event listeners + * in the case of an editable list. + * @return {jQuery} the rendered buttons + */ + render_buttons: function() { + var self = this; + var add_button = false; + if (!this.$buttons) { // Ensures that this is only done once + add_button = true; + } + this._super.apply(this, arguments); // Sets this.$buttons + if (add_button && this.editable()) { + this.$buttons + .off('click', '.oe_list_save') + .on('click', '.oe_list_save', this.proxy('save_edition')) + .off('click', '.oe_list_discard') + .on('click', '.oe_list_discard', function (e) { + e.preventDefault(); + self.cancel_edition(); + }); + } + return this.$buttons; + }, /** * Builds a new editor object * diff --git a/addons/web/static/src/js/views/pivot_view.js b/addons/web/static/src/js/views/pivot_view.js index 05f84477cec..605cfe1667d 100644 --- a/addons/web/static/src/js/views/pivot_view.js +++ b/addons/web/static/src/js/views/pivot_view.js @@ -35,7 +35,6 @@ var PivotView = View.extend({ this._super(parent, dataset, view_id, options); this.model = new Model(dataset.model, {group_by_no_leaf: true}); - this.$sidebar = options.$sidebar; this.fields = {}; this.measures = {}; this.groupable_fields = {}; @@ -62,46 +61,68 @@ var PivotView = View.extend({ this.last_header_selected = null; this.sorted_column = {}; }, - start: function () { + willStart: function () { var self = this; + return session.rpc('/web/pivot/check_xlwt').then(function(result) { + self.xlwt_installed = result; + }); + }, + start: function () { this.$table_container = this.$('.o-pivot-table'); var load_fields = this.model.call('fields_get', []) .then(this.prepare_fields.bind(this)); - if (this.$sidebar) { - this.rpc('/web/pivot/check_xlwt').then(function (result) { - if (result) { - self.sidebar = new Sidebar(self); - self.sidebar.appendTo(self.$sidebar); - self.sidebar.add_items('other', [{ - label: _t("Download xls"), - callback: self.download_table.bind(self) - }]); - } - }); - } - return $.when(this._super(), load_fields).then(this.render_buttons.bind(this)); + return $.when(this._super(), load_fields).then(this.render_field_selection.bind(this)); }, - render_buttons: function () { + render_field_selection: function () { var self = this; - // Render buttons only in non-headless mode as buttons are in the header - if (!this.options.headless) { + var context = {fields: _.chain(this.groupable_fields).pairs().sortBy(function(f){return f[1].string;}).value()}; + this.$field_selection = this.$('.o-field-selection'); + this.$field_selection.html(QWeb.render('PivotView.FieldSelection', context)); + core.bus.on('click', self, function () { + self.$field_selection.find('ul').first().hide(); + }); + }, + /** + * Render the buttons according to the PivotView.buttons template and + * add listeners on it. + * Set this.$buttons with the produced jQuery element + * @param {jQuery} [$node] a jQuery node where the rendered buttons should be inserted + * $node may be undefined, in which case the PivotView does nothing + */ + render_buttons: function ($node) { + if ($node) { + var self = this; + var context = {measures: _.pairs(_.omit(this.measures, '__count__'))}; - this.$buttons.html(QWeb.render('PivotView.buttons', context)); + this.$buttons = $(QWeb.render('PivotView.buttons', context)); this.$buttons.click(this.on_button_click.bind(this)); this.active_measures.forEach(function (measure) { self.$buttons.find('li[data-field="' + measure + '"]').addClass('selected'); }); this.$buttons.find('button').tooltip(); + + this.$buttons.appendTo($node); + } + }, + /** + * Instantiate and render the sidebar. + * Sets this.sidebar + * @param {jQuery} [$node] a jQuery node where the sidebar should be inserted + * $node may be undefined, in which case the PivotView does nothing + **/ + render_sidebar: function($node) { + if (this.xlwt_installed && $node && this.options.sidebar) { + this.sidebar = new Sidebar(this); + this.sidebar.add_items('other', [{ + label: _t("Download xls"), + callback: this.download_table.bind(this), + }]); + + this.sidebar.appendTo($node); } - var another_ctx = {fields: _.chain(this.groupable_fields).pairs().sortBy(function(f){return f[1].string;}).value()}; - this.$field_selection = this.$('.o-field-selection'); - this.$field_selection.html(QWeb.render('PivotView.FieldSelection', another_ctx)); - core.bus.on('click', self, function () { - self.$field_selection.find('ul').first().hide(); - }); }, view_loading: function (fvg) { var self = this; @@ -172,9 +193,6 @@ var PivotView = View.extend({ }, do_show: function () { var self = this; - if (this.sidebar) { - this.sidebar.$el.show(); - } this.do_push_state({}); this.data_loaded.done(function () { self.display_table(); @@ -182,12 +200,6 @@ var PivotView = View.extend({ }); return this._super(); }, - do_hide: function () { - if (this.sidebar) { - this.sidebar.$el.hide(); - } - this._super(); - }, get_context: function () { return !this.ready ? {} : { pivot_measures: this.active_measures, @@ -597,7 +609,7 @@ var PivotView = View.extend({ .addClass(rows[i].expanded ? 'oe-opened' : 'oe-closed'); if (rows[i].indent > 0) $header.attr('title', groupby_labels[rows[i].indent - 1]); $header.appendTo($row); - for (j = 0; j < length; j++) { + for (j = 0; j < length; j++) { value = formats.format_value(rows[i].values[j], {type: measure_types[j % nbr_measures]}); $cell = $('') .data('id', rows[i].id) @@ -698,7 +710,7 @@ var PivotView = View.extend({ traverse_tree(self.main_col.root, add_cells, header.id, values, col_ids); if (self.main_col.width > 1) { aggregates = self.get_value(header.id, self.main_col.root.id); - for (i = 0; i < self.active_measures.length; i++) { + for (i = 0; i < self.active_measures.length; i++) { values.push(aggregates && aggregates[self.active_measures[i]]); } col_ids.push( self.main_col.root.id); diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 8af33bf13bc..25f490e86ed 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -495,12 +495,6 @@
- - -
- - -
-
+
-
+
diff --git a/addons/web_calendar/static/src/js/web_calendar.js b/addons/web_calendar/static/src/js/web_calendar.js index a43cd0fcef7..d9db45ea0f8 100644 --- a/addons/web_calendar/static/src/js/web_calendar.js +++ b/addons/web_calendar/static/src/js/web_calendar.js @@ -121,19 +121,6 @@ var CalendarView = View.extend({ this.info_fields = []; - /* buttons */ - this.$buttons = $(QWeb.render("CalendarView.buttons", {'widget': this})); - if (this.options.$buttons) { - this.$buttons.appendTo(this.options.$buttons); - } else { - this.$el.find('.oe_calendar_buttons').replaceWith(this.$buttons); - } - - this.$buttons.on('click', 'button.oe_calendar_button_new', function () { - self.dataset.index = null; - self.do_switch_view('form'); - }); - if (!attrs.date_start) { throw new Error(_t("Calendar view has not defined 'date_start' attribute.")); } @@ -251,6 +238,28 @@ var CalendarView = View.extend({ self.trigger('calendar_view_loaded', self.fields_view); }); }, + /** + * Render the buttons according to the CalendarView.buttons template and + * add listeners on it. + * Set this.$buttons with the produced jQuery element + * @param {jQuery} [$node] a jQuery node where the rendered buttons should be inserted + * $node may be undefined, in which case the ListView inserts them into this.options.$buttons + * or into a div of its template + */ + render_buttons: function($node) { + var self = this; + this.$buttons = $(QWeb.render("CalendarView.buttons", {'widget': this})); + this.$buttons.on('click', 'button.oe_calendar_button_new', function () { + self.dataset.index = null; + self.do_switch_view('form'); + }); + $node = $node || this.options.$buttons; + if ($node) { + this.$buttons.appendTo($node); + } else { + this.$('.oe_calendar_buttons').replaceWith(this.$buttons); + } + }, get_fc_init_options: function () { //Documentation here : http://arshaw.com/fullcalendar/docs/ var self = this; diff --git a/addons/web_diagram/static/src/js/diagram.js b/addons/web_diagram/static/src/js/diagram.js index 720bc1aa8f7..cb7c2471beb 100644 --- a/addons/web_diagram/static/src/js/diagram.js +++ b/addons/web_diagram/static/src/js/diagram.js @@ -64,8 +64,6 @@ var DiagramView = View.extend({ self.$el.find('.oe_diagram_header').append(html_label); }); - this.init_pager(); - // New Node,Edge this.$el.find('#new_node.oe_diagram_button_new').click(function(){self.add_node();}); @@ -361,29 +359,29 @@ var DiagramView = View.extend({ }); }, - do_hide: function () { - if (this.$pager) { - this.$pager.hide(); - } - this._super(); - }, - - init_pager: function() { + /** + * Render the pager according to the DiagramView.pager template and add listeners on it. + * Set this.$pager with the produced jQuery element + * @param {jQuery} [$node] a jQuery node where the rendered pager should be inserted + * $node may be undefined, in which case the DiagramView inserts the pager into this.options.$pager + * or into a div of its template + */ + render_pager: function($node) { var self = this; - if (this.$pager) - this.$pager.remove(); - - this.$pager = $(QWeb.render("DiagramView.pager", {'widget':self})).hide(); - if (this.options.$pager) { - this.$pager.appendTo(this.options.$pager); - } else { - this.$el.find('.oe_diagram_pager').replaceWith(this.$pager); - } - this.$pager.on('click','a[data-pager-action]',function() { + + this.$pager = $(QWeb.render("DiagramView.pager", {'widget': self})); + this.$pager.find('a[data-pager-action]').click(function() { var action = $(this).data('pager-action'); self.execute_pager_action(action); }); - this.do_update_pager(); + this.execute_pager_action('reload'); + + $node = $node || this.options.$pager; + if ($node) { + this.$pager.appendTo($node); + } else { + this.$('.oe_diagram_pager').replaceWith(this.$pager); + } }, pager_action_trigger: function(){ @@ -409,15 +407,16 @@ var DiagramView = View.extend({ break; } this.trigger('pager_action_executed'); - }, do_update_pager: function(hide_index) { - this.$pager.toggle(this.dataset.ids.length > 1); - if (hide_index) { - $(".oe_diagram_pager_state", this.$pager).html(""); - } else { - $(".oe_diagram_pager_state", this.$pager).html(_.str.sprintf(_t("%d / %d"), this.dataset.index + 1, this.dataset.ids.length)); + if (this.$pager) { + this.$pager.toggle(this.dataset.ids.length > 1); + if (hide_index) { + this.$pager.find(".oe_diagram_pager_state").html(""); + } else { + this.$pager.find(".oe_diagram_pager_state").html(_.str.sprintf(_t("%d / %d"), this.dataset.index + 1, this.dataset.ids.length)); + } } }, diff --git a/addons/web_diagram/static/src/xml/base_diagram.xml b/addons/web_diagram/static/src/xml/base_diagram.xml index 9fc5c427220..51bb413c472 100644 --- a/addons/web_diagram/static/src/xml/base_diagram.xml +++ b/addons/web_diagram/static/src/xml/base_diagram.xml @@ -1,9 +1,7 @@