[FIX] restore attachement, part2 very beginning of add
[odoo/odoo.git] / addons / web / static / src / js / views.js
index 778bf6d..79d3d4c 100644 (file)
@@ -6,18 +6,7 @@ openerp.web.views = function(session) {
 var QWeb = session.web.qweb,
     _t = session.web._t;
 
-/**
- * Registry for all the client actions key: tag value: widget
- */
-session.web.client_actions = new session.web.Registry();
-
-/**
- * Registry for all the main views
- */
-session.web.views = new session.web.Registry();
-
 session.web.ActionManager = session.web.Widget.extend({
-    identifier_prefix: "actionmanager",
     init: function(parent) {
         this._super(parent);
         this.inner_action = null;
@@ -26,40 +15,36 @@ session.web.ActionManager = session.web.Widget.extend({
         this.dialog_viewmanager = null;
         this.client_widget = null;
     },
-    render: function() {
-        return '<div id="' + this.element_id + '" style="height: 100%;"></div>';
-    },
     dialog_stop: function () {
         if (this.dialog) {
-            this.dialog_viewmanager.stop();
+            this.dialog_viewmanager.destroy();
             this.dialog_viewmanager = null;
-            this.dialog.stop();
+            this.dialog.destroy();
             this.dialog = null;
         }
     },
     content_stop: function () {
         if (this.inner_viewmanager) {
-            this.inner_viewmanager.stop();
+            this.inner_viewmanager.destroy();
             this.inner_viewmanager = null;
         }
         if (this.client_widget) {
-            this.client_widget.stop();
+            this.client_widget.destroy();
             this.client_widget = null;
         }
     },
     do_push_state: function(state) {
-        if (this.widget_parent && this.widget_parent.do_push_state) {
+        if (this.getParent() && this.getParent().do_push_state) {
             if (this.inner_action) {
+                state['model'] = this.inner_action.res_model;
                 if (this.inner_action.id) {
                     state['action_id'] = this.inner_action.id;
-                } else {
-                    state['model'] = this.inner_action.res_model;
                 }
             }
-            this.widget_parent.do_push_state(state);
+            this.getParent().do_push_state(state);
         }
     },
-    do_load_state: function(state) {
+    do_load_state: function(state, warm) {
         var self = this,
             action_loaded;
         if (state.action_id) {
@@ -67,27 +52,37 @@ session.web.ActionManager = session.web.Widget.extend({
             if (run_action) {
                 this.null_action();
                 action_loaded = this.do_action(state.action_id);
+                session.webclient.menu.has_been_loaded.then(function() {
+                    session.webclient.menu.open_action(state.action_id);
+                });
             }
-        }
-        else if (state.model && state.id) {
+        } else if (state.model && state.id) {
             // TODO handle context & domain ?
             this.null_action();
             var action = {
                 res_model: state.model,
                 res_id: state.id,
                 type: 'ir.actions.act_window',
-                views: [[false, 'page'], [false, 'form']]
+                views: [[false, 'form']]
             };
             action_loaded = this.do_action(action);
-        }
-        else if (state.client_action) {
+        } else if (state.sa) {
+            // load session action
+            var self = this;
+            this.null_action();
+            action_loaded = this.rpc('/web/session/get_session_action',  {key: state.sa}).pipe(function(action) {
+                if (action) {
+                    return self.do_action(action);
+                }
+            });
+        } else if (state.client_action) {
             this.null_action();
             this.ir_actions_client(state.client_action);
         }
 
         $.when(action_loaded || null).then(function() {
             if (self.inner_viewmanager) {
-                self.inner_viewmanager.do_load_state(state);
+                self.inner_viewmanager.do_load_state(state, warm);
             }
         });
     },
@@ -104,12 +99,13 @@ session.web.ActionManager = session.web.Widget.extend({
         }
         var type = action.type.replace(/\./g,'_');
         var popup = action.target === 'new';
+        var inline = action.target === 'inline';
         action.flags = _.extend({
-            views_switcher : !popup,
-            search_view : !popup,
-            action_buttons : !popup,
-            sidebar : !popup,
-            pager : !popup,
+            views_switcher : !popup && !inline,
+            search_view : !popup && !inline,
+            action_buttons : !popup && !inline,
+            sidebar : !popup && !inline,
+            pager : !popup && !inline,
             display_title : !popup
         }, action.flags || {});
         if (!(type in this)) {
@@ -128,8 +124,7 @@ session.web.ActionManager = session.web.Widget.extend({
                 .contains(action.res_model)) {
             var old_close = on_close;
             on_close = function () {
-                session.webclient.do_reload();
-                if (old_close) { old_close(); }
+                session.webclient.do_reload().then(old_close);
             };
         }
         if (action.target === 'new') {
@@ -137,9 +132,8 @@ session.web.ActionManager = session.web.Widget.extend({
                 this.dialog = new session.web.Dialog(this, { width: '80%' });
                 if(on_close)
                     this.dialog.on_close.add(on_close);
-                this.dialog.start();
             } else {
-                this.dialog_viewmanager.stop();
+                this.dialog_viewmanager.destroy();
             }
             this.dialog.dialog_title = action.name;
             this.dialog_viewmanager = new session.web.ViewManagerAction(this, action);
@@ -147,7 +141,7 @@ session.web.ActionManager = session.web.Widget.extend({
             this.dialog.open();
         } else  {
             if(action.menu_id) {
-                return this.widget_parent.do_action(action, function () {
+                return this.getParent().do_action(action, function () {
                     session.webclient.menu.open_menu(action.menu_id);
                 });
             }
@@ -177,7 +171,7 @@ session.web.ActionManager = session.web.Widget.extend({
         this.content_stop();
         this.dialog_stop();
         var ClientWidget = session.web.client_actions.get_object(action.tag);
-        (this.client_widget = new ClientWidget(this, action.params)).appendTo(this);
+        (this.client_widget = new ClientWidget(this, action.params)).appendTo(this.$element);
     },
     ir_actions_report_xml: function(action, on_closed) {
         var self = this;
@@ -197,7 +191,8 @@ session.web.ActionManager = session.web.Widget.extend({
                         on_closed();
                     }
                     self.dialog_stop();
-                }
+                },
+                error: session.webclient.crashmanager.on_rpc_error
             })
         });
     },
@@ -205,21 +200,12 @@ session.web.ActionManager = session.web.Widget.extend({
         window.open(action.url, action.target === 'self' ? '_self' : '_blank');
     },
     ir_ui_menu: function (action) {
-        this.widget_parent.do_action(action);
+        this.getParent().do_action(action);
     }
 });
 
-session.web.ViewManager =  session.web.Widget.extend(/** @lends session.web.ViewManager# */{
-    identifier_prefix: "viewmanager",
+session.web.ViewManager =  session.web.Widget.extend({
     template: "ViewManager",
-    /**
-     * @constructs session.web.ViewManager
-     * @extends session.web.Widget
-     *
-     * @param parent
-     * @param dataset
-     * @param views
-     */
     init: function(parent, dataset, views, flags) {
         this._super(parent);
         this.model = dataset ? dataset.model : undefined;
@@ -243,19 +229,13 @@ session.web.ViewManager =  session.web.Widget.extend(/** @lends session.web.View
         this.registry = session.web.views;
         this.views_history = [];
     },
-    render: function() {
-        return session.web.qweb.render(this.template, {
-            self: this,
-            prefix: this.element_id,
-            views: this.views_src});
-    },
     /**
      * @returns {jQuery.Deferred} initial view loading promise
      */
     start: function() {
         this._super();
         var self = this;
-        this.$element.find('.oe_vm_switch button').click(function() {
+        this.$element.find('.oe_view_manager_switch a').click(function() {
             self.on_mode_switch($(this).data('view-type'));
         });
         var views_ids = {};
@@ -264,7 +244,9 @@ session.web.ViewManager =  session.web.Widget.extend(/** @lends session.web.View
                 deferred : $.Deferred(),
                 controller : null,
                 options : _.extend({
-                    sidebar_id : self.element_id + '_sidebar_' + view.view_type,
+                    $buttons : self.$element.find('.oe_view_manager_buttons'),
+                    $sidebar : self.$element.find('.oe_view_manager_sidebar'),
+                    $pager : self.$element.find('.oe_view_manager_pager'),
                     action : self.action,
                     action_views_ids : views_ids
                 }, self.flags, self.flags[view.view_type] || {}, view.options || {})
@@ -272,7 +254,7 @@ session.web.ViewManager =  session.web.Widget.extend(/** @lends session.web.View
             views_ids[view.view_type] = view.view_id;
         });
         if (this.flags.views_switcher === false) {
-            this.$element.find('.oe_vm_switch').hide();
+            this.$element.find('.oe_view_manager_switch').hide();
         }
         // If no default view defined, switch to the first one in sequence
         var default_view = this.flags.default_view || this.views_src[0].view_type;
@@ -286,9 +268,9 @@ session.web.ViewManager =  session.web.Widget.extend(/** @lends session.web.View
      * @returns {jQuery.Deferred} new view loading promise
      */
     on_mode_switch: function(view_type, no_store) {
-        var self = this,
-            view = this.views[view_type],
-            view_promise;
+        var self = this;
+        var view = this.views[view_type];
+        var view_promise;
         if(!view)
             return $.Deferred().reject();
 
@@ -300,23 +282,36 @@ session.web.ViewManager =  session.web.Widget.extend(/** @lends session.web.View
         if (!view.controller) {
             // Lazy loading of views
             var controllerclass = this.registry.get_object(view_type);
-            var controller = new controllerclass(this, this.dataset, view.view_id, view.options);
+            var options = _.clone(view.options);
+            if (view_type === "form") {
+                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(this.on_mode_switch);
             controller.do_prev_view.add_last(this.on_prev_view);
-            var container = $("#" + this.element_id + '_view_' + view_type);
+            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 && view.controller.searchable !== false) {
+                if (self.searchview
+                        && self.flags.auto_search
+                        && view.controller.searchable !== false) {
                     self.searchview.ready.then(self.searchview.do_search);
                 }
             });
-        } else if (this.searchview && view.controller.searchable !== false) {
+        } else if (this.searchview
+                && self.flags.auto_search
+                && view.controller.searchable !== false) {
             this.searchview.ready.then(this.searchview.do_search);
         }
 
@@ -325,9 +320,10 @@ session.web.ViewManager =  session.web.Widget.extend(/** @lends session.web.View
         }
 
         this.$element
-            .find('.oe_vm_switch button').removeAttr('disabled')
-            .filter('[data-view-type="' + view_type + '"]')
-            .attr('disabled', true);
+            .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');
 
         $.when(view_promise).then(function () {
             _.each(_.keys(self.views), function(view_name) {
@@ -351,19 +347,21 @@ session.web.ViewManager =  session.web.Widget.extend(/** @lends session.web.View
      * navigation history (the navigation history is appended to via
      * on_mode_switch)
      *
-     * @param {Boolean} [created=false] returning from a creation
+     * @param {Object} [options]
+     * @param {Boolean} [options.created=false] resource was created
+     * @param {String} [options.default=null] view to switch to if no previous view
      * @returns {$.Deferred} switching end signal
      */
-    on_prev_view: function (created) {
+    on_prev_view: function (options) {
         var current_view = this.views_history.pop();
-        var previous_view = this.views_history[this.views_history.length - 1];
-        if (created && current_view === 'form' && previous_view === 'list') {
+        var previous_view = this.views_history[this.views_history.length - 1] || options['default'];
+        if (options.created && current_view === 'form' && previous_view === 'list') {
             // APR special case: "If creation mode from list (and only from a list),
             // after saving, go to page view (don't come back in list)"
-            return this.on_mode_switch('page');
-        } else if (created && !previous_view && this.action && this.action.flags.default_view === 'form') {
+            return this.on_mode_switch('form');
+        } else if (options.created && !previous_view && this.action && this.action.flags.default_view === 'form') {
             // APR special case: "If creation from dashboard, we have no previous view
-            return this.on_mode_switch('page');
+            return this.on_mode_switch('form');
         }
         return this.on_mode_switch(previous_view, true);
     },
@@ -376,14 +374,12 @@ session.web.ViewManager =  session.web.Widget.extend(/** @lends session.web.View
     setup_search_view: function(view_id, search_defaults) {
         var self = this;
         if (this.searchview) {
-            this.searchview.stop();
+            this.searchview.destroy();
         }
-        this.searchview = new session.web.SearchView(
-                this, this.dataset,
-                view_id, search_defaults, this.flags.search_view === false);
+        this.searchview = new session.web.SearchView(this, this.dataset, view_id, search_defaults, this.flags.search_view === false);
 
         this.searchview.on_search.add(this.do_searchview_search);
-        return this.searchview.appendTo($("#" + this.element_id + "_search"));
+        return this.searchview.appendTo(this.$element.find(".oe_view_manager_view_search"));
     },
     do_searchview_search: function(domains, contexts, groupbys) {
         var self = this,
@@ -399,6 +395,9 @@ session.web.ViewManager =  session.web.Widget.extend(/** @lends session.web.View
             var groupby = results.group_by.length
                         ? results.group_by
                         : action_context.group_by;
+            if (_.isString(groupby)) {
+                groupby = [groupby];
+            }
             controller.do_search(results.domain, results.context, groupby || []);
         });
     },
@@ -436,7 +435,7 @@ session.web.ViewManager =  session.web.Widget.extend(/** @lends session.web.View
     }
 });
 
-session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepnerp.web.ViewManagerAction# */{
+session.web.ViewManagerAction = session.web.ViewManager.extend({
     template:"ViewManagerAction",
     /**
      * @constructs session.web.ViewManagerAction
@@ -450,6 +449,9 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
         // do not have it yet (and we don't, because we've not called our own
         // ``_super()``) rpc requests will blow up.
         var flags = action.flags || {};
+        if (!('auto_search' in flags)) {
+            flags.auto_search = action.auto_search !== false;
+        }
         if (action.res_model == 'board.board' && action.view_mode === 'form') {
             // Special case for Dashboards
             _.extend(flags, {
@@ -519,7 +521,7 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
                 }
             });
             if (!(self.action.id in self.session.hidden_menutips)) {
-                Users.read_ids([this.session.uid], ['menu_tips'], function(users) {
+                Users.read_ids([this.session.uid], ['menu_tips']).then(function(users) {
                     var user = users[0];
                     if (!(user && user.id === self.session.uid)) {
                         return;
@@ -529,55 +531,80 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
             }
         }
 
-        var $res_logs = this.$element.find('.oe-view-manager-logs:first');
-        $res_logs.delegate('a.oe-more-logs', 'click', function () {
-            $res_logs.removeClass('oe-folded');
-            return false;
-        }).delegate('a.oe-remove-everything', 'click', function () {
-            $res_logs.removeClass('oe-has-more').find('ul').empty();
-            $res_logs.css('display','none');
-            return false;
-        });
-        $res_logs.css('display','none');
-
         return manager_ready;
     },
     on_debug_changed: function (evt) {
-        var $sel = $(evt.currentTarget),
+        var self = this,
+            $sel = $(evt.currentTarget),
             $option = $sel.find('option:selected'),
-            val = $sel.val();
+            val = $sel.val(),
+            current_view = this.views[this.active_view].controller;
         switch (val) {
             case 'fvg':
-                $('<pre>').text(session.web.json_node_to_xml(
-                    this.views[this.active_view].controller.fields_view.arch, true)
-                ).dialog({ width: '95%'});
+                var dialog = new session.web.Dialog(this, { title: _t("Fields View Get"), width: '95%' }).open();
+                $('<pre>').text(session.web.json_node_to_xml(current_view.fields_view.arch, true)).appendTo(dialog.$element);
                 break;
-            case 'edit':
-                var model = $option.data('model'),
-                    id = $option.data('id'),
-                    domain = $option.data('domain'),
-                    action = {
-                        res_model : model,
-                        type : 'ir.actions.act_window',
-                        view_type : 'form',
-                        view_mode : 'form',
-                        target : 'new',
-                        flags : {
-                            action_buttons : true,
-                            form : {
-                                resize_textareas : true
+            case 'perm_read':
+                var ids = current_view.get_selected_ids();
+                if (ids.length === 1) {
+                    this.dataset.call('perm_read', [ids]).then(function(result) {
+                        var dialog = new session.web.Dialog(this, {
+                            title: _.str.sprintf(_t("View Log (%s)"), self.dataset.model),
+                            width: 400
+                        }, QWeb.render('ViewManagerDebugViewLog', {
+                            perm : result[0],
+                            format : session.web.format_value
+                        })).open();
+                    });
+                }
+                break;
+            case 'toggle_layout_outline':
+                current_view.rendering_engine.toggle_layout_debugging();
+                break;
+            case 'fields':
+                this.dataset.call_and_eval(
+                        'fields_get', [false, {}], null, 1).then(function (fields) {
+                    var $root = $('<dl>');
+                    _(fields).each(function (attributes, name) {
+                        $root.append($('<dt>').append($('<h4>').text(name)));
+                        var $attrs = $('<dl>').appendTo(
+                                $('<dd>').appendTo($root));
+                        _(attributes).each(function (def, name) {
+                            if (def instanceof Object) {
+                                def = JSON.stringify(def);
                             }
-                        }
-                    };
-                if (id) {
-                    action.res_id = id,
-                    action.views = [[false, 'form']];
-                } else if (domain) {
-                    action.views = [[false, 'list'], [false, 'form']];
-                    action.domain = domain;
-                    action.flags.views_switcher = true;
+                            $attrs
+                                .append($('<dt>').text(name))
+                                .append($('<dd style="white-space: pre-wrap;">').text(def));
+                        });
+                    });
+                    new session.web.Dialog(self, {
+                        title: _.str.sprintf(_t("Model %s fields"),
+                                             self.dataset.model),
+                        width: '95%'}, $root).open();
+                });
+                break;
+            case 'manage_views':
+                if (current_view.fields_view && current_view.fields_view.arch) {
+                    var view_editor = new session.web.ViewEditor(current_view, current_view.$element, this.dataset, current_view.fields_view.arch);
+                    view_editor.start();
+                } else {
+                    this.do_warn(_t("Manage Views"),
+                            _t("Could not find current view declaration"));
                 }
-                this.do_action(action);
+                break;
+            case 'edit_workflow':
+                return this.do_action({
+                    res_model : 'workflow',
+                    domain : [['osv', '=', this.dataset.model]],
+                    views: [[false, 'list'], [false, 'form'], [false, 'diagram']],
+                    type : 'ir.actions.act_window',
+                    view_type : 'list',
+                    view_mode : 'list'
+                });
+                break;
+            case 'edit':
+                this.do_edit_resource($option.data('model'), $option.data('id'), { name : $option.text() });
                 break;
             default:
                 if (val) {
@@ -586,14 +613,30 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
         }
         evt.currentTarget.selectedIndex = 0;
     },
+    do_edit_resource: function(model, id, action) {
+        var action = _.extend({
+            res_model : model,
+            res_id : id,
+            type : 'ir.actions.act_window',
+            view_type : 'form',
+            view_mode : 'form',
+            views : [[false, 'form']],
+            target : 'new',
+            flags : {
+                action_buttons : true,
+                form : {
+                    resize_textareas : true
+                }
+            }
+        }, action || {});
+        this.do_action(action);
+    },
     on_mode_switch: function (view_type, no_store) {
         var self = this;
 
         return $.when(this._super(view_type, no_store)).then(function () {
             self.shortcut_check(self.views[view_type]);
 
-            self.$element.find('.oe-view-manager-logs:first').addClass('oe-folded').removeClass('oe-has-more').css('display','none').find('ul').empty();
-
             var controller = self.views[self.active_view].controller,
                 fvg = controller.fields_view,
                 view_id = (fvg && fvg.view_id) || '--';
@@ -617,12 +660,12 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
         });
     },
     do_push_state: function(state) {
-        if (this.widget_parent && this.widget_parent.do_push_state) {
+        if (this.getParent() && this.getParent().do_push_state) {
             state["view_type"] = this.active_view;
-            this.widget_parent.do_push_state(state);
+            this.getParent().do_push_state(state);
         }
     },
-    do_load_state: function(state) {
+    do_load_state: function(state, warm) {
         var self = this,
             defs = [];
         if (state.view_type && state.view_type !== this.active_view) {
@@ -634,12 +677,12 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
         } 
 
         $.when(defs).then(function() {
-            self.views[self.active_view].controller.do_load_state(state);
+            self.views[self.active_view].controller.do_load_state(state, warm);
         });
     },
     shortcut_check : function(view) {
         var self = this;
-        var grandparent = this.widget_parent && this.widget_parent.widget_parent;
+        var grandparent = this.getParent() && this.getParent().getParent();
         // display shortcuts if on the first view for the action
         var $shortcut_toggle = this.$element.find('.oe-shortcut-toggle');
         if (!this.action.name ||
@@ -675,194 +718,127 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
                 }
             });
     },
-    /**
-     * Intercept do_action resolution from children views
-     */
-    on_action_executed: function () {
-        return new session.web.DataSet(this, 'res.log')
-                .call('get', [], this.do_display_log);
-    },
-    /**
-     * @param {Array<Object>} log_records
-     */
-    do_display_log: function (log_records) {
-        var self = this;
-        var cutoff = 3;
-        var $logs = this.$element.find('.oe-view-manager-logs:first').addClass('oe-folded').css('display', 'block');
-        var $logs_list = $logs.find('ul').empty();
-        $logs.toggleClass('oe-has-more', log_records.length > cutoff);
-        _(log_records.reverse()).each(function (record) {
-            $(_.str.sprintf('<li><a href="#">%s</a></li>', record.name))
-                .appendTo($logs_list)
-                .delegate('a', 'click', function (e) {
-                    self.do_action({
-                        type: 'ir.actions.act_window',
-                        res_model: record.res_model,
-                        res_id: record.res_id,
-                        // TODO: need to have an evaluated context here somehow
-                        //context: record.context,
-                        views: [[false, 'form']]
-                    });
-                    return false;
-                });
-        });
-    },
     display_title: function () {
         return this.action.name;
     }
 });
 
 session.web.Sidebar = session.web.Widget.extend({
-    init: function(parent, element_id) {
-        this._super(parent, element_id);
-        this.items = {};
-        this.sections = {};
+    init: function(parent) {
+        this._super(parent);
+        var view = this.getParent();
+        this.sections = [
+            { 'name' : 'print', 'label' : _t('Print'), },
+            { 'name' : 'files', 'label' : _t('Attachement'), },
+            { 'name' : 'other', 'label' : _t('More'), }
+        ];
+        this.items = {
+            'print' : [],
+            'files' : [],
+            'other' : [
+                    { label: _t("Import"), callback: view.on_sidebar_import },
+                    { label: _t("Export"), callback: view.on_sidebar_export }
+            ]
+        }
+        if (this.session.uid === 1) {
+            var item = { label: _t("Translate"), callback: view.on_sidebar_translate, title: _t("Technical translation") };
+            this.items.other.push(item);
+        }
     },
     start: function() {
-        this._super(this);
         var self = this;
-        this.$element.html(session.web.qweb.render('Sidebar'));
-        this.$element.find(".toggle-sidebar").click(function(e) {
-            self.do_toggle();
+        this._super(this);
+        this.redraw();
+        this.$element.on('click','.oe_dropdown_toggle',function(event) {
+            $(this).parent().find('ul').toggle();
+            return false;
         });
-    },
-    add_default_sections: function() {
-        var self = this,
-            view = this.widget_parent,
-            view_manager = view.widget_parent,
-            action = view_manager.action;
-        if (this.session.uid === 1) {
-            this.add_section(_t('Customize'), 'customize');
-            this.add_items('customize', [
-                {
-                    label: _t("Manage Views"),
-                    callback: view.on_sidebar_manage_views,
-                    title: _t("Manage views of the current object")
-                }, {
-                    label: _t("Edit Workflow"),
-                    callback: view.on_sidebar_edit_workflow,
-                    title: _t("Manage views of the current object"),
-                    classname: 'oe_sidebar_edit_workflow'
-                }, {
-                    label: _t("Customize Object"),
-                    callback: view.on_sidebar_customize_object,
-                    title: _t("Manage views of the current object")
-                }, {
-                    label: _t("Translate"),
-                    callback: view.on_sidebar_translate,
-                    title: _t("Technical translation")
-                }
-            ]);
-        }
-
-        this.add_section(_t('Other Options'), 'other');
-        this.add_items('other', [
-            {
-                label: _t("Import"),
-                callback: view.on_sidebar_import
-            }, {
-                label: _t("Export"),
-                callback: view.on_sidebar_export
-            }, {
-                label: _t("View Log"),
-                callback: view.on_sidebar_view_log,
-                classname: 'oe_hide oe_sidebar_view_log'
+        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]);
+            } else if (item.action) {
+                self.on_item_action_clicked(item);
+            } else if (item.url) {
+                return true;
             }
-        ]);
+            return false;
+        });
+        //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();
+    },
+    add_section: function() {
+        var self = this;
     },
-
     add_toolbar: function(toolbar) {
         var self = this;
-        _.each([['print', _t("Reports")], ['action', _t("Actions")], ['relate', _t("Links")]], function(type) {
-            var items = toolbar[type[0]];
-            if (items.length) {
+        _.each(['print','action','relate'], function(type) {
+            var items = toolbar[type];
+            if (items) {
                 for (var i = 0; i < items.length; i++) {
                     items[i] = {
                         label: items[i]['name'],
                         action: items[i],
-                        classname: 'oe_sidebar_' + type[0]
+                        classname: 'oe_sidebar_' + type
                     }
                 }
-                self.add_section(type[1], type[0]);
-                self.add_items(type[0], items);
+                self.add_items(type=='print' ? 'print' : 'other', items);
             }
         });
     },
-
-    add_section: function(name, code) {
-        if(!code) code = _.str.underscored(name);
-        var $section = this.sections[code];
-
-        if(!$section) {
-            var section_id = _.uniqueId(this.element_id + '_section_' + code + '_');
-            $section = $(session.web.qweb.render("Sidebar.section", {
-                section_id: section_id,
-                name: name,
-                classname: 'oe_sidebar_' + code
-            }));
-            $section.appendTo(this.$element.find('div.sidebar-actions'));
-            this.sections[code] = $section;
-        }
-        return $section;
-    },
-
+    /**
+     * For each item added to the section:
+     *
+     * ``label``
+     *     will be used as the item's name in the sidebar, can be html
+     *
+     * ``action``
+     *     descriptor for the action which will be executed, ``action`` and
+     *     ``callback`` should be exclusive
+     *
+     * ``callback``
+     *     function to call when the item is clicked in the sidebar, called
+     *     with the item descriptor as its first argument (so information
+     *     can be stored as additional keys on the object passed to
+     *     ``add_items``)
+     *
+     * ``classname`` (optional)
+     *     ``@class`` set on the sidebar serialization of the item
+     *
+     * ``title`` (optional)
+     *     will be set as the item's ``@title`` (tooltip)
+     *
+     * @param {String} section_code
+     * @param {Array<{label, action | callback[, classname][, title]}>} items
+     */
     add_items: function(section_code, items) {
-        // An item is a dictonary : {
-        //    label: label to be displayed for the link,
-        //    action: action to be launch when the link is clicked,
-        //    callback: a function to be executed when the link is clicked,
-        //    classname: optional dom class name for the line,
-        //    title: optional title for the link
-        // }
-        // Note: The item should have one action or/and a callback
-        //
-
-        var self = this,
-            $section = this.add_section(_.str.titleize(section_code.replace('_', ' ')), section_code),
-            section_id = $section.attr('id');
-
+        var self = this;
         if (items) {
-            for (var i = 0; i < items.length; i++) {
-                items[i].element_id = _.uniqueId(section_id + '_item_');
-                this.items[items[i].element_id] = items[i];
-            }
-
-            var $items = $(session.web.qweb.render("Sidebar.section.items", {items: items}));
-
-            $items.find('a.oe_sidebar_action_a').click(function() {
-                var item = self.items[$(this).attr('id')];
-                if (item.callback) {
-                    item.callback.apply(self, [item]);
-                }
-                if (item.action) {
-                    self.on_item_action_clicked(item);
-                }
-                return false;
-            });
-
-            var $ul = $section.find('ul');
-            if(!$ul.length) {
-                $ul = $('<ul/>').appendTo($section);
-            }
-            $items.appendTo($ul);
+            this.items[section_code].push.apply(this.items[section_code],items);
+            this.redraw();
         }
     },
     on_item_action_clicked: function(item) {
         var self = this;
-        self.widget_parent.sidebar_context().then(function (context) {
-            var ids = self.widget_parent.get_selected_ids();
+        self.getParent().sidebar_context().then(function (context) {
+            var ids = self.getParent().get_selected_ids();
             if (ids.length == 0) {
-                //TODO: make prettier warning?
-                $("<div />").text(_t("You must choose at least one record.")).dialog({
-                    title: _t("Warning"),
-                    modal: true
-                });
+                session.web.dialog($("<div />").text(_t("You must choose at least one record.")), { title: _t("Warning"), modal: true });
                 return false;
             }
             var additional_context = _.extend({
                 active_id: ids[0],
                 active_ids: ids,
-                active_model: self.widget_parent.dataset.model
+                active_model: self.getParent().dataset.model
             }, context);
             self.rpc("/web/action/load", {
                 action_id: item.action.id,
@@ -874,19 +850,61 @@ session.web.Sidebar = session.web.Widget.extend({
                 result.result.flags.new_window = true;
                 self.do_action(result.result, function () {
                     // reload view
-                    self.widget_parent.reload();
+                    self.getParent().reload();
                 });
             });
         });
     },
-    do_fold: function() {
-        this.$element.addClass('closed-sidebar').removeClass('open-sidebar');
+    do_attachement_update: function(dataset, model_id) {
+        if (!model_id) {
+            this.on_attachments_loaded([]);
+        } else {
+            var dom = [ ['res_model', '=', dataset.model], ['res_id', '=', model_id], ['type', 'in', ['binary', 'url']] ];
+            var ds = new session.web.DataSetSearch(this, 'ir.attachment', dataset.get_context(), dom);
+            ds.read_slice(['name', 'url', 'type'], {}).then(this.on_attachments_loaded);
+        }
     },
-    do_unfold: function() {
-        this.$element.addClass('open-sidebar').removeClass('closed-sidebar');
+    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=';
+        _.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();
+    },
+    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();
+            $e.parent().find('input[type=file]').prop('disabled', true);
+            $e.parent().find('button').prop('disabled', true).find('img, span').toggle();
+        }
     },
-    do_toggle: function() {
-        this.$element.toggleClass('open-sidebar closed-sidebar');
+    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");
+            });
+        }
     }
 });
 
@@ -911,7 +929,7 @@ session.web.TranslateDialog = session.web.Dialog.extend({
         this.languages = null;
         this.languages_loaded = $.Deferred();
         (new session.web.DataSetSearch(this, 'res.lang', this.view.dataset.get_context(),
-            [['translatable', '=', '1']])).read_slice(['code', 'name'], { sort: 'id' }, this.on_languages_loaded);
+            [['translatable', '=', '1']])).read_slice(['code', 'name'], { sort: 'id' }).then(this.on_languages_loaded);
     },
     start: function() {
         var self = this;
@@ -980,7 +998,8 @@ session.web.TranslateDialog = session.web.Dialog.extend({
     },
     on_button_Save: function() {
         var trads = {},
-            self = this;
+            self = this,
+            trads_mutex = new $.Mutex();
         self.$fields_form.find('.oe_trad_field.touched').each(function() {
             var field = $(this).attr('name').split('-');
             if (!trads[field[0]]) {
@@ -993,9 +1012,10 @@ session.web.TranslateDialog = session.web.Dialog.extend({
                 _.each(data, function(value, field) {
                     self.view.fields[field].set_value(value);
                 });
-            } else {
-                self.view.dataset.write(self.view.datarecord.id, data, { 'lang': code });
             }
+            trads_mutex.exec(function() {
+                return self.view.dataset.write(self.view.datarecord.id, data, { context : { 'lang': code } });
+            });
         });
         this.close();
     },
@@ -1004,14 +1024,53 @@ session.web.TranslateDialog = session.web.Dialog.extend({
     }
 });
 
-session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
+session.web.View = session.web.Widget.extend({
     template: "EmptyComponent",
     // name displayed in view switchers
     display_name: '',
+    /**
+     * Define a view type for each view to allow automatic call to fields_view_get.
+     */
+    view_type: undefined,
+    init: function(parent, dataset, view_id, options) {
+        this._super(parent);
+        this.dataset = dataset;
+        this.view_id = view_id;
+        this.set_default_options(options);
+    },
+    start: function() {
+        return this.load_view();
+    },
+    load_view: function() {
+        if (this.embedded_view) {
+            var def = $.Deferred();
+            var self = this;
+            $.async_when().then(function() {def.resolve(self.embedded_view);});
+            return def.pipe(this.on_loaded);
+        } else {
+            var context = new session.web.CompoundContext(this.dataset.get_context());
+            if (! this.view_type)
+                console.warn("view_type is not defined", this);
+            return this.rpc("/web/view/load", {
+                "model": this.dataset.model,
+                "view_id": this.view_id,
+                "view_type": this.view_type,
+                toolbar: this.options.sidebar,
+                context: context
+                }).pipe(this.on_loaded);
+        }
+    },
+    /**
+     * Called after a successful call to fields_view_get.
+     * Must return a promise.
+     */
+    on_loaded: function(fields_view_get) {
+    },
     set_default_options: function(options) {
         this.options = options || {};
         _.defaults(this.options, {
             // All possible views options should be defaulted here
+            $sidebar: null,
             sidebar_id: null,
             sidebar: true,
             action: null,
@@ -1040,8 +1099,8 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
         var self = this;
         var result_handler = function () {
             if (on_closed) { on_closed.apply(null, arguments); }
-            if (self.widget_parent && self.widget_parent.on_action_executed) {
-                return self.widget_parent.on_action_executed.apply(null, arguments);
+            if (self.getParent() && self.getParent().on_action_executed) {
+                return self.getParent().on_action_executed.apply(null, arguments);
             }
         };
         var context = new session.web.CompoundContext(dataset.get_context(), action_data.context || {});
@@ -1113,11 +1172,11 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
         this.$element.hide();
     },
     do_push_state: function(state) {
-        if (this.widget_parent && this.widget_parent.do_push_state) {
-            this.widget_parent.do_push_state(state);
+        if (this.getParent() && this.getParent().do_push_state) {
+            this.getParent().do_push_state(state);
         }
     },
-    do_load_state: function(state) {
+    do_load_state: function(state, warm) {
     },
     /**
      * Switches to a specific view type
@@ -1128,42 +1187,15 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
     },
     /**
      * Cancels the switch to the current view, switches to the previous one
+     *
+     * @param {Object} [options]
+     * @param {Boolean} [options.created=false] resource was created
+     * @param {String} [options.default=null] view to switch to if no previous view
      */
-    do_prev_view: function () { 
+    do_prev_view: function (options) {
     },
     do_search: function(view) {
     },
-    set_common_sidebar_sections: function(sidebar) {
-        sidebar.add_default_sections();
-    },
-    on_sidebar_manage_views: function() {
-        if (this.fields_view && this.fields_view.arch) {
-            var view_editor = new session.web.ViewEditor(this, this.$element, this.dataset, this.fields_view.arch);
-            view_editor.start();
-        } else {
-            this.do_warn("Manage Views", "Could not find current view declaration");
-        }
-    },
-    on_sidebar_edit_workflow: function() {
-        return this.do_action({
-            res_model : 'workflow',
-            domain : [['osv', '=', this.dataset.model]],
-            views: [[false, 'list'], [false, 'form']],
-            type : 'ir.actions.act_window',
-            view_type : "list",
-            view_mode : "list"
-        });
-    },
-    on_sidebar_customize_object: function() {
-        var self = this;
-        this.rpc('/web/dataset/search_read', {
-            model: 'ir.model',
-            fields: ['id'],
-            domain: [['model', '=', self.dataset.model]]
-        }, function (result) {
-            self.on_sidebar_edit_resource('ir.model', result.ids[0]);
-        });
-    },
     on_sidebar_import: function() {
         var import_view = new session.web.DataImport(this, this.dataset);
         import_view.start();
@@ -1182,29 +1214,6 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
             view_mode : "list"
         });
     },
-    on_sidebar_edit_resource: function(model, id, domain) {
-        var action = {
-            res_model : model,
-            type : 'ir.actions.act_window',
-            view_type : 'form',
-            view_mode : 'form',
-            target : 'new',
-            flags : {
-                action_buttons : true
-            }
-        }
-        if (id) {
-            action.res_id = id,
-            action.views = [[false, 'form']];
-        } else if (domain) {
-            action.views = [[false, 'list'], [false, 'form']];
-            action.domain = domain;
-            action.flags.views_switcher = true;
-        }
-        this.do_action(action);
-    },
-    on_sidebar_view_log: function() {
-    },
     sidebar_context: function () {
         return $.when();
     },
@@ -1217,9 +1226,31 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
     }
 });
 
+session.web.xml_to_json = function(node) {
+    switch (node.nodeType) {
+        case 3:
+        case 4:
+            return node.data;
+        break;
+        case 1:
+            var attrs = $(node).getAttributes();
+            _.each(['domain', 'filter_domain', 'context', 'default_get'], function(key) {
+                if (attrs[key]) {
+                    try {
+                        attrs[key] = JSON.parse(attrs[key]);
+                    } catch(e) { }
+                }
+            });
+            return {
+                tag: node.tagName.toLowerCase(),
+                attrs: attrs,
+                children: _.map(node.childNodes, session.web.xml_to_json)
+            }
+    }
+}
 session.web.json_node_to_xml = function(node, human_readable, indent) {
     // For debugging purpose, this function will convert a json node back to xml
-    // Maybe usefull for xml view editor
+    // Maybe useful for xml view editor
     indent = indent || 0;
     var sindent = (human_readable ? (new Array(indent + 1).join('\t')) : ''),
         r = sindent + '<' + node.tag,
@@ -1255,6 +1286,43 @@ session.web.json_node_to_xml = function(node, human_readable, indent) {
         return r + '/>';
     }
 }
+session.web.xml_to_str = function(node) {
+    if (window.ActiveXObject) {
+        return node.xml;
+    } else {
+        return (new XMLSerializer()).serializeToString(node);
+    }
+}
+session.web.str_to_xml = function(s) {
+    if (window.DOMParser) {
+        var dp = new DOMParser();
+        var r = dp.parseFromString(s, "text/xml");
+        if (r.body && r.body.firstChild && r.body.firstChild.nodeName == 'parsererror') {
+            throw new Error("Could not parse string to xml");
+        }
+        return r;
+    }
+    var xDoc;
+    try {
+        xDoc = new ActiveXObject("MSXML2.DOMDocument");
+    } catch (e) {
+        throw new Error("Could not find a DOM Parser: " + e.message);
+    }
+    xDoc.async = false;
+    xDoc.preserveWhiteSpace = true;
+    xDoc.loadXML(s);
+    return xDoc;
+}
+
+/**
+ * Registry for all the client actions key: tag value: widget
+ */
+session.web.client_actions = new session.web.Registry();
+
+/**
+ * Registry for all the main views
+ */
+session.web.views = new session.web.Registry();
 
 };