diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 970d509d481..2dbf130e030 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -822,8 +822,8 @@ instance.web.UserMenu = instance.web.Widget.extend({ {text: _t("Change password"), click: function(){ self.change_password(); }}, {text: _t("Cancel"), click: function(){ $(this).dialog('destroy'); }}, {text: _t("Save"), click: function(){ - var inner_viewmanager = action_manager.inner_viewmanager; - inner_viewmanager.views[inner_viewmanager.active_view].controller.do_save() + var inner_widget = action_manager.inner_widget; + inner_widget.views[inner_widget.active_view].controller.do_save() .then(function() { self.dialog.destroy(); // needs to refresh interface in case language changed @@ -1037,6 +1037,7 @@ instance.web.WebClient = instance.web.Widget.extend({ if (options.needaction) { action.context.search_default_needaction_pending = true; } + self.action_manager.clear_breadcrumbs(); self.action_manager.do_action(action); }); }, diff --git a/addons/web/static/src/js/view_editor.js b/addons/web/static/src/js/view_editor.js index 8733a191b21..6df78679ecb 100644 --- a/addons/web/static/src/js/view_editor.js +++ b/addons/web/static/src/js/view_editor.js @@ -53,7 +53,7 @@ instance.web.ViewEditor = instance.web.OldWidget.extend({ this.main_view_id = this.parent.fields_view.view_id; this.action_manager = new instance.web.ActionManager(this); $.when(this.action_manager.do_action(action)).then(function() { - var viewmanager = self.action_manager.inner_viewmanager, + var viewmanager = self.action_manager.inner_widget, controller = viewmanager.views[viewmanager.active_view].controller; self.action_manager.appendTo(self.view_edit_dialog.$element); self.action_manager.renderElement(self.view_edit_dialog); @@ -88,7 +88,7 @@ instance.web.ViewEditor = instance.web.OldWidget.extend({ } else { $.when(self.do_save_view(view_values)).then(function() { self.create_view_dialog.close(); - var controller = self.action_manager.inner_viewmanager.views[self.action_manager.inner_viewmanager.active_view].controller; + var controller = self.action_manager.inner_widget.views[self.action_manager.inner_widget.active_view].controller; controller.reload_content(); }); } @@ -167,7 +167,7 @@ instance.web.ViewEditor = instance.web.OldWidget.extend({ do_delete_view: function() { var self = this; if (confirm(_t("Do you really want to remove this view?"))) { - var controller = this.action_manager.inner_viewmanager.views[this.action_manager.inner_viewmanager.active_view].controller; + var controller = this.action_manager.inner_widget.views[this.action_manager.inner_widget.active_view].controller; this.dataset.unlink([this.main_view_id]).then(function() { controller.reload_content(); self.main_view_id = self.parent.fields_view.view_id; @@ -397,7 +397,7 @@ instance.web.ViewEditor = instance.web.OldWidget.extend({ action_manager.do_action(action); }}, {text: _t("Close"), click: function(){ - self.action_manager.inner_viewmanager.views[self.action_manager.inner_viewmanager.active_view].controller.reload_content(); + self.action_manager.inner_widget.views[self.action_manager.inner_widget.active_view].controller.reload_content(); self.edit_xml_dialog.close(); }} ] @@ -998,7 +998,7 @@ instance.web.ViewEditor = instance.web.OldWidget.extend({ }; var action_manager = new instance.web.ActionManager(self); $.when(action_manager.do_action(action)).then(function() { - var controller = action_manager.dialog_viewmanager.views['form'].controller; + var controller = action_manager.dialog_widget.views['form'].controller; controller.on_button_cancel.add_last(function(){ action_manager.destroy() }); diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index d629b8e9b3d..a90654b2cf5 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -287,10 +287,12 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM on_record_loaded: function(record) { var self = this, set_values = []; if (!record) { + this.set({ 'title' : undefined }); this.do_warn("Form", "The record could not be found in the database.", true); return $.Deferred().reject(); } this.datarecord = record; + this.set({ 'title' : record.id ? record.name : "New record" }); if (this.qweb) { this.kill_current_form(); diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 6586807264d..ffa78f315e5 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -10,28 +10,111 @@ instance.web.ActionManager = instance.web.Widget.extend({ init: function(parent) { this._super(parent); this.inner_action = null; - this.inner_viewmanager = null; + this.inner_widget = null; this.dialog = null; - this.dialog_viewmanager = null; - this.client_widget = null; + this.dialog_widget = null; + this.breadcrumbs = []; + }, + start: function() { + this._super.apply(this, arguments); + this.$element.on('click', '.oe_breadcrumb_item', this.on_breadcrumb_clicked); }, dialog_stop: function () { if (this.dialog) { - this.dialog_viewmanager.destroy(); - this.dialog_viewmanager = null; + this.dialog_widget.destroy(); + this.dialog_widget = null; this.dialog.destroy(); this.dialog = null; } }, - content_stop: function () { - if (this.inner_viewmanager) { - this.inner_viewmanager.destroy(); - this.inner_viewmanager = null; + /** + * Add a new item to the breadcrumb + * + * If the title of an item is an array, the multiple title mode is in use. + * (eg: a widget with multiple views might need to display a title for each view) + * In multiple title mode, the show() callback can check the index it receives + * in order to detect which of its titles has been clicked on by the user. + * + * @param {Object} item breadcrumb item + * @param {Object} item.widget widget containing the view(s) to be added to the breadcrumb added + * @param {Function} [item.show] triggered whenever the widget should be shown back + * @param {Function} [item.hide] triggered whenever the widget should be shown hidden + * @param {Function} [item.destroy] triggered whenever the widget should be destroyed + * @param {String|Array} [item.title] title(s) of the view(s) to be displayed in the breadcrumb + * @param {Function} [item.get_title] should return the title(s) of the view(s) to be displayed in the breadcrumb + */ + push_breadcrumb: function(item) { + var last = this.breadcrumbs.slice(-1)[0]; + if (last) { + last.hide(); } - if (this.client_widget) { - this.client_widget.destroy(); - this.client_widget = null; + var item = _.extend({ + show: function(index) { + this.widget.$element.show(); + }, + hide: function() { + this.widget.$element.hide(); + }, + destroy: function() { + this.widget.destroy(); + }, + get_title: function() { + return this.title || this.widget.get('title'); + } + }, item); + item.id = _.uniqueId('breadcrumb_'); + this.breadcrumbs.push(item); + }, + on_breadcrumb_clicked: function(ev) { + var $e = $(ev.target); + var id = $e.data('id'); + var item; + for (var i = this.breadcrumbs.length - 1; i >= 0; i--) { + var it = this.breadcrumbs[i]; + if (it.id == id) { + item = it; + break; + } + this.remove_breadcrumb(i); } + var index = $e.parent().find('.oe_breadcrumb_item[data-id=' + $e.data('id') + ']').index($e); + item.show(index, $e); + this.inner_widget = item.widget; + }, + clear_breadcrumbs: function() { + while (this.breadcrumbs.length) { + this.remove_breadcrumb(0); + } + }, + remove_breadcrumb: function(index) { + var item = this.breadcrumbs.splice(index, 1)[0]; + if (item) { + var dups = _.filter(this.breadcrumbs, function(it) { + return item.widget === it.widget; + }); + if (!dups.length) { + item.destroy(); + } + } + }, + get_title: function() { + var titles = []; + for (var i = 0; i < this.breadcrumbs.length; i += 1) { + var item = this.breadcrumbs[i]; + var tit = item.get_title(); + if (!_.isArray(tit)) { + tit = [tit]; + } + for (var j = 0; j < tit.length; j += 1) { + var label = _.escape(tit[j]); + if (i === this.breadcrumbs.length - 1 && j === tit.length - 1) { + titles.push(label); + } else { + titles.push(_.str.sprintf('%s', item.id, label)); + } + } + } + return titles.join(' / '); }, do_push_state: function(state) { if (this.getParent() && this.getParent().do_push_state) { @@ -49,7 +132,7 @@ instance.web.ActionManager = instance.web.Widget.extend({ var self = this, action_loaded; if (state.action_id) { - var run_action = (!this.inner_viewmanager) || this.inner_viewmanager.action.id !== state.action_id; + var run_action = (!this.inner_widget || !this.inner_widget.action) || this.inner_widget.action.id !== state.action_id; if (run_action) { this.null_action(); action_loaded = this.do_action(state.action_id); @@ -89,8 +172,8 @@ instance.web.ActionManager = instance.web.Widget.extend({ } $.when(action_loaded || null).then(function() { - if (self.inner_viewmanager) { - self.inner_viewmanager.do_load_state(state, warm); + if (self.inner_widget && self.inner_widget.do_load_state) { + self.inner_widget.do_load_state(state, warm); } }); }, @@ -124,7 +207,7 @@ instance.web.ActionManager = instance.web.Widget.extend({ }, null_action: function() { this.dialog_stop(); - this.content_stop(); + this.clear_breadcrumbs(); }, ir_actions_act_window: function (action, on_close) { var self = this; @@ -141,25 +224,23 @@ instance.web.ActionManager = instance.web.Widget.extend({ if(on_close) this.dialog.on_close.add(on_close); } else { - this.dialog_viewmanager.destroy(); + this.dialog_widget.destroy(); } this.dialog.dialog_title = action.name; - this.dialog_viewmanager = new instance.web.ViewManagerAction(this.dialog, action); - this.dialog_viewmanager.appendTo(this.dialog.$element); - this.dialog_viewmanager.$element.addClass("oe_view_manager_" + (action.target || 'current')); + this.dialog_widget = new instance.web.ViewManagerAction(this.dialog, action); + this.dialog_widget.appendTo(this.dialog.$element); this.dialog.open(); } else { this.dialog_stop(); - this.content_stop(); if(action.menu_id) { return this.getParent().do_action(action, function () { instance.webclient.menu.open_menu(action.menu_id); }); } this.inner_action = action; - this.inner_viewmanager = new instance.web.ViewManagerAction(this, action); - this.inner_viewmanager.appendTo(this.$element); - this.inner_viewmanager.$element.addClass("oe_view_manager_" + (action.target || 'current')); + var inner_widget = this.inner_widget = new instance.web.ViewManagerAction(this, action); + inner_widget.add_breadcrumb(); + this.inner_widget.appendTo(this.$element); } }, ir_actions_act_window_close: function (action, on_closed) { @@ -178,10 +259,14 @@ instance.web.ActionManager = instance.web.Widget.extend({ }); }, ir_actions_client: function (action) { - this.content_stop(); this.dialog_stop(); var ClientWidget = instance.web.client_actions.get_object(action.tag); - (this.client_widget = new ClientWidget(this, action.params)).appendTo(this.$element); + this.inner_widget = new ClientWidget(this, action.params); + this.push_breadcrumb({ + widget: this.inner_widget, + title: action.name + }); + this.inner_widget.appendTo(this.$element); }, ir_actions_report_xml: function(action, on_closed) { var self = this; @@ -290,35 +375,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ this.active_view = view_type; if (!view.controller) { - // Lazy loading of views - var controllerclass = this.registry.get_object(view_type); - var options = _.clone(view.options); - if (view_type === "form" && this.action) { - switch (this.action.target) { - case 'new': - case 'inline': - options.initial_mode = 'edit'; - break; - } - } - var controller = new controllerclass(this, this.dataset, view.view_id, options); - if (view.embedded_view) { - controller.set_embedded_view(view.embedded_view); - } - controller.do_switch_view.add_last(_.bind(this.switch_view, this)); - controller.do_prev_view.add_last(this.on_prev_view); - var container = this.$element.find(".oe_view_manager_view_" + view_type); - view_promise = controller.appendTo(container); - this.views[view_type].controller = controller; - this.views[view_type].deferred.resolve(view_type); - $.when(view_promise).then(function() { - self.on_controller_inited(view_type, controller); - if (self.searchview - && self.flags.auto_search - && view.controller.searchable !== false) { - self.searchview.ready.then(self.searchview.do_search); - } - }); + view_promise = this.do_create_view(view_type); } else if (this.searchview && self.flags.auto_search && view.controller.searchable !== false) { @@ -349,12 +406,81 @@ instance.web.ViewManager = instance.web.Widget.extend({ } } }); - - self.$element.find('.oe_view_title_text:first').text( - self.display_title()); }); return view_promise; }, + do_create_view: function(view_type) { + // Lazy loading of views + var self = this; + var view = this.views[view_type]; + var controllerclass = this.registry.get_object(view_type); + var options = _.clone(view.options); + if (view_type === "form" && this.action) { + switch (this.action.target) { + case 'new': + case 'inline': + options.initial_mode = 'edit'; + break; + } + } + var controller = new controllerclass(this, this.dataset, view.view_id, options); + + controller.on("change:title", this, function() { + if (self.active_view === view_type) { + self.set_title(controller.get('title')); + } + }); + + if (view.embedded_view) { + controller.set_embedded_view(view.embedded_view); + } + controller.do_switch_view.add_last(_.bind(this.switch_view, this)); + + controller.do_prev_view.add_last(this.on_prev_view); + var container = this.$element.find(".oe_view_manager_view_" + view_type); + var view_promise = controller.appendTo(container); + this.views[view_type].controller = controller; + this.views[view_type].deferred.resolve(view_type); + return $.when(view_promise).then(function() { + self.on_controller_inited(view_type, controller); + if (self.searchview + && self.flags.auto_search + && view.controller.searchable !== false) { + self.searchview.ready.then(self.searchview.do_search); + } + }); + }, + set_title: function(title) { + this.$element.find('.oe_view_title_text:first').text(title); + }, + add_breadcrumb: function() { + var self = this; + var views = [this.active_view || this.views_src[0].view_type]; + this.on_mode_switch.add(function(mode) { + var last = views.slice(-1)[0]; + if (mode !== last) { + if (mode !== 'form') { + views.length = 0; + } + views.push(mode); + } + }); + this.getParent().push_breadcrumb({ + widget: this, + show: function(index, $e) { + var view_to_select = views[index]; + self.$element.show(); + if (self.active_view !== view_to_select) { + self.on_mode_switch(view_to_select); + } + }, + get_title: function() { + return _.map(views, function(v) { + return self.views[v].controller.get('title'); + }); + } + }); + }, /** * Method used internally when a view asks to switch view. This method is meant * to be extended by child classes to change the default behavior, which simply @@ -447,14 +573,6 @@ instance.web.ViewManager = instance.web.Widget.extend({ */ on_action_executed: function () { }, - display_title: function () { - var view = this.views[this.active_view]; - if (view) { - // ick - return view.controller.fields_view.arch.attrs.string; - } - return ''; - } }); instance.web.ViewManagerAction = instance.web.ViewManager.extend({ @@ -499,7 +617,7 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ if (this.session.hidden_menutips) { return; } - this.session.hidden_menutips = {} + this.session.hidden_menutips = {}; }, /** * Initializes the ViewManagerAction: sets up the searchview (if the @@ -527,6 +645,7 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ var manager_ready = $.when(searchview_loaded, main_view_loaded); this.$element.find('.oe_debug_view').change(this.on_debug_changed); + this.$element.addClass("oe_view_manager_" + (this.action.target || 'current')); if (this.action.help && !this.flags.low_profile) { var Users = new instance.web.DataSet(self, 'res.users'), @@ -675,12 +794,18 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ view: controller, view_manager: self })); - if (!self.action.name && fvg) { - self.$element.find('.oe_view_title_text').text(fvg.arch.attrs.string || fvg.name); - } - + self.set_title(); }); }, + do_create_view: function(view_type) { + var r = this._super.apply(this, arguments); + var view = this.views[view_type].controller; + view.set({ 'title': this.action.name }); + return r; + }, + set_title: function(title) { + this.$element.find('.oe_breadcrumb_title:first').html(this.getParent().get_title()); + }, do_push_state: function(state) { if (this.getParent() && this.getParent().do_push_state) { state["view_type"] = this.active_view; @@ -702,9 +827,6 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ self.views[self.active_view].controller.do_load_state(state, warm); }); }, - display_title: function () { - return this.action.name; - } }); instance.web.Sidebar = instance.web.Widget.extend({ diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 8acf63a97b8..eb3f367ae8c 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -406,7 +406,7 @@

- +

diff --git a/addons/web_dashboard/static/src/js/dashboard.js b/addons/web_dashboard/static/src/js/dashboard.js index 651c3808c2f..0754bfd7e2b 100644 --- a/addons/web_dashboard/static/src/js/dashboard.js +++ b/addons/web_dashboard/static/src/js/dashboard.js @@ -209,8 +209,8 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({ } }); } - if (am.inner_viewmanager) { - am.inner_viewmanager.on_mode_switch.add(function(mode) { + if (am.inner_widget) { + am.inner_widget.on_mode_switch.add(function(mode) { var new_views = []; _.each(action_orig.views, function(view) { new_views[view[1] === mode ? 'unshift' : 'push'](view); @@ -219,7 +219,7 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({ new_views.unshift([false, mode]); } action_orig.views = new_views; - action_orig.res_id = am.inner_viewmanager.dataset.ids[am.inner_viewmanager.dataset.index]; + action_orig.res_id = am.inner_widget.dataset.ids[am.inner_widget.dataset.index]; self.do_action(action_orig); }); } diff --git a/addons/web_process/static/src/js/process.js b/addons/web_process/static/src/js/process.js index 2a2e13ca196..bb32d300be8 100644 --- a/addons/web_process/static/src/js/process.js +++ b/addons/web_process/static/src/js/process.js @@ -260,7 +260,7 @@ openerp.web_process = function (instance) { buttons : [ {text: _t("Cancel"), click: function() { $(this).dialog('destroy'); }}, {text: _t("Save"), click: function() { - var form_view = action_manager.inner_viewmanager.views.form.controller; + var form_view = action_manager.inner_widget.views.form.controller; form_view.do_save(function() { self.initialize_process_view();