[WIP] Breadcrumb: compact code, fix client actions
[odoo/odoo.git] / addons / web / static / src / js / views.js
index 01297e7..e6d4047 100644 (file)
@@ -15,6 +15,10 @@ instance.web.ActionManager = instance.web.Widget.extend({
         this.dialog_viewmanager = null;
         this.client_widget = null;
     },
+    start: function() {
+        this._super.apply(this, arguments);
+        this.breadcrumb = new instance.web.BreadCrumb(this);
+    },
     dialog_stop: function () {
         if (this.dialog) {
             this.dialog_viewmanager.destroy();
@@ -24,6 +28,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
         }
     },
     content_stop: function () {
+        // TODO: problem with bread crumb here. Check if those references are needed
         if (this.inner_viewmanager) {
             this.inner_viewmanager.destroy();
             this.inner_viewmanager = null;
@@ -36,6 +41,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
     do_push_state: function(state) {
         if (this.getParent() && this.getParent().do_push_state) {
             if (this.inner_action) {
+                state['title'] = this.inner_action.name;
                 state['model'] = this.inner_action.res_model;
                 if (this.inner_action.id) {
                     state['action_id'] = this.inner_action.id;
@@ -77,7 +83,14 @@ instance.web.ActionManager = instance.web.Widget.extend({
             });
         } else if (state.client_action) {
             this.null_action();
-            this.ir_actions_client(state.client_action);
+            var action = state.client_action;
+            if(_.isString(action)) {
+                action = {
+                    tag: action,
+                    params: state,
+                };
+            }
+            this.ir_actions_client(action);
         }
 
         $.when(action_loaded || null).then(function() {
@@ -128,7 +141,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
             };
         }
         if (action.target === 'new') {
-            if (this.dialog == null) {
+            if (this.dialog === null) {
                 this.dialog = new instance.web.Dialog(this, { width: '80%' });
                 if(on_close)
                     this.dialog.on_close.add(on_close);
@@ -136,20 +149,24 @@ instance.web.ActionManager = instance.web.Widget.extend({
                 this.dialog_viewmanager.destroy();
             }
             this.dialog.dialog_title = action.name;
-            this.dialog_viewmanager = new instance.web.ViewManagerAction(this, action);
+            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);
             this.dialog.open();
         } else  {
             this.dialog_stop();
-            this.content_stop();
+            //this.content_stop();
+            this.breadcrumb.hide_items();
             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);
+            var inner_viewmanager = this.inner_viewmanager = new instance.web.ViewManagerAction(this, action);
+            this.breadcrumb.push_actionmanager(inner_viewmanager);
             this.inner_viewmanager.appendTo(this.$element);
+            this.inner_viewmanager.$element.addClass("oe_view_manager_" + action.target);
         }
     },
     ir_actions_act_window_close: function (action, on_closed) {
@@ -168,10 +185,16 @@ instance.web.ActionManager = instance.web.Widget.extend({
         });
     },
     ir_actions_client: function (action) {
-        this.content_stop();
+        //this.content_stop();
         this.dialog_stop();
+        this.breadcrumb.hide_items();
         var ClientWidget = instance.web.client_actions.get_object(action.tag);
-        (this.client_widget = new ClientWidget(this, action.params)).appendTo(this.$element);
+        this.client_widget = new ClientWidget(this, action.params);
+        this.breadcrumb.push({
+            widget: this.client_widget,
+            title: action.name
+        });
+        this.client_widget.appendTo(this.$element);
     },
     ir_actions_report_xml: function(action, on_closed) {
         var self = this;
@@ -204,6 +227,105 @@ instance.web.ActionManager = instance.web.Widget.extend({
     }
 });
 
+instance.web.BreadCrumb = instance.web.CallbackEnabled.extend({
+    init: function(parent) {
+        this._super(parent);
+        this.action_manager = parent;
+        this.items = [];
+        this.action_manager.$element.on('click', '.oe_breadcrumb_item', this.on_item_clicked);
+    },
+    push: function(item) {
+        item.show = item.show || function() {
+            item.widget.$element.show();
+        };
+        item.hide = item.hide || function() {
+            item.widget.$element.hide();
+        };
+        item.destroy = item.destroy || function() {
+            item.widget.destroy();
+        };
+        item.get_title = item.get_title || function() {
+            return item.title || item.widget.get('title');
+        };
+        console.log("breadcrumb push", item);
+        this.items.push(item);
+    },
+    push_actionmanager: function(am, view_type) {
+        var self = this;
+        var bookmarked_view = view_type || am.active_view || am.views_src[0].view_type;
+        this.push({
+            widget: am,
+            show: function() {
+                am.$element.show();
+                if (am.active_view !== bookmarked_view) {
+                    am.on_mode_switch(bookmarked_view);
+                    am.set_title();
+                }
+            },
+            get_title: function() {
+                return am.views[bookmarked_view].controller.get('title');
+            }
+        });
+        if (bookmarked_view !== 'form') {
+            am.on_mode_switch.add_first(function(mode) {
+                if (mode === 'form') {
+                    self.push_actionmanager(am, 'form');
+                } else {
+                    // select previous to form and remove form
+                }
+            });
+        }
+    },
+    pop: function() {
+        this.remove_item(this.items.length - 1);
+        this.select_item(this.items.length - 1);
+    },
+    get_title: function() {
+        return QWeb.render('BreadCrumb', { widget: this });
+    },
+    hide_items: function() {
+        _.each(this.items, function(i) {
+            i.hide();
+        });
+    },
+    on_item_clicked: function(ev) {
+        var $e = $(ev.target);
+        var index = $e.data('index');
+        this.select_item(index);
+    },
+    select_item: function(index) {
+        for (var i = index + 1; i < this.items.length; i += 1) {
+            this.remove_item(i);
+        }
+        var item = this.items[index];
+        if (item) {
+            item.show();
+        } else {
+            console.warn("Breadcrumb: Can't select item at index", index);
+        }
+    },
+    remove_item: function(index) {
+        console.log("Breadcrumb remove index", index);
+        var item = this.items.splice(index, 1)[0];
+        if (item) {
+            var dups = _.filter(this.items, function(it) {
+                return item.widget === it.widget;
+            });
+            if (dups.length === 0) {
+                console.log("Breadcrumb Destroy", item);
+                item.destroy();
+            }
+        } else {
+            console.warn("Breadcrumb: Can't remove item at index", index);
+        }
+    },
+    clear: function() {
+        while (this.items.length) {
+            this.remove_item(0);
+        }
+    },
+});
+
 instance.web.ViewManager =  instance.web.Widget.extend({
     template: "ViewManager",
     init: function(parent, dataset, views, flags) {
@@ -267,7 +389,7 @@ instance.web.ViewManager =  instance.web.Widget.extend({
      * @param {Boolean} [no_store=false] don't store the view being switched to on the switch stack
      * @returns {jQuery.Deferred} new view loading promise
      */
-    on_mode_switch: function(view_type, no_store) {
+    on_mode_switch: function(view_type, no_store, view_options) {
         var self = this;
         var view = this.views[view_type];
         var view_promise;
@@ -280,35 +402,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) {
@@ -320,7 +414,7 @@ instance.web.ViewManager =  instance.web.Widget.extend({
         }
 
         this.$element
-            .find('.oe_view_manager_switch a').parent().removeClass('active')
+            .find('.oe_view_manager_switch a').parent().removeClass('active');
         this.$element
             .find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]')
             .parent().addClass('active');
@@ -329,26 +423,70 @@ instance.web.ViewManager =  instance.web.Widget.extend({
             _.each(_.keys(self.views), function(view_name) {
                 var controller = self.views[view_name].controller;
                 if (controller) {
+                    var container = self.$element.find(".oe_view_manager_view_" + view_name + ":first");
                     if (view_name === view_type) {
-                        controller.do_show();
+                        container.show();
+                        controller.do_show(view_options || {});
                     } else {
+                        container.hide();
                         controller.do_hide();
                     }
                 }
             });
-
-            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);
+    },
     /**
      * 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
      * consist to switch to the asked view.
      */
-    switch_view: function(view_type, no_store) {
-        return this.on_mode_switch(view_type, no_store);
+    switch_view: function(view_type, no_store, options) {
+        return this.on_mode_switch(view_type, no_store, options);
     },
     /**
      * Returns to the view preceding the caller view in this manager's
@@ -434,14 +572,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({
@@ -486,7 +616,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
@@ -651,10 +781,10 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
         }, action || {});
         this.do_action(action);
     },
-    on_mode_switch: function (view_type, no_store) {
+    on_mode_switch: function (view_type, no_store, options) {
         var self = this;
 
-        return $.when(this._super(view_type, no_store)).then(function () {
+        return $.when(this._super.apply(this, arguments)).then(function () {
             var controller = self.views[self.active_view].controller,
                 fvg = controller.fields_view,
                 view_id = (fvg && fvg.view_id) || '--';
@@ -668,6 +798,18 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
 
         });
     },
+    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) {
+        var breadcrumb = this.getParent().breadcrumb;
+        if (breadcrumb) {
+            this.$element.find('.oe_breadcrumb_title:first').html(breadcrumb.get_title());
+        }
+    },
     do_push_state: function(state) {
         if (this.getParent() && this.getParent().do_push_state) {
             state["view_type"] = this.active_view;
@@ -689,13 +831,11 @@ 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({
     init: function(parent) {
+        var self = this;
         this._super(parent);
         var view = this.getParent();
         this.sections = [
@@ -712,19 +852,24 @@ instance.web.Sidebar = instance.web.Widget.extend({
             var item = { label: _t("Translate"), callback: view.on_sidebar_translate, title: _t("Technical translation") };
             this.items.other.push(item);
         }
+        this.fileupload_id = _.uniqueId('oe_fileupload');
+        $(window).on(this.fileupload_id, function() {
+            var args = [].slice.call(arguments).slice(1);
+            if (args[0] && args[0].error) {
+                alert(args[0].error);
+            } else {
+                self.do_attachement_update(self.dataset, self.model_id);
+            }
+            $.unblockUI();
+        });
     },
     start: function() {
         var self = this;
         this._super(this);
         this.redraw();
-        this.$element.on('click','.oe_dropdown_toggle',function(event) {
-            $(this).parent().find('ul').toggle();
-            return false;
-        });
         this.$element.on('click','.oe_dropdown_menu li a', function(event) {
             var section = $(this).data('section');
             var index = $(this).data('index');
-            $(this).closest('ul').hide();
             var item = self.items[section][index];
             if (item.callback) {
                 item.callback.apply(self, [item]);
@@ -733,16 +878,12 @@ instance.web.Sidebar = instance.web.Widget.extend({
             } else if (item.url) {
                 return true;
             }
-            return false;
+            event.preventDefault();
         });
-        //this.$div.html(QWeb.render('FormView.sidebar.attachments', this));
-        //this.$element.find('.oe-binary-file').change(this.on_attachment_changed);
-        //this.$element.find('.oe-sidebar-attachment-delete').click(this.on_attachment_delete);
     },
     redraw: function() {
         var self = this;
         self.$element.html(QWeb.render('Sidebar', {widget: self}));
-        this.$element.find('ul').hide();
 
         // Hides Sidebar sections when item list is empty
         this.$('.oe_form_dropdown_section').each(function() {
@@ -826,6 +967,8 @@ instance.web.Sidebar = instance.web.Widget.extend({
         });
     },
     do_attachement_update: function(dataset, model_id) {
+        this.dataset = dataset;
+        this.model_id = model_id;
         if (!model_id) {
             this.on_attachments_loaded([]);
         } else {
@@ -837,42 +980,37 @@ instance.web.Sidebar = instance.web.Widget.extend({
     on_attachments_loaded: function(attachments) {
         var self = this;
         var items = [];
-        // TODO: preprend: _s +
-        var prefix = '/web/binary/saveas?session_id=' + self.session.session_id + '&model=ir.attachment&field=datas&filename_field=name&id=';
+        var prefix = this.session.origin + '/web/binary/saveas?session_id=' + self.session.session_id + '&model=ir.attachment&field=datas&filename_field=name&id=';
         _.each(attachments,function(a) {
             a.label = a.name;
             if(a.type === "binary") {
                 a.url = prefix  + a.id + '&t=' + (new Date().getTime());
             }
         });
-        attachments.push( { label: _t("Add..."), callback: self.on_attachment_add } );
         self.items['files'] = attachments;
         self.redraw();
-    },
-    on_attachment_add: function(e) {
-        this.$element.find('.oe_sidebar_add').show();
+        this.$('.oe_sidebar_add_attachment .oe_form_binary_file').change(this.on_attachment_changed);
+        this.$element.find('.oe_sidebar_delete_item').click(this.on_attachment_delete);
     },
     on_attachment_changed: function(e) {
-        return;
-        window[this.element_id + '_iframe'] = this.do_update;
         var $e = $(e.target);
-        if ($e.val() != '') {
-            this.$element.find('form.oe-binary-form').submit();
+        if ($e.val() !== '') {
+            this.$element.find('form.oe_form_binary_form').submit();
             $e.parent().find('input[type=file]').prop('disabled', true);
             $e.parent().find('button').prop('disabled', true).find('img, span').toggle();
+            this.$('.oe_sidebar_add_attachment span').text(_t('Uploading...'));
+            $.blockUI();
         }
     },
     on_attachment_delete: function(e) {
-        return;
-        var self = this, $e = $(e.currentTarget);
-        var name = _.str.trim($e.parent().find('a.oe-sidebar-attachments-link').text());
-        if (confirm(_.str.sprintf(_t("Do you really want to delete the attachment %s?"), name))) {
-            this.rpc('/web/dataset/unlink', {
-                model: 'ir.attachment',
-                ids: [parseInt($e.attr('data-id'))]
-            }, function(r) {
-                $e.parent().remove();
-                self.do_notify("Delete an attachment", "The attachment '" + name + "' has been deleted");
+        var self = this;
+        e.preventDefault();
+        e.stopPropagation();
+        var self = this;
+        var $e = $(e.currentTarget);
+        if (confirm(_t("Do you really want to delete this attachment ?"))) {
+            (new instance.web.DataSet(this, 'ir.attachment')).unlink([parseInt($e.attr('data-id'), 10)]).then(function() {
+                self.do_attachement_update(self.dataset, self.model_id);
             });
         }
     }
@@ -884,8 +1022,8 @@ instance.web.TranslateDialog = instance.web.Dialog.extend({
         // TODO fme: should add the language to fields_view_get because between the fields view get
         // and the moment the user opens the translation dialog, the user language could have been changed
         this.view_language = view.session.user_context.lang;
-        this['on_button' + _t("Save")] = this.on_button_Save;
-        this['on_button' + _t("Close")] = this.on_button_Close;
+        this['on_button_' + _t("Save")] = this.on_btn_save;
+        this['on_button_' + _t("Close")] = this.on_btn_close;
         this._super(view, {
             width: '80%',
             height: '80%'
@@ -966,7 +1104,7 @@ instance.web.TranslateDialog = instance.web.Dialog.extend({
             }
         });
     },
-    on_button_Save: function() {
+    on_btn_save: function() {
         var trads = {},
             self = this,
             trads_mutex = new $.Mutex();
@@ -989,7 +1127,7 @@ instance.web.TranslateDialog = instance.web.Dialog.extend({
         });
         this.close();
     },
-    on_button_Close: function() {
+    on_btn_close: function() {
         this.close();
     }
 });