[FIX] Field chars broken since merge 6.1
[odoo/odoo.git] / addons / web / static / src / js / view_form.js
index 2f30e83..c6295ea 100644 (file)
@@ -1,31 +1,58 @@
-openerp.web.form = function (openerp) {
+openerp.web.form = function (instance) {
+var _t = instance.web._t,
+   _lt = instance.web._lt;
+var QWeb = instance.web.qweb;
 
-var _t = openerp.web._t,
-   _lt = openerp.web._lt;
-var QWeb = openerp.web.qweb;
+/** @namespace */
+instance.web.form = {};
+
+/**
+ * Interface implemented by the form view or any other object
+ * able to provide the features necessary for the fields to work.
+ * 
+ * Properties:
+ *     - display_invalid_fields : if true, all fields where is_valid() return true should
+ *     be displayed as invalid.
+ * Events:
+ *     - view_content_has_changed : when the values of the fields have changed. When
+ *     this event is triggered all fields should reprocess their modifiers.
+ */
+instance.web.form.FieldManagerMixin = {
+    /**
+     * Must return the asked field as in fields_get.
+     */
+    get_field: function(field_name) {},
+    /**
+     * Called by the field when the translate button is clicked.
+     */
+    open_translate_dialog: function(field) {},
+    /**
+     * Returns true when the view is in create mode.
+     */
+    is_create_mode: function() {},
+};
 
-openerp.web.views.add('form', 'openerp.web.FormView');
-openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# */{
+instance.web.views.add('form', 'instance.web.FormView');
+instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerMixin, {
     /**
      * Indicates that this view is not searchable, and thus that no search
      * view should be displayed (if there is one active).
      */
     searchable: false,
-    readonly : false,
-    form_template: "FormView",
+    template: "FormView",
     display_name: _lt('Form'),
+    view_type: "form",
     /**
-     * @constructs openerp.web.FormView
-     * @extends openerp.web.View
+     * @constructs instance.web.FormView
+     * @extends instance.web.View
      *
-     * @param {openerp.web.Session} session the current openerp session
-     * @param {openerp.web.DataSet} dataset the dataset this view will work with
+     * @param {instance.web.Session} session the current openerp session
+     * @param {instance.web.DataSet} dataset the dataset this view will work with
      * @param {String} view_id the identifier of the OpenERP view object
      * @param {Object} options
-     *                  - sidebar : [true|false]
      *                  - resize_textareas : [true|false|max_height]
      *
-     * @property {openerp.web.Registry} registry=openerp.web.form.widgets widgets registry for this form view instance
+     * @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance
      */
     init: function(parent, dataset, view_id, options) {
         this._super(parent);
@@ -34,110 +61,148 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         this.model = dataset.model;
         this.view_id = view_id || false;
         this.fields_view = {};
-        this.widgets = {};
-        this.widgets_counter = 0;
         this.fields = {};
         this.fields_order = [];
         this.datarecord = {};
         this.default_focus_field = null;
         this.default_focus_button = null;
-        this.registry = openerp.web.form.widgets;
+        this.fields_registry = instance.web.form.widgets;
+        this.tags_registry = instance.web.form.tags;
         this.has_been_loaded = $.Deferred();
-        this.$form_header = null;
         this.translatable_fields = [];
         _.defaults(this.options, {
-            "not_interactible_on_create": false
+            "not_interactible_on_create": false,
+            "initial_mode": "view",
         });
         this.is_initialized = $.Deferred();
         this.mutating_mutex = new $.Mutex();
         this.on_change_mutex = new $.Mutex();
         this.reload_mutex = new $.Mutex();
-
         this.__clicked_inside = false;
         this.__blur_timeout = null;
+        this.rendering_engine = new instance.web.form.FormRenderingEngineReadonly(this);
+        this.qweb = null; // A QWeb instance will be created if the view is a QWeb template
     },
-    start: function() {
-        this._super();
-        return this.init_view();
-    },
-    init_view: function() {
-        if (this.embedded_view) {
-            var def = $.Deferred().then(this.on_loaded);
-            var self = this;
-            $.async_when().then(function() {def.resolve(self.embedded_view);});
-            return def.promise();
-        } else {
-            var context = new openerp.web.CompoundContext(this.dataset.get_context());
-            return this.rpc("/web/view/load", {
-                "model": this.model,
-                "view_id": this.view_id,
-                "view_type": "form",
-                toolbar: this.options.sidebar,
-                context: context
-                }, this.on_loaded);
-        }
-    },
-    stop: function() {
-        if (this.sidebar) {
-            this.sidebar.attachments.stop();
-            this.sidebar.stop();
-        }
-        _.each(this.widgets, function(w) {
-            $(w).unbind('.formBlur');
-            w.stop();
+    destroy: function() {
+        _.each(this.get_widgets(), function(w) {
+            w.off('focused blurred');
+            w.destroy();
         });
-        this.$element.unbind('.formBlur');
+        this.$element.off('.formBlur');
         this._super();
     },
-    reposition: function ($e) {
-        this.$element = $e;
-        this.on_loaded();
-    },
     on_loaded: function(data) {
         var self = this;
-        if (data) {
-            this.fields_order = [];
-            this.fields_view = data;
-            var frame = new (this.registry.get_object('frame'))(this, this.fields_view.arch);
+        if (!data) {
+            throw new Error("No data provided.");
+        }
+        if (this.arch) {
+            throw "Form view does not support multiple calls to on_loaded";
+        }
+        this.fields_order = [];
+        this.fields_view = data;
 
-            this.rendered = QWeb.render(this.form_template, { 'frame': frame, 'widget': this });
+        this.rendering_engine.set_fields_registry(this.fields_registry);
+        this.rendering_engine.set_tags_registry(this.tags_registry);
+        if (!this.extract_qweb_template(data)) {
+            this.rendering_engine.set_fields_view(data);
+            var $dest = this.$element.hasClass("oe_form_container") ? this.$element : this.$element.find('.oe_form_container');
+            this.rendering_engine.render_to($dest);
         }
-        this.$element.html(this.rendered);
-        this.$element.bind('mousedown.formBlur', function () {
+
+        this.$element.on('mousedown.formBlur', function () {
             self.__clicked_inside = true;
         });
-        _.each(this.widgets, function(w) {
-            w.start();
-            $(w).bind('widget-focus.formBlur', self.proxy('widgetFocused'))
-                .bind('widget-blur.formBlur', self.proxy('widgetBlurred'));
-        });
-        this.$form_header = this.$element.find('.oe_form_header:first');
-        this.$form_header.find('div.oe_form_pager button[data-pager-action]').click(function() {
+
+        this.$buttons = $(QWeb.render("FormView.buttons", {'widget':self}));
+        if (this.options.$buttons) {
+            this.$buttons.appendTo(this.options.$buttons);
+        } else {
+            this.$element.find('.oe_form_buttons').replaceWith(this.$buttons);
+        }
+        this.$buttons.on('click','.oe_form_button_create',this.on_button_create);
+        this.$buttons.on('click','.oe_form_button_edit',this.on_button_edit);
+        this.$buttons.on('click','.oe_form_button_save',this.on_button_save);
+        this.$buttons.on('click','.oe_form_button_cancel',this.on_button_cancel);
+
+        this.$pager = $(QWeb.render("FormView.pager", {'widget':self}));
+        if (this.options.$pager) {
+            this.$pager.appendTo(this.options.$pager);
+        } else {
+            this.$element.find('.oe_form_pager').replaceWith(this.$pager);
+        }
+        this.$pager.on('click','a[data-pager-action]',function() {
             var action = $(this).data('pager-action');
             self.on_pager_action(action);
         });
 
-        this.$form_header.find('button.oe_form_button_save').click(this.on_button_save);
-        this.$form_header.find('button.oe_form_button_cancel').click(this.on_button_cancel);
-
-        if (!this.sidebar && this.options.sidebar && this.options.sidebar_id) {
-            this.sidebar = new openerp.web.Sidebar(this, this.options.sidebar_id);
-            this.sidebar.start();
-            this.sidebar.do_unfold();
-            this.sidebar.attachments = new openerp.web.form.SidebarAttachments(this.sidebar, this);
-            this.sidebar.add_toolbar(this.fields_view.toolbar);
-            this.set_common_sidebar_sections(this.sidebar);
-
-            this.sidebar.add_section(_t('Customize'), 'customize');
-            this.sidebar.add_items('customize', [{
-                label: _t('Set Default'),
-                form: this,
-                callback: function (item) {
-                    item.form.open_defaults_dialog();
-                }
-            }]);
+        this.$sidebar = this.options.$sidebar || this.$element.find('.oe_form_sidebar');
+        if (!this.sidebar && this.options.$sidebar) {
+            this.sidebar = new instance.web.Sidebar(this);
+            this.sidebar.appendTo(this.$sidebar);
+            if(this.fields_view.toolbar) {
+                this.sidebar.add_toolbar(this.fields_view.toolbar);
+            }
+            this.sidebar.add_items('other', [
+                { label: _t('Delete'), callback: self.on_button_delete },
+                { label: _t('Duplicate'), callback: self.on_button_duplicate },
+                { label: _t('Set Default'), callback: function (item) { self.open_defaults_dialog(); } },
+            ]);
         }
+        this.on("change:mode", this, this.switch_mode);
+        this.set({mode: this.options.initial_mode});
         this.has_been_loaded.resolve();
+        return $.when();
+    },
+    extract_qweb_template: function(fvg) {
+        for (var i=0, ii=fvg.arch.children.length; i < ii; i++) {
+            var child = fvg.arch.children[i];
+            if (child.tag === "templates") {
+                this.qweb = new QWeb2.Engine();
+                this.qweb.add_template(instance.web.json_node_to_xml(child));
+                if (!this.qweb.has_template('form')) {
+                    throw new Error("No QWeb template found for form view");
+                }
+                return true;
+            }
+        }
+        this.qweb = null;
+        return false;
+    },
+    get_fvg_from_qweb: function(record) {
+        var view = this.qweb.render('form', this.get_qweb_context(record));
+        var fvg = _.clone(this.fields_view);
+        fvg.arch = instance.web.xml_to_json(instance.web.str_to_xml(view).firstChild);
+        return fvg;
+    },
+    get_qweb_context: function(record) {
+        var self = this,
+            new_record = {};
+        _.each(record, function(value_, name) {
+            var r = _.clone(self.fields_view.fields[name] || {});
+            if ((r.type === 'date' || r.type === 'datetime') && value_) {
+                r.raw_value = instance.web.auto_str_to_date(value_);
+            } else {
+                r.raw_value = value_;
+            }
+            r.value = instance.web.format_value(value_, r);
+            new_record[name] = r;
+        });
+        return {
+            record : new_record,
+            new_record : !record.id
+        };
+    },
+    kill_current_form: function() {
+        _.each(this.getChildren(), function(el) {
+            el.destroy();
+        });
+        this.fields = {};
+        this.fields_order = [];
+        this.default_focus_field = null;
+        this.default_focus_button = null;
+        this.translatable_fields = [];
+        this.$element.find('.oe_form_container').empty();
     },
 
     widgetFocused: function() {
@@ -159,7 +224,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         // clear timeout, if any
         this.widgetFocused();
         this.__blur_timeout = setTimeout(function () {
-            $(self).trigger('form-blur');
+            self.trigger('blurred');
         }, 0);
     },
 
@@ -174,9 +239,19 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             }
         }
     },
-
-    do_show: function () {
+    do_show: function (options) {
         var self = this;
+        options = options || {};
+        if (this.sidebar) {
+            this.sidebar.$element.show();
+        }
+        if (this.$buttons) {
+            this.$buttons.show();
+            this.$buttons.find('.oe_form_button_save').removeClass('oe_form_button_save_dirty');
+        }
+        if (this.$pager) {
+            this.$pager.show();
+        }
         this.$element.show().css('visibility', 'hidden');
         this.$element.removeClass('oe_form_dirty');
         return this.has_been_loaded.pipe(function() {
@@ -190,19 +265,25 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                 }).pipe(self.on_record_loaded);
             }
             result.pipe(function() {
+                if (options.editable) {
+                    self.set({mode: "edit"});
+                }
                 self.$element.css('visibility', 'visible');
             });
-            if (self.sidebar) {
-                self.sidebar.$element.show();
-            }
             return result;
         });
     },
     do_hide: function () {
-        this._super();
         if (this.sidebar) {
             this.sidebar.$element.hide();
         }
+        if (this.$buttons) {
+            this.$buttons.hide();
+        }
+        if (this.$pager) {
+            this.$pager.hide();
+        }
+        this._super();
     },
     on_record_loaded: function(record) {
         var self = this, set_values = [];
@@ -212,13 +293,17 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         }
         this.datarecord = record;
 
+        if (this.qweb) {
+            this.kill_current_form();
+            this.rendering_engine.set_fields_view(this.get_fvg_from_qweb(record));
+            var $dest = this.$element.hasClass("oe_form_container") ? this.$element : this.$element.find('.oe_form_container');
+            this.rendering_engine.render_to($dest);
+        }
+
         _(this.fields).each(function (field, f) {
-            field.reset();
+            field._dirty_flag = false;
             var result = field.set_value(self.datarecord[f] || false);
             set_values.push(result);
-            $.when(result).then(function() {
-                field.validate();
-            });
         });
         return $.when.apply(null, set_values).pipe(function() {
             if (!record.id) {
@@ -227,7 +312,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                 _.each(self.fields_order, function(field_name) {
                     if (record[field_name] !== undefined) {
                         var field = self.fields[field_name];
-                        field.dirty = true;
+                        field._dirty_flag = true;
                         self.do_onchange(field);
                     }
                 });
@@ -236,7 +321,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             self.is_initialized.resolve();
             self.do_update_pager(record.id == null);
             if (self.sidebar) {
-                self.sidebar.attachments.do_update();
+               self.sidebar.do_attachement_update(self.dataset, self.datarecord.id);
             }
             if (self.default_focus_field) {
                 self.default_focus_field.focus();
@@ -245,20 +330,15 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                 self.do_push_state({id:record.id});
             }
             self.$element.removeClass('oe_form_dirty');
+            self.$buttons.find('.oe_form_button_save').removeClass('oe_form_button_save_dirty');
         });
     },
     on_form_changed: function() {
-        for (var w in this.widgets) {
-            w = this.widgets[w];
-            w.process_modifiers();
-            if (w.field) {
-                w.validate();
-            }
-            w.update_dom();
-        }
+        this.trigger("view_content_has_changed");
     },
     do_notify_change: function() {
         this.$element.addClass('oe_form_dirty');
+        this.$buttons.find('.oe_form_button_save').addClass('oe_form_button_save_dirty');
     },
     on_pager_action: function(action) {
         if (this.can_be_discarded()) {
@@ -280,11 +360,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         }
     },
     do_update_pager: function(hide_index) {
-        var $pager = this.$form_header.find('div.oe_form_pager');
         var index = hide_index ? '-' : this.dataset.index + 1;
-        $pager.find('button').prop('disabled', this.dataset.ids.length < 2);
-        $pager.find('span.oe_pager_index').html(index);
-        $pager.find('span.oe_pager_count').html(this.dataset.ids.length);
+        this.$pager.find('button').prop('disabled', this.dataset.ids.length < 2).end()
+                   .find('span.oe_pager_index').html(index).end()
+                   .find('span.oe_pager_count').html(this.dataset.ids.length);
     },
     parse_on_change: function (on_change, widget) {
         var self = this;
@@ -305,7 +384,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             'None': function () {return null;},
             'context': function (i) {
                 context_index = i;
-                var ctx = new openerp.web.CompoundContext(self.dataset.get_context(), widget.build_context() ? widget.build_context() : {});
+                var ctx = new instance.web.CompoundContext(self.dataset.get_context(), widget.build_context() ? widget.build_context() : {});
                 return ctx;
             }
         };
@@ -323,8 +402,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             }
             // form field
             if (self.fields[field]) {
-                var value = self.fields[field].get_on_change_value();
-                return value == null ? false : value;
+                var value_ = self.fields[field].get_value();
+                return value_ == null ? false : value_;
             }
             // parent field
             var splitted = field.split('.');
@@ -387,17 +466,17 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                 }
 
                 if (widget.field['change_default']) {
-                    var fieldname = widget.name, value;
+                    var fieldname = widget.name, value_;
                     if (response.value && (fieldname in response.value)) {
                         // Use value from onchange if onchange executed
-                        value = response.value[fieldname];
+                        value_ = response.value[fieldname];
                     } else {
                         // otherwise get form value for field
-                        value = self.fields[fieldname].get_on_change_value();
+                        value_ = self.fields[fieldname].get_value();
                     }
-                    var condition = fieldname + '=' + value;
+                    var condition = fieldname + '=' + value_;
 
-                    if (value) {
+                    if (value_) {
                         can_process_onchange = self.rpc({
                             url: '/web/dataset/call',
                             async: false
@@ -425,23 +504,23 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                 return self.on_processed_onchange(response, processed);
             } catch(e) {
                 console.error(e);
+                instance.webclient.crashmanager.on_javascript_exception(e);
                 return $.Deferred().reject();
             }
         });
     },
-    on_processed_onchange: function(response, processed) {
+    on_processed_onchange: function(result, processed) {
         try {
-        var result = response;
         if (result.value) {
             for (var f in result.value) {
                 if (!result.value.hasOwnProperty(f)) { continue; }
                 var field = this.fields[f];
                 // If field is not defined in the view, just ignore it
                 if (field) {
-                    var value = result.value[f];
-                    if (field.get_value() != value) {
-                        field.set_value(value);
-                        field.dirty = true;
+                    var value_ = result.value[f];
+                    if (field.get_value() != value_) {
+                        field.set_value(value_);
+                        field._dirty_flag = true;
                         if (!_.contains(processed, field.name)) {
                             this.do_onchange(field, processed);
                         }
@@ -451,7 +530,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             this.on_form_changed();
         }
         if (!_.isEmpty(result.warning)) {
-            $(QWeb.render("CrashManagerWarning", result.warning)).dialog({
+               instance.web.dialog($(QWeb.render("CrashManager.warning", result.warning)), {
+                title:result.warning.title,
                 modal: true,
                 buttons: [
                     {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
@@ -460,6 +540,9 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         }
         if (result.domain) {
             function edit_domain(node) {
+                if (typeof node !== "object") {
+                    return;
+                }
                 var new_domain = result.domain[node.attrs.name];
                 if (new_domain) {
                     node.attrs.domain = new_domain;
@@ -471,22 +554,46 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         return $.Deferred().resolve();
         } catch(e) {
             console.error(e);
+            instance.webclient.crashmanager.on_javascript_exception(e);
             return $.Deferred().reject();
         }
     },
+    switch_mode: function() {
+        var self = this;
+        if(this.get("mode") == "view") {
+            self.$element.removeClass('oe_form_editable').addClass('oe_form_readonly');
+            self.$buttons.find('.oe_form_buttons_edit').hide();
+            self.$buttons.find('.oe_form_buttons_view').show();
+            self.$sidebar.show();
+            _.each(this.fields,function(field){
+                field.set({"force_readonly": true});
+            });
+        } else {
+            self.$element.removeClass('oe_form_readonly').addClass('oe_form_editable');
+            self.$buttons.find('.oe_form_buttons_edit').show();
+            self.$buttons.find('.oe_form_buttons_view').hide();
+            self.$sidebar.hide();
+            _.each(this.fields,function(field){
+                field.set({"force_readonly": false});
+            });
+        }
+    },
     on_button_save: function() {
         var self = this;
         return this.do_save().then(function(result) {
-            self.do_prev_view({'created': result.created, 'default': 'page'});
+            self.set({mode: "view"});
         });
     },
-    on_button_cancel: function() {
+    on_button_cancel: function(event) {
         if (this.can_be_discarded()) {
-            return this.do_prev_view({'default': 'page'});
+            this.set({mode: "view"});
+            this.on_record_loaded(this.datarecord);
         }
+        return false;
     },
     on_button_new: function() {
         var self = this;
+        this.set({mode: "edit"});
         var def = $.Deferred();
         $.when(this.has_been_loaded).then(function() {
             if (self.can_be_discarded()) {
@@ -504,6 +611,44 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         });
         return def.promise();
     },
+    on_button_edit: function() {
+        return this.set({mode: "edit"});
+    },
+    on_button_create: function() {
+        this.dataset.index = null;
+        this.do_show();
+    },
+    on_button_duplicate: function() {
+        var self = this;
+        var def = $.Deferred();
+        $.when(this.has_been_loaded).then(function() {
+            self.dataset.call('copy', [self.datarecord.id, {}, self.dataset.context]).then(function(new_id) {
+                return self.on_created({ result : new_id });
+            }).then(function() {
+                return self.set({mode: "edit"});
+            }).then(function() {
+                def.resolve();
+            });
+        });
+        return def.promise();
+    },
+    on_button_delete: function() {
+        var self = this;
+        var def = $.Deferred();
+        $.when(this.has_been_loaded).then(function() {
+            if (self.datarecord.id && confirm(_t("Do you really want to delete this record?"))) {
+                self.dataset.unlink([self.datarecord.id]).then(function() {
+                    self.on_pager_action('next');
+                    def.resolve();
+                });
+            } else {
+                $.async_when().then(function () {
+                    def.reject();
+                })
+            }
+        });
+        return def.promise();
+    },
     can_be_discarded: function() {
         return !this.$element.is('.oe_form_dirty') || confirm(_t("Warning, the record has been modified, your changes will be discarded."));
     },
@@ -512,7 +657,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
      * record or saving an existing one depending on whether the record
      * already has an id property.
      *
-     * @param {Function} success callback on save success
+     * @param {Function} [success] callback on save success
      * @param {Boolean} [prepend_on_create=false] if ``do_save`` creates a new record, should that record be inserted at the start of the dataset (by default, records are added at the end)
      */
     do_save: function(success, prepend_on_create) {
@@ -526,11 +671,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                 f = self.fields[f];
                 if (!f.is_valid()) {
                     form_invalid = true;
-                    f.update_dom(true);
                     if (!first_invalid_field) {
                         first_invalid_field = f;
                     }
-                } else if (f.name !== 'id' && !f.readonly && (!self.datarecord.id || f.is_dirty())) {
+                } else if (f.name !== 'id' && !f.get("readonly") && (!self.datarecord.id || f._dirty_flag)) {
                     // Special case 'id' field, do not save this field
                     // on 'create' : save all non readonly fields
                     // on 'edit' : save non readonly modified fields
@@ -538,10 +682,15 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                 }
             }
             if (form_invalid) {
+                self.set({'display_invalid_fields': true});
+                for (var f in self.fields) {
+                    self.fields[f]._check_css_flags();
+                }
                 first_invalid_field.focus();
                 self.on_invalid();
                 return $.Deferred().reject();
             } else {
+                self.set({'display_invalid_fields': false});
                 var save_deferral;
                 if (!self.datarecord.id) {
                     //console.log("FormView(", self, ") : About to create", values);
@@ -570,7 +719,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         var msg = "<ul>";
         _.each(this.fields, function(f) {
             if (!f.is_valid()) {
-                msg += "<li>" + f.string + "</li>";
+                msg += "<li>" + (f.node.attrs.string || f.field.string) + "</li>";
             }
         });
         msg += "</ul>";
@@ -609,12 +758,12 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                 this.dataset.alter_ids(this.dataset.ids.concat([this.datarecord.id]));
                 this.dataset.index = this.dataset.ids.length - 1;
             } else {
-               this.dataset.alter_ids([this.datarecord.id].concat(this.dataset.ids));
+                this.dataset.alter_ids([this.datarecord.id].concat(this.dataset.ids));
                 this.dataset.index = 0;
             }
             this.do_update_pager();
             if (this.sidebar) {
-                this.sidebar.attachments.do_update();
+                this.sidebar.do_attachement_update(this.dataset, this.datarecord.id);
             }
             //openerp.log("The record has been created with id #" + this.datarecord.id);
             return $.when(this.reload()).pipe(function () {
@@ -628,6 +777,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
     reload: function() {
         var self = this;
         return this.reload_mutex.exec(function() {
+            if (self.dataset.index == null) {
+                self.do_prev_view();
+                return $.Deferred().reject().promise();
+            }
             if (self.dataset.index == null || self.dataset.index < 0) {
                 return $.when(self.on_button_new());
             } else {
@@ -637,15 +790,20 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             }
         });
     },
+    get_widgets: function() {
+        return _.filter(this.getChildren(), function(obj) {
+            return obj instanceof instance.web.form.FormWidget;
+        });
+    },
     get_fields_values: function(blacklist) {
        blacklist = blacklist || [];
         var values = {};
         var ids = this.get_selected_ids();
         values["id"] = ids.length > 0 ? ids[0] : false;
-        _.each(this.fields, function(value, key) {
+        _.each(this.fields, function(value_, key) {
                if (_.include(blacklist, key))
                        return;
-            var val = value.get_value();
+            var val = value_.get_value();
             values[key] = val;
         });
         return values;
@@ -662,8 +820,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         });
     },
     is_dirty: function() {
-        return _.any(this.fields, function (value) {
-            return value.is_dirty();
+        return _.any(this.fields, function (value_) {
+            return value_._dirty_flag;
         });
     },
     is_interactible_record: function() {
@@ -672,7 +830,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             if (this.options.not_interactible_on_create)
                 return false;
         } else if (typeof(id) === "string") {
-            if(openerp.web.BufferedDataSet.virtual_id_regex.test(id))
+            if(instance.web.BufferedDataSet.virtual_id_regex.test(id))
                 return false;
         }
         return true;
@@ -688,29 +846,24 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                 // ignore fields which are empty, invisible, readonly, o2m
                 // or m2m
                 if (!value
-                        || field.invisible
-                        || field.readonly
+                        || field.get('invisible')
+                        || field.get("readonly")
                         || field.field.type === 'one2many'
                         || field.field.type === 'many2many') {
                     return false;
                 }
-                var displayed;
-                switch(field.field.type) {
+                var displayed = value;
+                switch (field.field.type) {
                 case 'selection':
                     displayed = _(field.values).find(function (option) {
                             return option[0] === value;
                         })[1];
                     break;
-                case 'many2one':
-                    displayed = field.value[1] || value;
-                    break;
-                default:
-                    displayed = value;
                 }
 
                 return {
                     name: name,
-                    string: field.string,
+                    string: field.node.attrs.string || field.field.string,
                     value: value,
                     displayed: displayed,
                     // convert undefined to false
@@ -724,7 +877,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             .filter(function (field) { return field.change_default; })
             .value();
 
-        var d = new openerp.web.Dialog(this, {
+        var d = new instance.web.Dialog(this, {
             title: _t("Set Default"),
             args: {
                 fields: fields,
@@ -736,12 +889,12 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                     var $defaults = d.$element.find('#formview_default_fields');
                     var field_to_set = $defaults.val();
                     if (!field_to_set) {
-                        $defaults.parent().addClass('invalid');
+                        $defaults.parent().addClass('oe_form_invalid');
                         return;
                     }
                     var condition = d.$element.find('#formview_default_conditions').val(),
                         all_users = d.$element.find('#formview_default_all').is(':checked');
-                    new openerp.web.DataSet(self, 'ir.values').call(
+                    new instance.web.DataSet(self, 'ir.values').call(
                         'set_default', [
                             self.dataset.model,
                             field_to_set,
@@ -755,9 +908,443 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         });
         d.template = 'FormView.set_default';
         d.open();
-    }
+    },
+    register_field: function(field, name) {
+        this.fields[name] = field;
+        this.fields_order.push(name);
+
+        field.on('focused', null, this.proxy('widgetFocused'))
+             .on('blurred', null, this.proxy('widgetBlurred'));
+        if (this.get_field(name).translate) {
+            this.translatable_fields.push(field);
+        }
+        field.on('changed_value', this, function() {
+            field._dirty_flag = true;
+            if (field.is_syntax_valid()) {
+                this.do_onchange(field);
+                this.on_form_changed(true);
+                this.do_notify_change();
+            }
+        });
+    },
+    get_field: function(field_name) {
+        return this.fields_view.fields[field_name];
+    },
+    is_create_mode: function() {
+        return !this.datarecord.id;
+    },
+});
+
+/**
+ * Interface to be implemented by rendering engines for the form view.
+ */
+instance.web.form.FormRenderingEngineInterface = instance.web.Class.extend({
+    set_fields_view: function(fields_view) {},
+    set_fields_registry: function(fields_registry) {},
+    render_to: function($element) {},
+});
+
+/**
+ * Default rendering engine for the form view.
+ * 
+ * It is necessary to set the view using set_view() before usage.
+ */
+instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInterface.extend({
+    init: function(view) {
+        this.view = view;
+    },
+    set_fields_view: function(fvg) {
+        this.fvg = fvg;
+        this.version = parseFloat(this.fvg.arch.attrs.version);
+        if (isNaN(this.version)) {
+            this.version = 6.1;
+        }
+    },
+    set_tags_registry: function(tags_registry) {
+        this.tags_registry = tags_registry;
+    },
+    set_fields_registry: function(fields_registry) {
+        this.fields_registry = fields_registry;
+    },
+    // Backward compatibility tools, current default version: v6.1
+    process_version: function() {
+        if (this.version < 7.0) {
+            this.$form.find('form:first').wrapInner('<group col="4"/>');
+            this.$form.find('page').each(function() {
+                if (!$(this).parents('field').length) {
+                    $(this).wrapInner('<group col="4"/>');
+                }
+            });
+        }
+        selector = 'form[version!="7.0"] page,form[version!="7.0"]';
+    },
+    render_to: function($target) {
+        var self = this;
+        this.$target = $target;
+
+        // TODO: I know this will save the world and all the kitten for a moment,
+        //       but one day, we will have to get rid of xml2json
+        var xml = instance.web.json_node_to_xml(this.fvg.arch);
+        this.$form = $('<div class="oe_form">' + xml + '</div>');
+
+        this.process_version();
+
+        this.fields_to_init = [];
+        this.tags_to_init = [];
+        this.labels = {};
+        this.process(this.$form);
+
+        this.$form.appendTo(this.$target);
+
+        _.each(this.fields_to_init, function($elem) {
+            var name = $elem.attr("name");
+            if (!self.fvg.fields[name]) {
+                throw new Error("Field '" + name + "' specified in view could not be found.");
+            }
+            var obj = self.fields_registry.get_any([$elem.attr('widget'), self.fvg.fields[name].type]);
+            if (!obj) {
+                throw new Error("Widget type '"+ $elem.attr('widget') + "' is not implemented");
+            }
+            var w = new (obj)(self.view, instance.web.xml_to_json($elem[0]));
+            var $label = self.labels[$elem.attr("name")];
+            if ($label) {
+                w.set_input_id($label.attr("for"));
+            }
+            self.alter_field(w);
+            self.view.register_field(w, $elem.attr("name"));
+            w.replace($elem);
+        });
+        _.each(this.tags_to_init, function($elem) {
+            var tag_name = $elem[0].tagName.toLowerCase();
+            var obj = self.tags_registry.get_object(tag_name);
+            var w = new (obj)(self.view, instance.web.xml_to_json($elem[0]));
+            w.replace($elem);
+        });
+        // TODO: return a deferred
+    },
+    render_element: function(template /* dictionaries */) {
+        var dicts = [].slice.call(arguments).slice(1);
+        var dict = _.extend.apply(_, dicts);
+        dict['classnames'] = dict['class'] || ''; // class is a reserved word and might caused problem to Safari when used from QWeb
+        return $(QWeb.render(template, dict));
+    },
+    alter_field: function(field) {
+    },
+    toggle_layout_debugging: function() {
+        if (!this.$target.has('.oe_layout_debug_cell:first').length) {
+            this.$target.find('[title]').removeAttr('title');
+            this.$target.find('.oe_form_group_cell').each(function() {
+                var text = 'W:' + ($(this).attr('width') || '') + ' - C:' + $(this).attr('colspan');
+                $(this).attr('title', text);
+            });
+        }
+        this.$target.toggleClass('oe_layout_debugging');
+    },
+    process: function($tag) {
+        var self = this;
+        var tagname = $tag[0].nodeName.toLowerCase();
+        if (this.tags_registry.contains(tagname)) {
+            this.tags_to_init.push($tag);
+            return $tag;
+        }
+        var fn = self['process_' + tagname];
+        if (fn) {
+            var args = [].slice.call(arguments);
+            args[0] = $tag;
+            return fn.apply(self, args);
+        } else {
+            // generic tag handling, just process children
+            $tag.children().each(function() {
+                self.process($(this));
+            });
+            self.handle_common_properties($tag, $tag);
+            $tag.removeAttr("modifiers");
+            return $tag;
+        }
+    },
+    process_sheet: function($sheet) {
+        var $new_sheet = this.render_element('FormRenderingSheet', $sheet.getAttributes());
+        this.handle_common_properties($new_sheet, $sheet);
+        var $dst = $new_sheet.find('.oe_form_sheet');
+        $sheet.contents().appendTo($dst);
+        $sheet.before($new_sheet).remove();
+        this.process($new_sheet);
+    },
+    process_form: function($form) {
+        if ($form.find('> sheet').length === 0) {
+            $form.addClass('oe_form_nosheet');
+        }
+        var $new_form = this.render_element('FormRenderingForm', $form.getAttributes());
+        this.handle_common_properties($new_form, $form);
+        $form.contents().appendTo($new_form);
+        if ($form[0] === this.$form[0]) {
+            // If root element, replace it
+            this.$form = $new_form;
+        } else {
+            $form.before($new_form).remove();
+        }
+        this.process($new_form);
+    },
+    /*
+     * Used by direct <field> children of a <group> tag only
+     * This method will add the implicit <label...> for every field
+     * in the <group>
+    */
+    preprocess_field: function($field) {
+        var self = this;
+        var name = $field.attr('name'),
+            field_colspan = parseInt($field.attr('colspan'), 10),
+            field_modifiers = JSON.parse($field.attr('modifiers') || '{}');
+
+        if ($field.attr('nolabel') === '1')
+            return;
+        $field.attr('nolabel', '1');
+        var found = false;
+        this.$form.find('label[for="' + name + '"]').each(function(i ,el) {
+            $(el).parents().each(function(unused, tag) {
+                var name = tag.tagName.toLowerCase();
+                if (name === "field" || name in self.tags_registry.map)
+                    found = true;
+            });
+        });
+        if (found)
+            return;
+
+        $label = $('<label/>').attr({
+            'for' : name,
+            "modifiers": JSON.stringify({invisible: field_modifiers.invisible}),
+            "string": $field.attr('string'),
+            "help": $field.attr('help'),
+            "class": $field.attr('class'),
+        });
+        $label.insertBefore($field);
+        if (field_colspan > 1) {
+            $field.attr('colspan', field_colspan - 1);
+        }
+        return $label;
+    },
+    process_field: function($field) {
+        if ($field.parent().is('group')) {
+            // No implicit labels for normal fields, only for <group> direct children
+            var $label = this.preprocess_field($field);
+            if ($label) {
+                this.process($label);
+            }
+        }
+        this.fields_to_init.push($field);
+        return $field;
+    },
+    process_group: function($group) {
+        var self = this;
+        $group.children('field').each(function() {
+            self.preprocess_field($(this));
+        });
+        var $new_group = this.render_element('FormRenderingGroup', $group.getAttributes());
+        var $table;
+        if ($new_group.first().is('table.oe_form_group')) {
+            $table = $new_group;
+        } else if ($new_group.filter('table.oe_form_group').length) {
+            $table = $new_group.filter('table.oe_form_group').first();
+        } else {
+            $table = $new_group.find('table.oe_form_group').first();
+        }
+
+        var $tr, $td,
+            cols = parseInt($group.attr('col') || 2, 10),
+            row_cols = cols;
+
+        var children = [];
+        $group.children().each(function(a,b,c) {
+            var $child = $(this);
+            var colspan = parseInt($child.attr('colspan') || 1, 10);
+            var tagName = $child[0].tagName.toLowerCase();
+            var $td = $('<td/>').addClass('oe_form_group_cell').attr('colspan', colspan);
+            var newline = tagName === 'newline';
+
+            // Note FME: those classes are used in layout debug mode
+            if ($tr && row_cols > 0 && (newline || row_cols < colspan)) {
+                $tr.addClass('oe_form_group_row_incomplete');
+                if (newline) {
+                    $tr.addClass('oe_form_group_row_newline');
+                }
+            }
+            if (newline) {
+                $tr = null;
+                return;
+            }
+            if (!$tr || row_cols < colspan) {
+                $tr = $('<tr/>').addClass('oe_form_group_row').appendTo($table);
+                row_cols = cols;
+            }
+            row_cols -= colspan;
+
+            // invisibility transfer
+            var field_modifiers = JSON.parse($child.attr('modifiers') || '{}');
+            var invisible = field_modifiers.invisible;
+            self.handle_common_properties($td, $("<dummy>").attr("modifiers", JSON.stringify({invisible: invisible})));
+
+            $tr.append($td.append($child));
+            children.push($child[0]);
+        });
+        if (row_cols && $td) {
+            $td.attr('colspan', parseInt($td.attr('colspan'), 10) + row_cols);
+        }
+        $group.before($new_group).remove();
+
+        $table.find('> tbody > tr').each(function() {
+            var to_compute = [],
+                row_cols = cols,
+                total = 100;
+            $(this).children().each(function() {
+                var $td = $(this),
+                    $child = $td.children(':first');
+                switch ($child[0].tagName.toLowerCase()) {
+                    case 'separator':
+                        if ($child.attr('orientation') === 'vertical') {
+                            $td.addClass('oe_vertical_separator').attr('width', '1');
+                            $td.empty();
+                            row_cols-= $td.attr('colspan') || 1;
+                            total--;
+                        }
+                        break;
+                    case 'label':
+                        if ($child.attr('for')) {
+                            $td.attr('width', '1%').addClass('oe_form_group_cell_label');
+                            row_cols-= $td.attr('colspan') || 1;
+                            total--;
+                        }
+                        break;
+                    default:
+                        var width = _.str.trim($child.attr('width') || ''),
+                            iwidth = parseInt(width, 10);
+                        if (iwidth) {
+                            if (width.substr(-1) === '%') {
+                                total -= iwidth;
+                                width = iwidth + '%';
+                            } else {
+                                // Absolute width
+                                $td.css('min-width', width + 'px');
+                            }
+                            $td.attr('width', width);
+                            $child.removeAttr('width');
+                            row_cols-= $td.attr('colspan') || 1;
+                        } else {
+                            to_compute.push($td);
+                        }
+
+                }
+            });
+            if (row_cols) {
+                var unit = Math.floor(total / row_cols);
+                if (!$(this).is('.oe_form_group_row_incomplete')) {
+                    _.each(to_compute, function($td, i) {
+                        var width = parseInt($td.attr('colspan'), 10) * unit;
+                        $td.attr('width', width + '%');
+                        total -= width;
+                    });
+                }
+            }
+        });
+        _.each(children, function(el) {
+            self.process($(el));
+        });
+        this.handle_common_properties($new_group, $group);
+        return $new_group;
+    },
+    process_notebook: function($notebook) {
+        var self = this;
+        var pages = [];
+        $notebook.find('> page').each(function() {
+            var $page = $(this);
+            var page_attrs = $page.getAttributes();
+            page_attrs.id = _.uniqueId('notebook_page_');
+            var $new_page = self.render_element('FormRenderingNotebookPage', page_attrs);
+            $page.contents().appendTo($new_page);
+            $page.before($new_page).remove();
+            var ic = self.handle_common_properties($new_page, $page).invisibility_changer;
+            page_attrs.__page = $new_page;
+            page_attrs.__ic = ic;
+            pages.push(page_attrs);
+            
+            $new_page.children().each(function() {
+                self.process($(this));
+            });
+        });
+        var $new_notebook = this.render_element('FormRenderingNotebook', { pages : pages });
+        $notebook.contents().appendTo($new_notebook);
+        $notebook.before($new_notebook).remove();
+        self.process($($new_notebook.children()[0]));
+        //tabs and invisibility handling
+        $new_notebook.tabs();
+        _.each(pages, function(page, i) {
+            if (! page.__ic)
+                return;
+            page.__ic.on("change:effective_invisible", null, function() {
+                var current = $new_notebook.tabs("option", "selected");
+                if (! pages[current].__ic || ! pages[current].__ic.get("effective_invisible"))
+                    return;
+                var first_visible = _.find(_.range(pages.length), function(i2) {
+                    return (! pages[i2].__ic) || (! pages[i2].__ic.get("effective_invisible"));
+                });
+                if (first_visible !== undefined) {
+                    $new_notebook.tabs('select', first_visible);
+                }
+            });
+        });
+                
+        this.handle_common_properties($new_notebook, $notebook);
+        return $new_notebook;
+    },
+    process_separator: function($separator) {
+        var $new_separator = this.render_element('FormRenderingSeparator', $separator.getAttributes());
+        $separator.before($new_separator).remove();
+        this.handle_common_properties($new_separator, $separator);
+        return $new_separator;
+    },
+    process_label: function($label) {
+        var name = $label.attr("for"),
+            field_orm = this.fvg.fields[name];
+        var dict = {
+            string: $label.attr('string') || (field_orm || {}).string || '',
+            help: $label.attr('help') || (field_orm || {}).help || '',
+            _for: name ? _.uniqueId('oe-field-input-') : undefined,
+        };
+        var align = parseFloat(dict.align);
+        if (isNaN(align) || align === 1) {
+            align = 'right';
+        } else if (align === 0) {
+            align = 'left';
+        } else {
+            align = 'center';
+        }
+        dict.align = align;
+        var $new_label = this.render_element('FormRenderingLabel', dict);
+        $label.before($new_label).remove();
+        this.handle_common_properties($new_label, $label);
+        if (name) {
+            this.labels[name] = $new_label;
+        }
+        return $new_label;
+    },
+    handle_common_properties: function($new_element, $node) {
+        var str_modifiers = $node.attr("modifiers") || "{}"
+        var modifiers = JSON.parse(str_modifiers);
+        var ic = null;
+        if (modifiers.invisible !== undefined)
+            ic = new instance.web.form.InvisibilityChanger(this.view, this.view, modifiers.invisible, $new_element);
+        $new_element.addClass($node.attr("class") || "");
+        $new_element.attr('style', $node.attr('style'));
+        return {invisibility_changer: ic,};
+    },
 });
-openerp.web.FormDialog = openerp.web.Dialog.extend({
+
+instance.web.form.FormRenderingEngineReadonly = instance.web.form.FormRenderingEngine.extend({
+    alter_field: function(field) {
+        field.set({"force_readonly": true});
+    },
+});
+
+instance.web.form.FormDialog = instance.web.Dialog.extend({
     init: function(parent, options, view_id, dataset) {
         this._super(parent, options);
         this.dataset = dataset;
@@ -766,8 +1353,7 @@ openerp.web.FormDialog = openerp.web.Dialog.extend({
     },
     start: function() {
         this._super();
-        this.form = new openerp.web.FormView(this, this.dataset, this.view_id, {
-            sidebar: false,
+        this.form = new instance.web.FormView(this, this.dataset, this.view_id, {
             pager: false
         });
         this.form.appendTo(this.$element);
@@ -788,63 +1374,7 @@ openerp.web.FormDialog = openerp.web.Dialog.extend({
     }
 });
 
-/** @namespace */
-openerp.web.form = {};
-
-openerp.web.form.SidebarAttachments = openerp.web.OldWidget.extend({
-    init: function(parent, form_view) {
-        var $section = parent.add_section(_t('Attachments'), 'attachments');
-        this.$div = $('<div class="oe-sidebar-attachments"></div>');
-        $section.append(this.$div);
-
-        this._super(parent, $section.attr('id'));
-        this.view = form_view;
-    },
-    do_update: function() {
-        if (!this.view.datarecord.id) {
-            this.on_attachments_loaded([]);
-        } else {
-            (new openerp.web.DataSetSearch(
-                this, 'ir.attachment', this.view.dataset.get_context(),
-                [
-                    ['res_model', '=', this.view.dataset.model],
-                    ['res_id', '=', this.view.datarecord.id],
-                    ['type', 'in', ['binary', 'url']]
-                ])).read_slice(['name', 'url', 'type'], {}).then(this.on_attachments_loaded);
-        }
-    },
-    on_attachments_loaded: function(attachments) {
-        this.attachments = attachments;
-        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);
-    },
-    on_attachment_changed: function(e) {
-        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();
-        }
-    },
-    on_attachment_delete: function(e) {
-        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_update()
-                self.do_notify("Delete an attachment", "The attachment '" + name + "' has been deleted");
-            });
-        }
-    }
-});
-
-openerp.web.form.compute_domain = function(expr, fields) {
+instance.web.form.compute_domain = function(expr, fields) {
     var stack = [];
     for (var i = expr.length - 1; i >= 0; i--) {
         var ex = expr[i];
@@ -873,7 +1403,7 @@ openerp.web.form.compute_domain = function(expr, fields) {
                 _t("Unknown field %s in domain %s"),
                 ex[0], JSON.stringify(expr)));
         }
-        var field_value = field.get_value ? fields[ex[0]].get_value() : fields[ex[0]].value;
+        var field_value = field.get_value ? field.get_value() : field.value;
         var op = ex[1];
         var val = ex[2];
 
@@ -915,82 +1445,98 @@ openerp.web.form.compute_domain = function(expr, fields) {
     return _.all(stack, _.identity);
 };
 
-openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.form.Widget# */{
-    template: 'Widget',
+/**
+ * Must be applied over an class already possessing the PropertiesMixin.
+ *
+ * Apply the result of the "invisible" domain to this.$element.
+ */
+instance.web.form.InvisibilityChangerMixin = {
+    init: function(field_manager, invisible_domain) {
+        this._ic_field_manager = field_manager
+        this._ic_invisible_modifier = invisible_domain;
+        this._ic_field_manager.on("view_content_has_changed", this, function() {
+            var result = this._ic_invisible_modifier === undefined ? false :
+                instance.web.form.compute_domain(this._ic_invisible_modifier, this._ic_field_manager.fields);
+            this.set({"invisible": result});
+        });
+        this.set({invisible: this._ic_invisible_modifier === true, force_invisible: false});
+        var check = function() {
+            if (this.get("invisible") || this.get('force_invisible')) {
+                this.set({"effective_invisible": true});
+            } else {
+                this.set({"effective_invisible": false});
+            }
+        };
+        this.on('change:invisible', this, check);
+        this.on('change:force_invisible', this, check);
+        _.bind(check, this)();
+    },
+    start: function() {
+        this.on("change:effective_invisible", this, this._check_visibility);
+        this._check_visibility();
+    },
+    _check_visibility: function() {
+        this.$element.toggleClass('oe_form_invisible', this.get("effective_invisible"));
+    },
+};
+
+instance.web.form.InvisibilityChanger = instance.web.Class.extend(instance.web.PropertiesMixin, instance.web.form.InvisibilityChangerMixin, {
+    init: function(parent, field_manager, invisible_domain, $element) {
+        this.setParent(parent);
+        instance.web.PropertiesMixin.init.call(this);
+        instance.web.form.InvisibilityChangerMixin.init.call(this, field_manager, invisible_domain);
+        this.$element = $element;
+        this.start();
+    },
+});
+
+instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.InvisibilityChangerMixin, {
     /**
-     * @constructs openerp.web.form.Widget
-     * @extends openerp.web.OldWidget
+     * @constructs instance.web.form.FormWidget
+     * @extends instance.web.Widget
      *
      * @param view
      * @param node
      */
     init: function(view, node) {
+        this._super(view);
         this.view = view;
         this.node = node;
         this.modifiers = JSON.parse(this.node.attrs.modifiers || '{}');
-        this.always_invisible = (this.modifiers.invisible && this.modifiers.invisible === true);
-        this.type = this.type || node.tag;
-        this.element_name = this.element_name || this.type;
-        this.element_class = [
-            'formview', this.view.view_id, this.element_name,
-            this.view.widgets_counter++].join("_");
+        instance.web.form.InvisibilityChangerMixin.init.call(this, view, this.modifiers.invisible);
 
-        this._super(view);
-
-        this.view.widgets[this.element_class] = this;
-        this.children = node.children;
-        this.colspan = parseInt(node.attrs.colspan || 1, 10);
-        this.decrease_max_width = 0;
-
-        this.string = this.string || node.attrs.string;
-        this.help = this.help || node.attrs.help;
-        this.invisible = this.modifiers['invisible'] === true;
-        this.classname = 'oe_form_' + this.type;
-
-        this.align = parseFloat(this.node.attrs.align);
-        if (isNaN(this.align) || this.align === 1) {
-            this.align = 'right';
-        } else if (this.align === 0) {
-            this.align = 'left';
-        } else {
-            this.align = 'center';
-        }
-
-
-        this.width = this.node.attrs.width;
+        this.view.on("view_content_has_changed", this, this.process_modifiers);
     },
-    /**
-     * Sets up blur/focus forwarding from DOM elements to a widget (`this`)
+    renderElement: function() {
+        this._super();
+        this.$element.addClass(this.node.attrs["class"] || "");
+    },
+    destroy: function() {
+        $.fn.tipsy.clear();
+        this._super.apply(this, arguments);
+    },
+    /**
+     * Sets up blur/focus forwarding from DOM elements to a widget (`this`)
      *
      * @param {jQuery} $e jQuery object of elements to bind focus/blur on
      */
     setupFocus: function ($e) {
         var self = this;
-        $e.bind({
-            focus: function () { $(self).trigger('widget-focus'); },
-            blur: function () { $(self).trigger('widget-blur'); }
+        $e.on({
+            focus: function () { self.trigger('focused'); },
+            blur: function () { self.trigger('blurred'); }
         });
     },
-    start: function() {
-        this.$element = this.view.$element.find(
-            '.' + this.element_class.replace(/[^\r\n\f0-9A-Za-z_-]/g, "\\$&"));
-    },
-    stop: function() {
-        this._super.apply(this, arguments);
-        $.fn.tipsy.clear();
-    },
     process_modifiers: function() {
-        var compute_domain = openerp.web.form.compute_domain;
+        var compute_domain = instance.web.form.compute_domain;
+        var to_set = {};
         for (var a in this.modifiers) {
-            this[a] = compute_domain(this.modifiers[a], this.view.fields);
+            if (!_.include(["invisible"], a)) {
+                var val = compute_domain(this.modifiers[a], this.view.fields);
+                to_set[a] = val;
+            }
         }
-    },
-    update_dom: function() {
-        this.$element.toggle(!this.invisible);
-    },
-    render: function() {
-        var template = this.template;
-        return QWeb.render(template, { "widget": this });
+        this.set(to_set);
     },
     do_attach_tooltip: function(widget, trigger, options) {
         widget = widget || this;
@@ -1005,7 +1551,7 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
                         template = 'WidgetLabel.tooltip';
                     }
                     return QWeb.render(template, {
-                        debug: openerp.connection.debug,
+                        debug: instance.connection.debug,
                         widget: widget
                 })},
                 gravity: $.fn.tipsy.autoBounds(50, 'nw'),
@@ -1013,7 +1559,7 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
                 opacity: 0.85,
                 trigger: 'hover'
             }, options || {});
-        trigger.tipsy(options);
+        $(trigger).tipsy(options);
     },
     _build_view_fields_values: function(blacklist) {
         var a_dataset = this.view.dataset;
@@ -1032,7 +1578,7 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
     },
     _build_eval_context: function(blacklist) {
         var a_dataset = this.view.dataset;
-        return new openerp.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values(blacklist));
+        return new instance.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values(blacklist));
     },
     /**
      * Builds a new context usable for operations related to fields by merging
@@ -1047,7 +1593,7 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
         
         if (v_context.__ref || true) { //TODO: remove true
             var fields_values = this._build_eval_context(blacklist);
-            v_context = new openerp.web.CompoundContext(v_context).set_eval_context(fields_values);
+            v_context = new instance.web.CompoundContext(v_context).set_eval_context(fields_values);
         }
         return v_context;
     },
@@ -1058,208 +1604,29 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
         var final_domain = n_domain !== null ? n_domain : f_domain;
         if (!(final_domain instanceof Array) || true) { //TODO: remove true
             var fields_values = this._build_eval_context();
-            final_domain = new openerp.web.CompoundDomain(final_domain).set_eval_context(fields_values);
+            final_domain = new instance.web.CompoundDomain(final_domain).set_eval_context(fields_values);
         }
         return final_domain;
     }
 });
 
-openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({
-    template: 'WidgetFrame',
-    init: function(view, node) {
-        this._super(view, node);
-        this.columns = parseInt(node.attrs.col || 4, 10);
-        this.x = 0;
-        this.y = 0;
-        this.table = [];
-        this.add_row();
-        for (var i = 0; i < node.children.length; i++) {
-            var n = node.children[i];
-            if (n.tag == "newline") {
-                this.add_row();
-            } else {
-                this.handle_node(n);
-            }
-        }
-        this.set_row_cells_with(this.table[this.table.length - 1]);
-    },
-    add_row: function(){
-        if (this.table.length) {
-            this.set_row_cells_with(this.table[this.table.length - 1]);
-        }
-        var row = [];
-        this.table.push(row);
-        this.x = 0;
-        this.y += 1;
-        return row;
-    },
-    set_row_cells_with: function(row) {
-        var bypass = 0,
-            max_width = 100,
-            row_length = row.length;
-        for (var i = 0; i < row.length; i++) {
-            if (row[i].always_invisible) {
-                row_length--;
-            } else {
-                bypass += row[i].width === undefined ? 0 : 1;
-                max_width -= row[i].decrease_max_width;
-            }
-        }
-        var size_unit = Math.round(max_width / (this.columns - bypass)),
-            colspan_sum = 0;
-        for (var i = 0; i < row.length; i++) {
-            var w = row[i];
-            if (w.always_invisible) {
-                continue;
-            }
-            colspan_sum += w.colspan;
-            if (w.width === undefined) {
-                var width = (i === row_length - 1 && colspan_sum === this.columns) ? max_width : Math.round(size_unit * w.colspan);
-                max_width -= width;
-                w.width = width + '%';
-            }
-        }
-    },
-    handle_node: function(node) {
-        var type = {};
-        if (node.tag == 'field') {
-            type = this.view.fields_view.fields[node.attrs.name] || {};
-            if (node.attrs.widget == 'statusbar' && node.attrs.nolabel !== '1') {
-                // This way we can retain backward compatibility between addons and old clients
-                node.attrs.colspan = (parseInt(node.attrs.colspan, 10) || 1) + 1;
-                node.attrs.nolabel = '1';
-            }
-        }
-        var widget = new (this.view.registry.get_any(
-                [node.attrs.widget, type.type, node.tag])) (this.view, node);
-        if (node.tag == 'field') {
-            if (!this.view.default_focus_field || node.attrs.default_focus == '1') {
-                this.view.default_focus_field = widget;
-            }
-            if (node.attrs.nolabel != '1') {
-                var label = new (this.view.registry.get_object('label')) (this.view, node);
-                label["for"] = widget;
-                this.add_widget(label, widget.colspan + 1);
-            }
-        }
-        this.add_widget(widget);
-    },
-    add_widget: function(widget, colspan) {
-        var current_row = this.table[this.table.length - 1];
-        if (!widget.always_invisible) {
-            colspan = colspan || widget.colspan;
-            if (current_row.length && (this.x + colspan) > this.columns) {
-                current_row = this.add_row();
-            }
-            this.x += widget.colspan;
-        }
-        current_row.push(widget);
-        return widget;
-    }
-});
-
-openerp.web.form.WidgetGroup = openerp.web.form.WidgetFrame.extend({
-    template: 'WidgetGroup'
-}),
-
-openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({
-    template: 'WidgetNotebook',
-    init: function(view, node) {
-        this._super(view, node);
-        this.pages = [];
-        for (var i = 0; i < node.children.length; i++) {
-            var n = node.children[i];
-            if (n.tag == "page") {
-                var page = new (this.view.registry.get_object('notebookpage'))(
-                        this.view, n, this, this.pages.length);
-                this.pages.push(page);
-            }
-        }
-    },
-    start: function() {
-        var self = this;
-        this._super.apply(this, arguments);
-        this.$element.find('> ul > li').each(function (index, tab_li) {
-            var page = self.pages[index],
-                id = _.uniqueId(self.element_name + '-');
-            page.element_id = id;
-            $(tab_li).find('a').attr('href', '#' + id);
-        });
-        this.$element.find('> div').each(function (index, page) {
-            page.id = self.pages[index].element_id;
-        });
-        this.$element.tabs();
-        this.view.on_button_new.add_first(this.do_select_first_visible_tab);
-        if (openerp.connection.debug) {
-            this.do_attach_tooltip(this, this.$element.find('ul:first'), {
-                gravity: 's'
-            });
-        }
-    },
-    do_select_first_visible_tab: function() {
-        for (var i = 0; i < this.pages.length; i++) {
-            var page = this.pages[i];
-            if (page.invisible === false) {
-                this.$element.tabs('select', page.index);
-                break;
-            }
-        }
-    }
-});
-
-openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({
-    template: 'WidgetNotebookPage',
-    init: function(view, node, notebook, index) {
-        this.notebook = notebook;
-        this.index = index;
-        this.element_name = 'page_' + index;
-        this._super(view, node);
-    },
-    start: function() {
-        this._super.apply(this, arguments);
-        this.$element_tab = this.notebook.$element.find(
-                '> ul > li:eq(' + this.index + ')');
-    },
-    update_dom: function() {
-        if (this.invisible && this.index === this.notebook.$element.tabs('option', 'selected')) {
-            this.notebook.do_select_first_visible_tab();
-        }
-        this.$element_tab.toggle(!this.invisible);
-        this.$element.toggle(!this.invisible);
-    }
-});
-
-openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({
-    template: 'WidgetSeparator',
-    init: function(view, node) {
-        this._super(view, node);
-        this.orientation = node.attrs.orientation || 'horizontal';
-        if (this.orientation === 'vertical') {
-            this.width = '1';
-        }
-        this.classname += '_' + this.orientation;
-    }
-});
-
-openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
+instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
     template: 'WidgetButton',
     init: function(view, node) {
         this._super(view, node);
         this.force_disabled = false;
-        if (this.string) {
-            // We don't have button key bindings in the webclient
-            this.string = this.string.replace(/_/g, '');
-        }
-        if (node.attrs.default_focus == '1') {
+        this.string = (this.node.attrs.string || '').replace(/_/g, '');
+        if (this.node.attrs.default_focus == '1') {
             // TODO fme: provide enter key binding to widgets
             this.view.default_focus_button = this;
         }
+        this.view.on('view_content_has_changed', this, this.check_disable);
     },
     start: function() {
         this._super.apply(this, arguments);
         var $button = this.$element.find('button');
         $button.click(this.on_click);
-        if (this.help || openerp.connection.debug) {
+        if (this.node.attrs.help || instance.connection.debug) {
             this.do_attach_tooltip();
         }
         this.setupFocus($button);
@@ -1278,7 +1645,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
         var exec_action = function() {
             if (self.node.attrs.confirm) {
                 var def = $.Deferred();
-                var dialog = $('<div>' + self.node.attrs.confirm + '</div>').dialog({
+                var dialog = instance.web.dialog($('<div/>').text(self.node.attrs.confirm), {
                     title: _t('Confirm'),
                     modal: true,
                     buttons: [
@@ -1313,7 +1680,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
 
         var context = this.node.attrs.context;
         if (context && context.__ref) {
-            context = new openerp.web.CompoundContext(context);
+            context = new instance.web.CompoundContext(context);
             context.set_eval_context(this._build_eval_context());
         }
 
@@ -1323,285 +1690,389 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
                 self.view.reload();
             });
     },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.check_disable();
-    },
     check_disable: function() {
-        var disabled = (this.readonly || this.force_disabled || !this.view.is_interactible_record());
-        this.$element.find('button').prop('disabled', disabled);
-        this.$element.find("button").css('color', disabled ? 'grey' : '');
+        var disabled = (this.force_disabled || !this.view.is_interactible_record());
+        this.$element.prop('disabled', disabled);
+        this.$element.css('color', disabled ? 'grey' : '');
     }
 });
 
-openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
-    template: 'WidgetLabel',
-    init: function(view, node) {
-        this.element_name = 'label_' + node.attrs.name;
-
-        this._super(view, node);
-
-        if (this.node.tag == 'label' && !this.string && this.node.children.length) {
-            this.string = this.node.children[0];
-            this.align = 'left';
-        }
-
-        if (this.node.tag == 'label' && (this.align === 'left' || this.node.attrs.colspan || (this.string && this.string.length > 32))) {
-            this.template = "WidgetParagraph";
-            this.colspan = parseInt(this.node.attrs.colspan || 1, 10);
-            // Widgets default to right-aligned, but paragraph defaults to
-            // left-aligned
-            if (isNaN(parseFloat(this.node.attrs.align))) {
-                this.align = 'left';
-            }
-
-            this.multilines = this.string && _.str.lines(this.string).length > 1;
-        } else {
-            this.colspan = 1;
-            this.width = '1%';
-            this.decrease_max_width = 1;
-            this.nowrap = true;
-        }
-    },
-    render: function () {
-        if (this['for'] && this.type !== 'label') {
-            return QWeb.render(this.template, {widget: this['for']});
-        }
-        // Actual label widgets should not have a false and have type label
-        return QWeb.render(this.template, {widget: this});
-    },
-    start: function() {
-        this._super();
-        var self = this;
-        if (this['for'] && (this['for'].help || openerp.connection.debug)) {
-            this.do_attach_tooltip(self['for']);
-        }
-        var $label = this.$element.find('label');
-        $label.dblclick(function() {
-            var widget = self['for'] || self;
-            openerp.log(widget.element_class , widget);
-            window.w = widget;
-        });
-        this.setupFocus($label);
-    }
-});
+/**
+ * Interface to be implemented by fields.
+ * 
+ * Properties:
+ *     - readonly: boolean. If set to true the field should appear in readonly mode.
+ *     - force_readonly: boolean, When it is true, the field should always appear
+ *      in read only mode, no matter what the value of the "readonly" property can be.
+ * Events:
+ *     - changed_value: triggered to inform the view to check on_changes
+ * 
+ */
+instance.web.form.FieldInterface = {
+    /**
+     * Constructor takes 2 arguments:
+     * - field_manager: Implements FieldManagerMixin
+     * - node: the "<field>" node in json form
+     */
+    init: function(field_manager, node) {},
+    /**
+     * Called by the form view to indicate the value of the field.
+     * 
+     * set_value() may return an object that can be passed to $.when() that represents the moment when
+     * the field has finished all operations necessary before the user can effectively use the widget.
+     * 
+     * Multiple calls to set_value() can occur at any time and must be handled correctly by the implementation,
+     * regardless of any asynchronous operation currently running and the status of any promise that a
+     * previous call to set_value() could have returned.
+     * 
+     * set_value() must be able, at any moment, to handle the syntax returned by the "read" method of the
+     * osv class in the OpenERP server as well as the syntax used by the set_value() (see below). It must
+     * also be able to handle any other format commonly used in the _defaults key on the models in the addons
+     * as well as any format commonly returned in a on_change. It must be able to autodetect those formats as
+     * no information is ever given to know which format is used.
+     */
+    set_value: function(value_) {},
+    /**
+     * Get the current value of the widget.
+     * 
+     * Must always return a syntaxically correct value to be passed to the "write" method of the osv class in
+     * the OpenERP server, although it is not assumed to respect the constraints applied to the field.
+     * For example if the field is marqued as "required", a call to get_value() can return false.
+     * 
+     * get_value() can also be called *before* a call to set_value() and, in that case, is supposed to
+     * return a defaut value according to the type of field.
+     * 
+     * This method is always assumed to perform synchronously, it can not return a promise.
+     * 
+     * If there was no user interaction to modify the value of the field, it is always assumed that
+     * get_value() return the same semantic value than the one passed in the last call to set_value(),
+     * altough the syntax can be different. This can be the case for type of fields that have a different
+     * syntax for "read" and "write" (example: m2o: set_value([0, "Administrator"]), get_value() => 0).
+     */
+    get_value: function() {},
+    /**
+     * Inform the current object of the id it should use to match a html <label> that exists somewhere in the
+     * view.
+     */
+    set_input_id: function(id) {},
+    /**
+     * Returns true if is_syntax_valid() returns true and the value is semantically
+     * valid too according to the semantic restrictions applied to the field.
+     */
+    is_valid: function() {},
+    /**
+     * Returns true if the field holds a value which is syntaxically correct, ignoring
+     * the potential semantic restrictions applied to the field.
+     */
+    is_syntax_valid: function() {},
+    /**
+     * Must set the focus on the field.
+     */
+    focus: function() {},
+};
 
-openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.form.Field# */{
+/**
+ * Abstract class for classes implementing FieldInterface.
+ * 
+ * Properties:
+ *     - effective_readonly: when it is true, the widget is displayed as readonly. Vary depending
+ *      the values of the "readonly" property and the "force_readonly" property on the field manager.
+ *     - value: useful property to hold the value of the field. By default, set_value() and get_value()
+ *     set and retrieve the value property. Changing the value property also triggers automatically
+ *     a 'changed_value' event that inform the view to trigger on_changes.
+ * 
+ */
+instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.web.form.FieldInterface, {
     /**
-     * @constructs openerp.web.form.Field
-     * @extends openerp.web.form.Widget
+     * @constructs instance.web.form.AbstractField
+     * @extends instance.web.form.FormWidget
      *
-     * @param view
+     * @param field_manager
      * @param node
      */
-    init: function(view, node) {
-        this.name = node.attrs.name;
-        this.value = undefined;
-        view.fields[this.name] = this;
-        view.fields_order.push(this.name);
-        this.type = node.attrs.widget || view.fields_view.fields[node.attrs.name].type;
-        this.element_name = "field_" + this.name + "_" + this.type;
-
-        this._super(view, node);
-
-        if (node.attrs.nolabel != '1' && this.colspan > 1) {
-            this.colspan--;
-        }
-        this.field = view.fields_view.fields[node.attrs.name] || {};
-        this.string = node.attrs.string || this.field.string;
-        this.help = node.attrs.help || this.field.help;
-        this.nolabel = (this.field.nolabel || node.attrs.nolabel) === '1';
-        this.readonly = this.modifiers['readonly'] === true;
-        this.required = this.modifiers['required'] === true;
-        this.invalid = this.dirty = false;
-
-        this.classname = 'oe_form_field_' + this.type;
+    init: function(field_manager, node) {
+        this._super(field_manager, node);
+        this.field_manager = field_manager;
+        this.name = this.node.attrs.name;
+        this.set({'value': false});
+        this.field = this.field_manager.get_field(this.name);
+        this.set({required: this.modifiers['required'] === true});
+        
+        // some events to make the property "effective_readonly" sync automatically with "readonly" and
+        // "force_readonly"
+        this.set({"readonly": this.modifiers['readonly'] === true});
+        var test_effective_readonly = function() {
+            this.set({"effective_readonly": this.get("readonly") || !!this.get("force_readonly")});
+        };
+        this.on("change:readonly", this, test_effective_readonly);
+        this.on("change:force_readonly", this, test_effective_readonly);
+        _.bind(test_effective_readonly, this)();
+        
+        this.on("change:value", this, function() {
+            if (! this._inhibit_on_change)
+                this.trigger('changed_value');
+            this._check_css_flags();
+        });
     },
-    start: function() {
-        this._super.apply(this, arguments);
+    renderElement: function() {
+        var self = this;
+        this._super();
         if (this.field.translate) {
-            this.view.translatable_fields.push(this);
             this.$element.addClass('oe_form_field_translatable');
-            this.$element.find('.oe_field_translate').click(this.on_translate);
+            this.$element.find('.oe_field_translate').click(_.bind(function() {
+                this.field_manager.open_translate_dialog(this);
+            }, this));
         }
-        if (this.nolabel && openerp.connection.debug) {
-            this.do_attach_tooltip(this, this.$element);
+        this.$label = this.view.$element.find('label[for=' + this.id_for_label + ']');
+        if (instance.connection.debug) {
+            this.do_attach_tooltip(this, this.$label[0] || this.$element);
+            this.$label.off('dblclick').on('dblclick', function() {
+                console.log("Field '%s' of type '%s' in View: %o", self.name, (self.node.attrs.widget || self.field.type), self.view);
+                window.w = self;
+                console.log("window.w =", window.w);
+            });
         }
+        if (!this.disable_utility_classes) {
+            this.off("change:required", this, this._set_required);
+            this.on("change:required", this, this._set_required);
+            this._set_required();
+        }
+        this._check_visibility();
+        this._check_css_flags();
     },
-    set_value: function(value) {
-        this.value = value;
-        this.invalid = false;
-        this.update_dom();
-        this.on_value_changed();
-    },
-    set_value_from_ui: function() {
-        this.on_value_changed();
-    },
-    on_value_changed: function() {
+    /**
+     * Private. Do not use.
+     */
+    _set_required: function() {
+        this.$element.toggleClass('oe_form_required', this.get("required"));
     },
-    on_translate: function() {
-        this.view.open_translate_dialog(this);
+    set_value: function(value_) {
+        this._inhibit_on_change = true;
+        this.set({'value': value_});
+        this._inhibit_on_change = false;
     },
     get_value: function() {
-        return this.value;
+        return this.get('value');
     },
     is_valid: function() {
-        return !this.invalid;
+        return this.is_syntax_valid() && !(this.get('required') && this.is_false());
     },
-    is_dirty: function() {
-        return this.dirty && !this.readonly;
+    is_syntax_valid: function() {
+        return true;
     },
-    get_on_change_value: function() {
-        return this.get_value();
+    /**
+     * Method useful to implement to ease validity testing. Must return true if the current
+     * value is similar to false in OpenERP.
+     */
+    is_false: function() {
+        return this.get('value') === false;
     },
-    update_dom: function(show_invalid) {
-        this._super.apply(this, arguments);
+    _check_css_flags: function(show_invalid) {
         if (this.field.translate) {
-            this.$element.find('.oe_field_translate').toggle(!!this.view.datarecord.id);
+            this.$element.find('.oe_field_translate').toggle(!this.field_manager.is_create_mode());
         }
         if (!this.disable_utility_classes) {
-            this.$element.toggleClass('disabled', this.readonly);
-            this.$element.toggleClass('required', this.required);
-            if (show_invalid) {
-                this.$element.toggleClass('invalid', !this.is_valid());
+            if (this.field_manager.get('display_invalid_fields')) {
+                this.$element.toggleClass('oe_form_invalid', !this.is_valid());
             }
         }
     },
-    on_ui_change: function() {
-        this.dirty = true;
-        this.validate();
-        if (this.is_valid()) {
-            this.set_value_from_ui();
-            this.view.do_onchange(this);
-            this.view.on_form_changed(true);
-            this.view.do_notify_change();
-        } else {
-            this.update_dom(true);
-        }
-    },
-    validate: function() {
-        this.invalid = false;
+    focus: function() {
     },
-    focus: function($element) {
-        if ($element) {
-            setTimeout(function() {
-                $element.focus();
-            }, 50);
-        }
-    },
-    reset: function() {
-        this.dirty = false;
+    /**
+     * Utility method to focus an element, but only after a small amount of time.
+     */
+    delay_focus: function($elem) {
+        setTimeout(function() {
+            $elem.focus();
+        }, 50);
     },
+    /**
+     * Utility method to get the widget options defined in the field xml description.
+     */
     get_definition_options: function() {
         if (!this.definition_options) {
             var str = this.node.attrs.options || '{}';
             this.definition_options = JSON.parse(str);
         }
         return this.definition_options;
-    }
+    },
+    set_input_id: function(id) {
+        this.id_for_label = id;
+    },
 });
 
-openerp.web.form.FieldChar = openerp.web.form.Field.extend({
+/**
+ * A mixin to apply on any field that has to completely re-render when its readonly state
+ * switch.
+ */
+instance.web.form.ReinitializeFieldMixin =  {
+    /**
+     * Default implementation of start(), use it or call explicitly initialize_field().
+     */
+    start: function() {
+        this._super();
+        this.initialize_field();
+    },
+    initialize_field: function() {
+        this.on("change:effective_readonly", this, function() {
+            this.destroy_content();
+            this.renderElement();
+            this.initialize_content();
+            this.render_value();
+        });
+        this.initialize_content();
+        this.render_value();
+    },
+    /**
+     * Called to destroy anything that could have been created previously, called before a
+     * re-initialization.
+     */
+    destroy_content: function() {},
+    /**
+     * Called to initialize the content.
+     */
+    initialize_content: function() {},
+    /**
+     * Called to render the value. Should also be explicitly called at the end of a set_value().
+     */
+    render_value: function() {},
+};
+
+instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
     template: 'FieldChar',
-    init: function (view, node) {
-        this._super(view, node);
+    widget_class: 'oe_form_field_char',
+    init: function (field_manager, node) {
+        this._super(field_manager, node);
         this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1';
     },
-    start: function() {
-        this._super.apply(this, arguments);
+    initialize_content: function() {
+        var self = this;
         var $input = this.$element.find('input');
-        $input.change(this.on_ui_change);
+        $input.change(function() {
+            self.set({'value': instance.web.parse_value($input.val(), self)});
+        });
         this.setupFocus($input);
     },
-    set_value: function(value) {
-        this._super.apply(this, arguments);
-        var show_value = openerp.web.format_value(value, this, '');
-        this.$element.find('input').val(show_value);
-        return show_value;
-    },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.$element.find('input').prop('readonly', this.readonly);
+    set_value: function(value_) {
+        this._super(value_);
+        this.render_value();
     },
-    set_value_from_ui: function() {
-        this.value = openerp.web.parse_value(this.$element.find('input').val(), this);
-        this._super();
+    render_value: function() {
+        var show_value = instance.web.format_value(this.get('value'), this, '');
+        if (!this.get("effective_readonly")) {
+            this.$element.find('input').val(show_value);
+        } else {
+            if (this.password) {
+                show_value = new Array(show_value.length + 1).join('*');
+            }
+            this.$element.text(show_value);
+        }
     },
-    validate: function() {
-        this.invalid = false;
-        try {
-            var value = openerp.web.parse_value(this.$element.find('input').val(), this, '');
-            this.invalid = this.required && value === '';
-        } catch(e) {
-            this.invalid = true;
+    is_syntax_valid: function() {
+        if (!this.get("effective_readonly")) {
+            try {
+                var value_ = instance.web.parse_value(this.$element.find('input').val(), this, '');
+                return true;
+            } catch(e) {
+                return false;
+            }
         }
+        return true;
     },
-    focus: function($element) {
-        this._super($element || this.$element.find('input:first'));
+    is_false: function() {
+        return this.get('value') === '' || this._super();
+    },
+    focus: function() {
+        this.delay_focus(this.$element.find('input:first'));
     }
 });
 
-openerp.web.form.FieldID = openerp.web.form.FieldChar.extend({
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.$element.find('input').prop('readonly', true);
-    }
+instance.web.form.FieldID = instance.web.form.FieldChar.extend({
+    
 });
 
-openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({
+instance.web.form.FieldEmail = instance.web.form.FieldChar.extend({
     template: 'FieldEmail',
-    start: function() {
-        this._super.apply(this, arguments);
+    initialize_content: function() {
+        this._super();
         var $button = this.$element.find('button');
         $button.click(this.on_button_clicked);
         this.setupFocus($button);
     },
+    render_value: function() {
+        if (!this.get("effective_readonly")) {
+            this._super();
+        } else {
+            this.$element.find('a')
+                    .attr('href', 'mailto:' + this.get('value'))
+                    .text(this.get('value'));
+        }
+    },
     on_button_clicked: function() {
-        if (!this.value || !this.is_valid()) {
+        if (!this.get('value') || !this.is_syntax_valid()) {
             this.do_warn("E-mail error", "Can't send email to invalid e-mail address");
         } else {
-            location.href = 'mailto:' + this.value;
+            location.href = 'mailto:' + this.get('value');
         }
     }
 });
 
-openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({
+instance.web.form.FieldUrl = instance.web.form.FieldChar.extend({
     template: 'FieldUrl',
-    start: function() {
-        this._super.apply(this, arguments);
+    initialize_content: function() {
+        this._super();
         var $button = this.$element.find('button');
         $button.click(this.on_button_clicked);
         this.setupFocus($button);
     },
+    render_value: function() {
+        if (!this.get("effective_readonly")) {
+            this._super();
+        } else {
+            var tmp = this.get('value');
+            var s = /(\w+):(.+)/.exec(tmp);
+            if (!s) {
+                tmp = "http://" + this.get('value');
+            }
+            this.$element.find('a').attr('href', tmp).text(tmp);
+        }
+    },
     on_button_clicked: function() {
-        if (!this.value) {
+        if (!this.get('value')) {
             this.do_warn("Resource error", "This resource is empty");
         } else {
-            window.open(this.value);
+            var url = $.trim(this.get('value'));
+            if(/^www\./i.test(url))
+                url = 'http://'+url;
+            window.open(url);
         }
     }
 });
 
-openerp.web.form.FieldFloat = openerp.web.form.FieldChar.extend({
-    init: function (view, node) {
-        this._super(view, node);
-        if (node.attrs.digits) {
-            this.digits = py.eval(node.attrs.digits).toJSON();
+instance.web.form.FieldFloat = instance.web.form.FieldChar.extend({
+    is_field_number: true,
+    widget_class: 'oe_form_field_float',
+    init: function (field_manager, node) {
+        this._super(field_manager, node);
+        this.set({'value': 0});
+        if (this.node.attrs.digits) {
+            this.digits = this.node.attrs.digits;
         } else {
-            this.digits = view.fields_view.fields[node.attrs.name].digits;
+            this.digits = this.field.digits;
         }
     },
-    set_value: function(value) {
-        if (value === false || value === undefined) {
+    set_value: function(value_) {
+        if (value_ === false || value_ === undefined) {
             // As in GTK client, floats default to 0
-            value = 0;
+            value_ = 0;
         }
-        this._super.apply(this, [value]);
+        this._super.apply(this, [value_]);
     }
 });
 
-openerp.web.DateTimeWidget = openerp.web.OldWidget.extend({
-    template: "web.datetimepicker",
+instance.web.DateTimeWidget = instance.web.OldWidget.extend({
+    template: "web.datepicker",
     jqueryui_object: 'datetimepicker',
     type_of_date: "datetime",
     init: function(parent) {
@@ -1614,6 +2085,7 @@ openerp.web.DateTimeWidget = openerp.web.OldWidget.extend({
         this.$input_picker = this.$element.find('input.oe_datepicker_container');
         this.$input.change(this.on_change);
         this.picker({
+            onClose: this.on_picker_select,
             onSelect: this.on_picker_select,
             changeMonth: true,
             changeYear: true,
@@ -1621,51 +2093,51 @@ openerp.web.DateTimeWidget = openerp.web.OldWidget.extend({
             showButtonPanel: true
         });
         this.$element.find('img.oe_datepicker_trigger').click(function() {
-            if (self.readonly || self.picker('widget').is(':visible')) {
+            if (self.get("effective_readonly") || self.picker('widget').is(':visible')) {
                 self.$input.focus();
                 return;
             }
-            self.picker('setDate', self.value ? openerp.web.auto_str_to_date(self.value) : new Date());
+            self.picker('setDate', self.value ? instance.web.auto_str_to_date(self.value) : new Date());
             self.$input_picker.show();
             self.picker('show');
             self.$input_picker.hide();
         });
         this.set_readonly(false);
-        this.value = false;
+        this.set({'value': false});
     },
     picker: function() {
         return $.fn[this.jqueryui_object].apply(this.$input_picker, arguments);
     },
-    on_picker_select: function(text, instance) {
+    on_picker_select: function(text, instance_) {
         var date = this.picker('getDate');
         this.$input
             .val(date ? this.format_client(date) : '')
             .change()
             .focus();
     },
-    set_value: function(value) {
-        this.value = value;
-        this.$input.val(value ? this.format_client(value) : '');
+    set_value: function(value_) {
+        this.set({'value': value_});
+        this.$input.val(value_ ? this.format_client(value_) : '');
     },
     get_value: function() {
-        return this.value;
+        return this.get('value');
     },
-    set_value_from_ui: function() {
-        var value = this.$input.val() || false;
-        this.value = this.parse_client(value);
+    set_value_from_ui_: function() {
+        var value_ = this.$input.val() || false;
+        this.set({'value': this.parse_client(value_)});
     },
     set_readonly: function(readonly) {
         this.readonly = readonly;
         this.$input.prop('readonly', this.readonly);
         this.$element.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly);
     },
-    is_valid: function(required) {
-        var value = this.$input.val();
-        if (value === "") {
-            return !required;
+    is_valid_: function() {
+        var value_ = this.$input.val();
+        if (value_ === "") {
+            return true;
         } else {
             try {
-                this.parse_client(value);
+                this.parse_client(value_);
                 return true;
             } catch(e) {
                 return false;
@@ -1673,102 +2145,121 @@ openerp.web.DateTimeWidget = openerp.web.OldWidget.extend({
         }
     },
     parse_client: function(v) {
-        return openerp.web.parse_value(v, {"widget": this.type_of_date});
+        return instance.web.parse_value(v, {"widget": this.type_of_date});
     },
     format_client: function(v) {
-        return openerp.web.format_value(v, {"widget": this.type_of_date});
+        return instance.web.format_value(v, {"widget": this.type_of_date});
     },
     on_change: function() {
-        if (this.is_valid()) {
-            this.set_value_from_ui();
+        if (this.is_valid_()) {
+            this.set_value_from_ui_();
         }
     }
 });
 
-openerp.web.DateWidget = openerp.web.DateTimeWidget.extend({
+instance.web.DateWidget = instance.web.DateTimeWidget.extend({
     jqueryui_object: 'datepicker',
     type_of_date: "date"
 });
 
-openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
-    template: "EmptyComponent",
+instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
+    template: "FieldDatetime",
     build_widget: function() {
-        return new openerp.web.DateTimeWidget(this);
+        return new instance.web.DateTimeWidget(this);
     },
-    start: function() {
-        var self = this;
-        this._super.apply(this, arguments);
-        this.datewidget = this.build_widget();
-        this.datewidget.on_change.add_last(this.on_ui_change);
-        this.datewidget.appendTo(this.$element);
-        this.setupFocus(this.datewidget.$input);
+    destroy_content: function() {
+        if (this.datewidget) {
+            this.datewidget.destroy();
+            this.datewidget = undefined;
+        }
     },
-    set_value: function(value) {
-        this._super(value);
-        this.datewidget.set_value(value);
+    initialize_content: function() {
+        if (!this.get("effective_readonly")) {
+            this.datewidget = this.build_widget();
+            this.datewidget.on_change.add_last(_.bind(function() {
+                this.set({'value': this.datewidget.get_value()});
+            }, this));
+            this.datewidget.appendTo(this.$element);
+            this.setupFocus(this.datewidget.$input);
+        }
     },
-    get_value: function() {
-        return this.datewidget.get_value();
+    set_value: function(value_) {
+        this._super(value_);
+        this.render_value();
     },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.datewidget.set_readonly(this.readonly);
+    render_value: function() {
+        if (!this.get("effective_readonly")) {
+            this.datewidget.set_value(this.get('value'));
+        } else {
+            this.$element.text(instance.web.format_value(this.get('value'), this, ''));
+        }
+    },
+    is_syntax_valid: function() {
+        if (!this.get("effective_readonly")) {
+            return this.datewidget.is_valid_();
+        }
+        return true;
     },
-    validate: function() {
-        this.invalid = !this.datewidget.is_valid(this.required);
+    is_false: function() {
+        return this.get('value') === '' || this._super();
     },
-    focus: function($element) {
-        this._super($element || this.datewidget.$input);
+    focus: function() {
+        if (this.datewidget && this.datewidget.$input)
+            this.delay_focus(this.datewidget.$input);
     }
 });
 
-openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({
+instance.web.form.FieldDate = instance.web.form.FieldDatetime.extend({
+    template: "FieldDate",
     build_widget: function() {
-        return new openerp.web.DateWidget(this);
+        return new instance.web.DateWidget(this);
     }
 });
 
-openerp.web.form.FieldText = openerp.web.form.Field.extend({
+instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
     template: 'FieldText',
-    start: function() {
-        this._super.apply(this, arguments);
-        var $textarea = this.$element.find('textarea');
-        $textarea.change(this.on_ui_change);
-        this.resized = false;
-        this.setupFocus($textarea);
-    },
-    set_value: function(value) {
-        this._super.apply(this, arguments);
-        var show_value = openerp.web.format_value(value, this, '');
-        this.$element.find('textarea').val(show_value);
-        if (!this.resized && this.view.options.resize_textareas) {
-            this.do_resize(this.view.options.resize_textareas);
-            this.resized = true;
+    initialize_content: function() {
+        this.$textarea = this.$element.find('textarea');
+        if (!this.get("effective_readonly")) {
+            this.$textarea.change(_.bind(function() {
+                this.set({'value': instance.web.parse_value(this.$textarea.val(), this)});
+            }, this));
+        } else {
+            this.$textarea.attr('disabled', 'disabled');
         }
+        this.setupFocus(this.$textarea);
     },
-    update_dom: function() {
+    set_value: function(value_) {
         this._super.apply(this, arguments);
-        this.$element.find('textarea').prop('readonly', this.readonly);
+        this.render_value();
     },
-    set_value_from_ui: function() {
-        this.value = openerp.web.parse_value(this.$element.find('textarea').val(), this);
-        this._super();
+    render_value: function() {
+        var show_value = instance.web.format_value(this.get('value'), this, '');
+        this.$textarea.val(show_value);
+        if (show_value && this.view.options.resize_textareas) {
+            this.do_resize(this.view.options.resize_textareas);
+        }
     },
-    validate: function() {
-        this.invalid = false;
-        try {
-            var value = openerp.web.parse_value(this.$element.find('textarea').val(), this, '');
-            this.invalid = this.required && value === '';
-        } catch(e) {
-            this.invalid = true;
+    is_syntax_valid: function() {
+        if (!this.get("effective_readonly")) {
+            try {
+                var value_ = instance.web.parse_value(this.$textarea.val(), this, '');
+                return true;
+            } catch(e) {
+                return false;
+            }
         }
+        return true;
+    },
+    is_false: function() {
+        return this.get('value') === '' || this._super();
     },
     focus: function($element) {
-        this._super($element || this.$element.find('textarea:first'));
+        this.delay_focus(this.$textarea);
     },
     do_resize: function(max_height) {
         max_height = parseInt(max_height, 10);
-        var $input = this.$element.find('textarea'),
+        var $input = this.$textarea,
             $div = $('<div style="position: absolute; z-index: 1000; top: 0"/>').width($input.width()),
             new_height;
         $div.text($input.val());
@@ -1786,66 +2277,61 @@ openerp.web.form.FieldText = openerp.web.form.Field.extend({
         $div.remove();
         $input.height(new_height);
     },
-    reset: function() {
-        this.resized = false;
-    }
 });
 
-openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({
+instance.web.form.FieldBoolean = instance.web.form.AbstractField.extend({
     template: 'FieldBoolean',
     start: function() {
-        var self = this;
-        this._super.apply(this, arguments);
-        var $input = this.$element.find('input');
-        $input.click(self.on_ui_change);
-        this.setupFocus($input);
-    },
-    set_value: function(value) {
         this._super.apply(this, arguments);
-        this.$element.find('input')[0].checked = value;
-    },
-    set_value_from_ui: function() {
-        this.value = this.$element.find('input').is(':checked');
-        this._super();
+        this.$checkbox = $("input", this.$element);
+        this.setupFocus(this.$checkbox);
+        this.$element.click(_.bind(function() {
+            this.set({'value': this.$checkbox.is(':checked')});
+        }, this));
+        var check_readonly = function() {
+            this.$checkbox.prop('disabled', this.get("effective_readonly"));
+        };
+        this.on("change:effective_readonly", this, check_readonly);
+        _.bind(check_readonly, this)();
     },
-    update_dom: function() {
+    set_value: function(value_) {
         this._super.apply(this, arguments);
-        this.$element.find('input').prop('disabled', this.readonly);
+        this.$checkbox[0].checked = value_;
     },
-    focus: function($element) {
-        this._super($element || this.$element.find('input:first'));
+    focus: function() {
+        this.delay_focus(this.$checkbox);
     }
 });
 
-openerp.web.form.FieldProgressBar = openerp.web.form.Field.extend({
+instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({
     template: 'FieldProgressBar',
     start: function() {
         this._super.apply(this, arguments);
-        this.$element.find('div').progressbar({
-            value: this.value,
-            disabled: this.readonly
+        this.$element.progressbar({
+            value: this.get('value'),
+            disabled: this.get("effective_readonly")
         });
     },
-    set_value: function(value) {
+    set_value: function(value_) {
         this._super.apply(this, arguments);
-        var show_value = Number(value);
+        var show_value = Number(value_);
         if (isNaN(show_value)) {
             show_value = 0;
         }
-        var formatted_value = openerp.web.format_value(show_value, { type : 'float' }, '0');
-        this.$element.find('div').progressbar('option', 'value', show_value).find('span').html(formatted_value + '%');
+        var formatted_value = instance.web.format_value(show_value, { type : 'float' }, '0');
+        this.$element.progressbar('option', 'value', show_value).find('span').html(formatted_value + '%');
     }
 });
 
-openerp.web.form.FieldTextXml = openerp.web.form.Field.extend({
+instance.web.form.FieldTextXml = instance.web.form.AbstractField.extend({
 // to replace view editor
 });
 
-openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
+instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
     template: 'FieldSelection',
-    init: function(view, node) {
+    init: function(field_manager, node) {
         var self = this;
-        this._super(view, node);
+        this._super(field_manager, node);
         this.values = _.clone(this.field.selection);
         _.each(this.values, function(v, i) {
             if (v[0] === false && v[1] === '') {
@@ -1854,7 +2340,7 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
         });
         this.values.unshift([false, '']);
     },
-    start: function() {
+    initialize_content: function() {
         // Flag indicating whether we're in an event chain containing a change
         // event on the select, in order to know what to do on keyup[RETURN]:
         // * If the user presses [RETURN] as part of changing the value of a
@@ -1867,10 +2353,10 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
         //   changing the selected value), takes the action as validating the
         //   row
         var ischanging = false;
-        this._super.apply(this, arguments);
-        var $select = this.$element.find('select');
-        $select
-            .change(this.on_ui_change)
+        var $select = this.$element.find('select')
+            .change(_.bind(function() {
+                this.set({'value': this.values[this.$element.find('select')[0].selectedIndex][0]});
+            }, this))
             .change(function () { ischanging = true; })
             .click(function () { ischanging = false; })
             .keyup(function (e) {
@@ -1880,30 +2366,35 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
             });
         this.setupFocus($select);
     },
-    set_value: function(value) {
-        value = value === null ? false : value;
-        value = value instanceof Array ? value[0] : value;
-        this._super(value);
-        var index = 0;
-        for (var i = 0, ii = this.values.length; i < ii; i++) {
-            if (this.values[i][0] === value) index = i;
+    set_value: function(value_) {
+        value_ = value_ === null ? false : value_;
+        value_ = value_ instanceof Array ? value_[0] : value_;
+        this._super(value_);
+        this.render_value();
+    },
+    render_value: function() {
+        if (!this.get("effective_readonly")) {
+            var index = 0;
+            for (var i = 0, ii = this.values.length; i < ii; i++) {
+                if (this.values[i][0] === this.get('value')) index = i;
+            }
+            this.$element.find('select')[0].selectedIndex = index;
+        } else {
+            var self = this;
+            var option = _(this.values)
+                .detect(function (record) { return record[0] === self.get('value'); }); 
+            this.$element.text(option ? option[1] : this.values[0][1]);
         }
-        this.$element.find('select')[0].selectedIndex = index;
-    },
-    set_value_from_ui: function() {
-        this.value = this.values[this.$element.find('select')[0].selectedIndex][0];
-        this._super();
-    },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.$element.find('select').prop('disabled', this.readonly);
     },
-    validate: function() {
-        var value = this.values[this.$element.find('select')[0].selectedIndex];
-        this.invalid = !(value && !(this.required && value[0] === false));
+    is_syntax_valid: function() {
+        if (this.get("effective_readonly")) {
+            return true;
+        }
+        var value_ = this.values[this.$element.find('select')[0].selectedIndex];
+        return !! value_;
     },
-    focus: function($element) {
-        this._super($element || this.$element.find('select:first'));
+    focus: function() {
+        this.delay_focus(this.$element.find('select:first'));
     }
 });
 
@@ -1914,8 +2405,8 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
 
     function filter( array, term ) {
         var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
-        return $.grep( array, function(value) {
-            return matcher.test( $( "<div>" ).html( value.label || value.value || value ).text() );
+        return $.grep( array, function(value_) {
+            return matcher.test( $( "<div>" ).html( value_.label || value_.value || value_ ).text() );
         });
     }
 
@@ -1939,131 +2430,229 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
     });
 })();
 
-openerp.web.form.dialog = function(content, options) {
-    options = _.extend({
-        width: '90%',
-        height: 'auto',
-        min_width: '800px'
-    }, options || {});
-    var dialog = new openerp.web.Dialog(null, options, content).open();
-    return dialog.$element;
+/**
+ * A mixin containing some useful methods to handle completion inputs.
+ */
+instance.web.form.CompletionFieldMixin = {
+    init: function() {
+        this.limit = 7;
+        this.orderer = new instance.web.DropMisordered();
+    },
+    /**
+     * Call this method to search using a string.
+     */
+    get_search_result: function(search_val) {
+        var self = this;
+
+        var dataset = new instance.web.DataSet(this, this.field.relation, self.build_context());
+        var blacklist = this.get_search_blacklist();
+
+        return this.orderer.add(dataset.name_search(
+                search_val, new instance.web.CompoundDomain(self.build_domain(), [["id", "not in", blacklist]]),
+                'ilike', this.limit + 1)).pipe(function(data) {
+            self.last_search = data;
+            // possible selections for the m2o
+            var values = _.map(data, function(x) {
+                return {
+                    label: _.str.escapeHTML(x[1]),
+                    value:x[1],
+                    name:x[1],
+                    id:x[0]
+                };
+            });
+
+            // search more... if more results that max
+            if (values.length > self.limit) {
+                values = values.slice(0, self.limit);
+                values.push({label: _t("<em>   Search More...</em>"), action: function() {
+                    dataset.name_search(search_val, self.build_domain(), 'ilike'
+                    , false, function(data) {
+                        self._search_create_popup("search", data);
+                    });
+                }});
+            }
+            // quick create
+            var raw_result = _(data.result).map(function(x) {return x[1];});
+            if (search_val.length > 0 && !_.include(raw_result, search_val)) {
+                values.push({label: _.str.sprintf(_t('<em>   Create "<strong>%s</strong>"</em>'),
+                        $('<span />').text(search_val).html()), action: function() {
+                    self._quick_create(search_val);
+                }});
+            }
+            // create...
+            values.push({label: _t("<em>   Create and Edit...</em>"), action: function() {
+                self._search_create_popup("form", undefined, {});
+            }});
+
+            return values;
+        });
+    },
+    get_search_blacklist: function() {
+        return [];
+    },
+    _quick_create: function(name) {
+        var self = this;
+        var slow_create = function () {
+            self._search_create_popup("form", undefined, {"default_name": name});
+        };
+        if (self.get_definition_options().quick_create === undefined || self.get_definition_options().quick_create) {
+            new instance.web.DataSet(this, this.field.relation, self.build_context())
+                .name_create(name, function(data) {
+                    self.add_id(data[0]);
+                }).fail(function(error, event) {
+                    event.preventDefault();
+                    slow_create();
+                });
+        } else
+            slow_create();
+    },
+    // all search/create popup handling
+    _search_create_popup: function(view, ids, context) {
+        var self = this;
+        var pop = new instance.web.form.SelectCreatePopup(this);
+        pop.select_element(
+            self.field.relation,
+            {
+                title: (view === 'search' ? _t("Search: ") : _t("Create: ")) + (this.string || this.name),
+                initial_ids: ids ? _.map(ids, function(x) {return x[0]}) : undefined,
+                initial_view: view,
+                disable_multiple_selection: true
+            },
+            self.build_domain(),
+            new instance.web.CompoundContext(self.build_context(), context || {})
+        );
+        pop.on_select_elements.add(function(element_ids) {
+            self.add_id(element_ids[0]);
+            self.focus();
+        });
+    },
+    /**
+     * To implement.
+     */
+    add_id: function(id) {},
 };
 
-openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
-    template: 'FieldMany2One',
-    init: function(view, node) {
-        this._super(view, node);
-        this.limit = 7;
-        this.value = null;
-        this.cm_id = _.uniqueId('m2o_cm_');
+instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, instance.web.form.ReinitializeFieldMixin, {
+    template: "FieldMany2One",
+    init: function(field_manager, node) {
+        this._super(field_manager, node);
+        instance.web.form.CompletionFieldMixin.init.call(this);
+        this.set({'value': false});
+        this.display_value = {};
         this.last_search = [];
-        this.tmp_value = undefined;
+        this.floating = false;
+        this.inhibit_on_change = false;
     },
     start: function() {
         this._super();
+        instance.web.form.ReinitializeFieldMixin.start.call(this);
+        this.on("change:value", this, function() {
+            this.floating = false;
+            this.render_value();
+        });
+    },
+    initialize_content: function() {
+        if (!this.get("effective_readonly"))
+            this.render_editable();
+        this.render_value();
+    },
+    render_editable: function() {
         var self = this;
         this.$input = this.$element.find("input");
+        
+        self.$input.tipsy({
+            title: function() {
+                return "No element was selected, you should create or select one from the dropdown list.";
+            },
+            trigger:'manual',
+            fade: true,
+        });
+        
         this.$drop_down = this.$element.find(".oe-m2o-drop-down-button");
-        this.$menu_btn = this.$element.find(".oe-m2o-cm-button");
-
-        // context menu
-        var init_context_menu_def = $.Deferred().then(function(e) {
-            var rdataset = new openerp.web.DataSetStatic(self, "ir.values", self.build_context());
-            rdataset.call("get", ['action', 'client_action_relate',
-                [[self.field.relation, false]], false, rdataset.get_context()], false, 0)
-                .then(function(result) {
-                self.related_entries = result;
-
-                var $cmenu = $("#" + self.cm_id);
-                $cmenu.append(QWeb.render("FieldMany2One.context_menu", {widget: self}));
-                var bindings = {};
-                bindings[self.cm_id + "_search"] = function() {
-                    if (self.readonly)
-                        return;
-                    self._search_create_popup("search");
-                };
-                bindings[self.cm_id + "_create"] = function() {
-                    if (self.readonly)
-                        return;
-                    self._search_create_popup("form");
-                };
-                bindings[self.cm_id + "_open"] = function() {
-                    if (!self.value) {
-                        self.focus();
-                        return;
-                    }
-                    var pop = new openerp.web.form.FormOpenPopup(self.view);
-                    pop.show_element(
-                        self.field.relation,
-                        self.value[0],
-                        self.build_context(),
-                        {
-                            title: _t("Open: ") + (self.string || self.name)
-                        }
-                    );
-                    pop.on_write_completed.add_last(function() {
-                        self.set_value(self.value[0]);
-                        self.focus();
-                    });
-                };
-                _.each(_.range(self.related_entries.length), function(i) {
-                    bindings[self.cm_id + "_related_" + i] = function() {
-                        self.open_related(self.related_entries[i]);
-                    };
-                });
-                var cmenu = self.$menu_btn.contextMenu(self.cm_id, {'noRightClick': true,
-                    bindings: bindings, itemStyle: {"color": ""},
-                    onContextMenu: function() {
-                        if(self.value) {
-                            $("#" + self.cm_id + " .oe_m2o_menu_item_mandatory").removeClass("oe-m2o-disabled-cm");
-                        } else {
-                            $("#" + self.cm_id + " .oe_m2o_menu_item_mandatory").addClass("oe-m2o-disabled-cm");
-                        }
-                        if (!self.readonly) {
-                            $("#" + self.cm_id + " .oe_m2o_menu_item_noreadonly").removeClass("oe-m2o-disabled-cm");
-                        } else {
-                            $("#" + self.cm_id + " .oe_m2o_menu_item_noreadonly").addClass("oe-m2o-disabled-cm");
-                        }
-                        return true;
-                    }, menuStyle: {width: "200px"}
-                });
-                $.async_when().then(function() {self.$menu_btn.trigger(e);});
+        this.$follow_button = $(".oe-m2o-cm-button", this.$element);
+        
+        this.$follow_button.click(function() {
+            if (!self.get('value')) {
+                self.focus();
+                return;
+            }
+            var pop = new instance.web.form.FormOpenPopup(self.view);
+            pop.show_element(
+                self.field.relation,
+                self.get("value"),
+                self.build_context(),
+                {
+                    title: _t("Open: ") + (self.string || self.name)
+                }
+            );
+            pop.on_write_completed.add_last(function() {
+                self.display_value = {};
+                self.render_value();
+                self.focus();
             });
         });
-        var ctx_callback = function(e) {init_context_menu_def.resolve(e); e.preventDefault()};
-        this.$menu_btn.click(ctx_callback);
 
         // some behavior for input
         this.$input.keyup(function() {
             if (self.$input.val() === "") {
-                self._change_int_value(null);
-            } else if (self.value === null || (self.value && self.$input.val() !== self.value[1])) {
-                self._change_int_value(undefined);
+                self.set({value: false});
+            } else {
+                self.floating = true;
             }
         });
         this.$drop_down.click(function() {
-            if (self.readonly)
-                return;
             if (self.$input.autocomplete("widget").is(":visible")) {
                 self.$input.autocomplete("close");
                 self.$input.focus();
             } else {
-                if (self.value) {
+                if (self.get("value") && ! self.floating) {
                     self.$input.autocomplete("search", "");
                 } else {
                     self.$input.autocomplete("search");
                 }
             }
         });
+        var tip_def = $.Deferred();
+        var untip_def = $.Deferred();
+        var tip_delay = 200;
+        var tip_duration = 3000;
         var anyoneLoosesFocus = function() {
-            if (!self.$input.is(":focus") &&
-                    !self.$input.autocomplete("widget").is(":visible") &&
-                    !self.value) {
-                if (self.value === undefined && self.last_search.length > 0) {
-                    self._change_int_ext_value(self.last_search[0]);
+            var used = false;
+            if (self.floating) {
+                if (self.last_search.length > 0) {
+                    if (self.last_search[0][0] != self.get("value")) {
+                        self.display_value = {};
+                        self.display_value["" + self.last_search[0][0]] = self.last_search[0][1];
+                        self.set({value: self.last_search[0][0]});
+                    } else {
+                        used = true;
+                        self.render_value();
+                    }
                 } else {
-                    self._change_int_ext_value(null);
+                    used = true;
+                    self.set({value: false});
+                    self.render_value();
                 }
+                self.floating = false;
+            }
+            if (used) {
+                tip_def.reject();
+                untip_def.reject();
+                tip_def = $.Deferred();
+                tip_def.then(function() {
+                    self.$input.tipsy("show");
+                });
+                setTimeout(function() {
+                    tip_def.resolve();
+                    untip_def.reject();
+                    untip_def = $.Deferred();
+                    untip_def.then(function() {
+                        self.$input.tipsy("hide");
+                    });
+                    setTimeout(function() {untip_def.resolve();}, tip_duration);
+                }, tip_delay);
+            } else {
+                tip_def.reject();
             }
         };
         this.$input.focusout(anyoneLoosesFocus);
@@ -2071,14 +2660,20 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
         var isSelecting = false;
         // autocomplete
         this.$input.autocomplete({
-            source: function(req, resp) { self.get_search_result(req, resp); },
+            source: function(req, resp) {
+                self.get_search_result(req.term).then(function(result) {
+                    resp(result);
+                });
+            },
             select: function(event, ui) {
                 isSelecting = true;
                 var item = ui.item;
                 if (item.id) {
-                    self._change_int_value([item.id, item.name]);
+                    self.display_value = {};
+                    self.display_value["" + item.id] = item.name;
+                    self.set({value: item.id});
                 } else if (item.action) {
-                    self._change_int_value(undefined);
+                    self.floating = true;
                     item.action();
                     return false;
                 }
@@ -2087,11 +2682,12 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
                 e.preventDefault();
             },
             html: true,
-            close: anyoneLoosesFocus,
+            // disabled to solve a bug, but may cause others
+            //close: anyoneLoosesFocus,
             minLength: 0,
             delay: 0
         });
-
+        this.$input.autocomplete("widget").addClass("openerp");
         // used to correct a bug when selecting an element by pushing 'enter' in an editable list
         this.$input.keyup(function(e) {
             if (e.which === 13) {
@@ -2100,195 +2696,70 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
             }
             isSelecting = false;
         });
-
-        this.setupFocus(this.$input.add(this.$menu_btn));
+        this.setupFocus(this.$input.add(this.$follow_button));
     },
-    // autocomplete component content handling
-    get_search_result: function(request, response) {
-        var search_val = request.term;
-        var self = this;
 
-        if (this.abort_last) {
-            this.abort_last();
-            delete this.abort_last;
-        }
-        var dataset = new openerp.web.DataSetStatic(this, this.field.relation, self.build_context());
-
-        dataset.name_search(search_val, self.build_domain(), 'ilike',
-                this.limit + 1, function(data) {
-            self.last_search = data;
-            // possible selections for the m2o
-            var values = _.map(data, function(x) {
-                return {
-                    label: _.str.escapeHTML(x[1]),
-                    value:x[1],
-                    name:x[1],
-                    id:x[0]
-                };
-            });
-
-            // search more... if more results that max
-            if (values.length > self.limit) {
-                var open_search_popup = function(data) {
-                    self._change_int_value(null);
-                    self._search_create_popup("search", data);
-                };
-                values = values.slice(0, self.limit);
-                values.push({label: _t("<em>   Search More...</em>"), action: function() {
-                    if (!search_val) {
-                        // search optimisation - in case user didn't enter any text we
-                        // do not need to prefilter records; for big datasets (ex: more
-                        // that 10.000 records) calling name_search() could be very very
-                        // expensive!
-                        open_search_popup();
-                        return;
-                    }
-                    dataset.name_search(search_val, self.build_domain(),
-                                        'ilike', false, open_search_popup);
-                }});
-            }
-            // quick create
-            var raw_result = _(data.result).map(function(x) {return x[1];});
-            if (search_val.length > 0 &&
-                !_.include(raw_result, search_val) &&
-                (!self.value || search_val !== self.value[1])) {
-                values.push({label: _.str.sprintf(_t('<em>   Create "<strong>%s</strong>"</em>'),
-                        $('<span />').text(search_val).html()), action: function() {
-                    self._quick_create(search_val);
-                }});
-            }
-            // create...
-            values.push({label: _t("<em>   Create and Edit...</em>"), action: function() {
-                self._change_int_value(null);
-                self._search_create_popup("form", undefined, {"default_name": search_val});
-            }});
-
-            response(values);
-        });
-        this.abort_last = dataset.abort_last;
-    },
-    _quick_create: function(name) {
-        var self = this;
-        var slow_create = function () {
-            self._change_int_value(null);
-            self._search_create_popup("form", undefined, {"default_name": name});
-        };
-        if (self.get_definition_options().quick_create === undefined || self.get_definition_options().quick_create) {
-            var dataset = new openerp.web.DataSetStatic(this, this.field.relation, self.build_context());
-            dataset.name_create(name, function(data) {
-                self._change_int_ext_value(data);
-            }).fail(function(error, event) {
-                event.preventDefault();
-                slow_create();
-            });
-        } else
-            slow_create();
-    },
-    // all search/create popup handling
-    _search_create_popup: function(view, ids, context) {
+    render_value: function(no_recurse) {
         var self = this;
-        var pop = new openerp.web.form.SelectCreatePopup(this);
-        pop.select_element(
-            self.field.relation,
-            {
-                title: (view === 'search' ? _t("Search: ") : _t("Create: ")) + (this.string || this.name),
-                initial_ids: ids ? _.map(ids, function(x) {return x[0]}) : undefined,
-                initial_view: view,
-                disable_multiple_selection: true
-            },
-            self.build_domain(),
-            new openerp.web.CompoundContext(self.build_context(), context || {})
-        );
-        pop.on_select_elements.add(function(element_ids) {
-            var dataset = new openerp.web.DataSetStatic(self, self.field.relation, self.build_context());
-            dataset.name_get([element_ids[0]], function(data) {
-                self._change_int_ext_value(data[0]);
-                self.focus();
-            });
-        });
-    },
-    _change_int_ext_value: function(value) {
-        this._change_int_value(value);
-        this.$input.val(this.value ? this.value[1] : "");
-    },
-    _change_int_value: function(value) {
-        this.value = value;
-        var back_orig_value = this.original_value;
-        if (this.value === null || this.value) {
-            this.original_value = this.value;
+        if (! this.get("value")) {
+            this.display_string("");
+            return;
         }
-        if (back_orig_value === undefined) { // first use after a set_value()
+        var display = this.display_value["" + this.get("value")];
+        if (display) {
+            this.display_string(display);
             return;
         }
-        if (this.value !== undefined && ((back_orig_value ? back_orig_value[0] : null)
-                !== (this.value ? this.value[0] : null))) {
-            this.on_ui_change();
+        if (! no_recurse) {
+            var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.view.dataset.get_context());
+            dataset.name_get([self.get("value")], function(data) {
+                self.display_value["" + self.get("value")] = data[0][1];
+                self.render_value(true);
+            });
         }
     },
-    set_value: function(value) {
-        value = value || null;
-        this.invalid = false;
+    display_string: function(str) {
         var self = this;
-        this.tmp_value = value;
-        self.update_dom();
-        self.on_value_changed();
-        var real_set_value = function(rval) {
-            self.tmp_value = undefined;
-            self.value = rval;
-            self.original_value = undefined;
-            self._change_int_ext_value(rval);
-        };
-        if (value && !(value instanceof Array)) {
-            // name_get in a m2o does not use the context of the field
-            var dataset = new openerp.web.DataSetStatic(this, this.field.relation, self.view.dataset.get_context());
-            dataset.name_get([value], function(data) {
-                real_set_value(data[0]);
-            }).fail(function() {self.tmp_value = undefined;});
+        if (!this.get("effective_readonly")) {
+            this.$input.val(str);
         } else {
-            $.async_when().then(function() {real_set_value(value);});
-        }
-    },
-    get_value: function() {
-        if (this.tmp_value !== undefined) {
-            if (this.tmp_value instanceof Array) {
-                return this.tmp_value[0];
-            }
-            return this.tmp_value ? this.tmp_value : false;
+            this.$element.find('a')
+                 .unbind('click')
+                 .text(str)
+                 .click(function () {
+                    self.do_action({
+                        type: 'ir.actions.act_window',
+                        res_model: self.field.relation,
+                        res_id: self.get("value"),
+                        context: self.build_context(),
+                        views: [[false, 'form']],
+                        target: 'current'
+                    });
+                    return false;
+                 });
         }
-        if (this.value === undefined)
-            return this.original_value ? this.original_value[0] : false;
-        return this.value ? this.value[0] : false;
     },
-    validate: function() {
-        this.invalid = false;
-        var val = this.tmp_value !== undefined ? this.tmp_value : this.value;
-        if (val === null) {
-            this.invalid = this.required;
+    set_value: function(value_) {
+        var self = this;
+        if (value_ instanceof Array) {
+            this.display_value = {};
+            this.display_value["" + value_[0]] = value_[1];
+            value_ = value_[0];
         }
+        value_ = value_ || false;
+        this.inhibit_on_change = true;
+        this._super(value_);
+        this.inhibit_on_change = false;
     },
-    open_related: function(related) {
-        var self = this;
-        if (!self.value)
-            return;
-        var additional_context = {
-                active_id: self.value[0],
-                active_ids: [self.value[0]],
-                active_model: self.field.relation
-        };
-        self.rpc("/web/action/load", {
-            action_id: related[2].id,
-            context: additional_context
-        }, function(result) {
-            result.result.context = _.extend(result.result.context || {}, additional_context);
-            self.do_action(result.result);
-        });
+    add_id: function(id) {
+        this.display_value = {};
+        this.set({value: id});
     },
-    focus: function ($element) {
-        this._super($element || this.$input);
+    is_false: function() {
+        return ! this.get("value");
     },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.$input.prop('readonly', this.readonly);
+    focus: function () {
+        this.delay_focus(this.$input);
     }
 });
 
@@ -2337,28 +2808,28 @@ var commands = {
         return [6, false, ids];
     }
 };
-openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
-    template: 'FieldOne2Many',
+instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
     multi_selection: false,
-    init: function(view, node) {
-        this._super(view, node);
+    disable_utility_classes: true,
+    init: function(field_manager, node) {
+        this._super(field_manager, node);
+        lazy_build_o2m_kanban_view();
         this.is_loaded = $.Deferred();
         this.initial_is_loaded = this.is_loaded;
         this.is_setted = $.Deferred();
         this.form_last_update = $.Deferred();
         this.init_form_last_update = this.form_last_update;
-        this.disable_utility_classes = true;
     },
     start: function() {
         this._super.apply(this, arguments);
+        this.$element.addClass('oe_form_field_one2many');
 
         var self = this;
 
-        this.dataset = new openerp.web.form.One2ManyDataSet(this, this.field.relation);
+        this.dataset = new instance.web.form.One2ManyDataSet(this, this.field.relation);
         this.dataset.o2m = this;
         this.dataset.parent_view = this.view;
         this.dataset.child_name = this.name;
-        //this.dataset.child_name = 
         this.dataset.on_change.add_last(function() {
             self.trigger_on_change();
         });
@@ -2366,16 +2837,23 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
         this.is_setted.then(function() {
             self.load_views();
         });
+        this.is_loaded.then(function() {
+            self.on("change:effective_readonly", self, function() {
+                self.is_loaded = self.is_loaded.pipe(function() {
+                    self.viewmanager.destroy();
+                    return $.when(self.load_views()).then(function() {
+                        self.reload_current_view();
+                    });
+                });
+            });
+        });
     },
     trigger_on_change: function() {
         var tmp = this.doing_on_change;
         this.doing_on_change = true;
-        this.on_ui_change();
+        this.trigger('changed_value');
         this.doing_on_change = tmp;
     },
-    is_readonly: function() {
-        return this.readonly || this.force_readonly;
-    },
     load_views: function() {
         var self = this;
         
@@ -2383,38 +2861,50 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
         modes = !!modes ? modes.split(",") : ["tree"];
         var views = [];
         _.each(modes, function(mode) {
+            if (! _.include(["list", "tree", "graph", "kanban"], mode)) {
+                try {
+                    throw new Error(_.str.sprintf("View type '%s' is not supported in One2Many.", mode));
+                } catch(e) {
+                    instance.webclient.crashmanager.on_javascript_exception(e)
+                }
+            }
             var view = {
                 view_id: false,
                 view_type: mode == "tree" ? "list" : mode,
-                options: { sidebar : false }
+                options: {}
             };
             if (self.field.views && self.field.views[mode]) {
                 view.embedded_view = self.field.views[mode];
             }
             if(view.view_type === "list") {
                 view.options.selectable = self.multi_selection;
-                if (self.is_readonly()) {
+                view.options.sortable = false;
+                if (self.get("effective_readonly")) {
                     view.options.addable = null;
                     view.options.deletable = null;
-                    view.options.isClarkGable = false;
+                    view.options.reorderable = false;
                 }
             } else if (view.view_type === "form") {
-                if (self.is_readonly()) {
-                    view.view_type = 'page';
+                if (self.get("effective_readonly")) {
+                    view.view_type = 'form';
                 }
                 view.options.not_interactible_on_create = true;
+            } else if (view.view_type === "kanban") {
+                view.options.confirm_on_delete = false;
+                if (self.get("effective_readonly")) {
+                    view.options.action_buttons = false;
+                    view.options.quick_creatable = false;
+                    view.options.creatable = false;
+                    view.options.read_only_mode = true;
+                }
             }
             views.push(view);
         });
         this.views = views;
 
-        this.viewmanager = new openerp.web.ViewManager(this, this.dataset, views, {});
-        this.viewmanager.template = 'One2Many.viewmanager';
-        this.viewmanager.registry = openerp.web.views.extend({
-            list: 'openerp.web.form.One2ManyListView',
-            form: 'openerp.web.form.One2ManyFormView',
-            page: 'openerp.web.PageView'
-        });
+        this.viewmanager = new instance.web.form.One2ManyViewManager(this, this.dataset, views, {});
+        this.viewmanager.$element.addClass("oe_view_manager_one2many");
+        this.viewmanager.o2m = self;
         var once = $.Deferred().then(function() {
             self.init_form_last_update.resolve();
         });
@@ -2422,12 +2912,12 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
             self.initial_is_loaded.resolve();
         });
         this.viewmanager.on_controller_inited.add_last(function(view_type, controller) {
+            controller.o2m = self;
             if (view_type == "list") {
-                controller.o2m = self;
-                if (self.is_readonly())
+                if (self.get("effective_readonly"))
                     controller.set_editable(false);
-            } else if (view_type == "form" || view_type == 'page') {
-                if (view_type == 'page' || self.is_readonly()) {
+            } else if (view_type === "form") {
+                if (self.get("effective_readonly")) {
                     $(".oe_form_buttons", controller.$element).children().remove();
                 }
                 controller.on_record_loaded.add_last(function() {
@@ -2461,7 +2951,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
             var view = self.viewmanager.views[active_view].controller;
             if(active_view === "list") {
                 return view.reload_content();
-            } else if (active_view === "form" || active_view === 'page') {
+            } else if (active_view === "form") {
                 if (self.dataset.index === null && self.dataset.ids.length >= 1) {
                     self.dataset.index = 0;
                 }
@@ -2470,18 +2960,18 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
                 };
                 self.form_last_update = self.form_last_update.pipe(act, act);
                 return self.form_last_update;
-            } else if (active_view === "graph") {
+            } else if (view.do_search) {
                 return view.do_search(self.build_domain(), self.dataset.get_context(), []);
             }
         }, undefined);
     },
-    set_value: function(value) {
-        value = value || [];
+    set_value: function(value_) {
+        value_ = value_ || [];
         var self = this;
         this.dataset.reset_ids([]);
-        if(value.length >= 1 && value[0] instanceof Array) {
+        if(value_.length >= 1 && value_[0] instanceof Array) {
             var ids = [];
-            _.each(value, function(command) {
+            _.each(value_, function(command) {
                 var obj = {values: command[2]};
                 switch (command[0]) {
                     case commands.CREATE:
@@ -2510,10 +3000,10 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
             });
             this._super(ids);
             this.dataset.set_ids(ids);
-        } else if (value.length >= 1 && typeof(value[0]) === "object") {
+        } else if (value_.length >= 1 && typeof(value_[0]) === "object") {
             var ids = [];
             this.dataset.delete_all = true;
-            _.each(value, function(command) {
+            _.each(value_, function(command) {
                 var obj = {values: command};
                 obj['id'] = _.uniqueId(self.dataset.virtual_id_prefix);
                 obj.defaults = {};
@@ -2524,8 +3014,8 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
             this._super(ids);
             this.dataset.set_ids(ids);
         } else {
-            this._super(value);
-            this.dataset.reset_ids(value);
+            this._super(value_);
+            this.dataset.reset_ids(value_);
         }
         if (this.dataset.index === null && this.dataset.ids.length > 0) {
             this.dataset.index = 0;
@@ -2582,7 +3072,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
                return false;
            }, this));
     },
-    is_valid: function() {
+    is_syntax_valid: function() {
         if (!this.viewmanager.views[this.viewmanager.active_view])
             return true;
         var view = this.viewmanager.views[this.viewmanager.active_view].controller;
@@ -2598,39 +3088,65 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
         }
         return true;
     },
-    is_dirty: function() {
-        this.save_any_view();
-        return this._super();
+});
+
+instance.web.form.One2ManyViewManager = instance.web.ViewManager.extend({
+    template: 'One2Many.viewmanager',
+    init: function(parent, dataset, views, flags) {
+        this._super(parent, dataset, views, _.extend({}, flags, {$sidebar: false}));
+        this.registry = this.registry.extend({
+            list: 'instance.web.form.One2ManyListView',
+            form: 'instance.web.form.One2ManyFormView',
+            kanban: 'instance.web.form.One2ManyKanbanView',
+        });
     },
-    update_dom: function() {
-        this._super.apply(this, arguments);
+    switch_view: function(mode, unused) {
+        if (mode !== 'form') {
+            return this._super(mode, unused);
+        }
         var self = this;
-        if (this.previous_readonly !== this.readonly) {
-            this.previous_readonly = this.readonly;
-            if (this.viewmanager) {
-                this.is_loaded = this.is_loaded.pipe(function() {
-                    self.viewmanager.stop();
-                    return $.when(self.load_views()).then(function() {
-                        self.reload_current_view();
-                    });
+        var id = self.o2m.dataset.index !== null ? self.o2m.dataset.ids[self.o2m.dataset.index] : null;
+        var pop = new instance.web.form.FormOpenPopup(self.o2m.view);
+        pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), {
+            title: _t("Open: ") + self.name,
+            create_function: function(data) {
+                return self.o2m.dataset.create(data).then(function(r) {
+                    self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r.result]));
+                    self.o2m.dataset.on_change();
                 });
-            }
-        }
-    }
+            },
+            write_function: function(id, data, options) {
+                return self.o2m.dataset.write(id, data, {}).then(function() {
+                    self.o2m.reload_current_view();
+                });
+            },
+            alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined,
+            parent_view: self.o2m.view,
+            child_name: self.o2m.name,
+            read_function: function() {
+                return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
+            },
+            form_view_options: {'not_interactible_on_create':true},
+            readonly: self.o2m.get("effective_readonly")
+        });
+        pop.on_select_elements.add_last(function() {
+            self.o2m.reload_current_view();
+        });
+    },
 });
 
-openerp.web.form.One2ManyDataSet = openerp.web.BufferedDataSet.extend({
+instance.web.form.One2ManyDataSet = instance.web.BufferedDataSet.extend({
     get_context: function() {
         this.context = this.o2m.build_context([this.o2m.name]);
         return this.context;
     }
 });
 
-openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
+instance.web.form.One2ManyListView = instance.web.ListView.extend({
     _template: 'One2Many.listview',
     init: function (parent, dataset, view_id, options) {
         this._super(parent, dataset, view_id, _.extend(options || {}, {
-            ListType: openerp.web.form.One2ManyList
+            ListType: instance.web.form.One2ManyList
         }));
     },
     is_valid: function () {
@@ -2649,8 +3165,7 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
         // Otherwise validate internal form
         return _(form.fields).chain()
             .invoke(function () {
-                this.validate();
-                this.update_dom(true);
+                this._check_css_flag();
                 return this.is_valid();
             })
             .all(_.identity)
@@ -2674,8 +3189,7 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
             this._super.apply(this, arguments);
         } else {
             var self = this;
-            var pop = new openerp.web.form.SelectCreatePopup(this);
-            pop.on_default_get.add(self.dataset.on_default_get);
+            var pop = new instance.web.form.SelectCreatePopup(this);
             pop.select_element(
                 self.o2m.field.relation,
                 {
@@ -2705,10 +3219,14 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
     },
     do_activate_record: function(index, id) {
         var self = this;
-        var pop = new openerp.web.form.FormOpenPopup(self.o2m.view);
+        var pop = new instance.web.form.FormOpenPopup(self.o2m.view);
         pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), {
             title: _t("Open: ") + self.name,
-            auto_write: false,
+            write_function: function(id, data) {
+                return self.o2m.dataset.write(id, data, {}, function(r) {
+                    self.o2m.reload_current_view();
+                });
+            },
             alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined,
             parent_view: self.o2m.view,
             child_name: self.o2m.name,
@@ -2716,26 +3234,18 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
                 return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
             },
             form_view_options: {'not_interactible_on_create':true},
-            readonly: self.o2m.is_readonly()
-        });
-        pop.dataset.call_button = function() {
-            var button_result = self.o2m.dataset.call_button.apply(self.o2m.dataset, arguments);
-            self.o2m.reload_current_view();
-            return button_result;
-        };
-        pop.on_write.add(function(id, data) {
-            self.o2m.dataset.write(id, data, {}, function(r) {
-                self.o2m.reload_current_view();
-            });
+            readonly: self.o2m.get("effective_readonly")
         });
     },
     do_button_action: function (name, id, callback) {
-        var self = this;
-        var def = $.Deferred().then(callback).then(function() {self.o2m.view.reload();});
-        return this._super(name, id, _.bind(def.resolve, def));
+        var _super = _.bind(this._super, this);
+
+        this.o2m.view.do_save().then(function () {
+            _super(name, id, callback);
+        });
     }
 });
-openerp.web.form.One2ManyList = openerp.web.ListView.List.extend({
+instance.web.form.One2ManyList = instance.web.ListView.List.extend({
     KEY_RETURN: 13,
     // blurring caused by hitting the [Return] key, should skip the
     // autosave-on-blur and let the handler for [Return] do its thing
@@ -2743,12 +3253,25 @@ openerp.web.form.One2ManyList = openerp.web.ListView.List.extend({
     render_row_as_form: function () {
         var self = this;
         return this._super.apply(this, arguments).then(function () {
+            // Replace the "Save Row" button with "Cancel Edition"
             self.edition_form.$element
                 .undelegate('button.oe-edit-row-save', 'click')
                 .delegate('button.oe-edit-row-save', 'click', function () {
                     self.cancel_pending_edition();
                 });
-            $(self.edition_form).bind('form-blur', function () {
+
+            // Overload execute_action on the edition form to perform a simple
+            // reload_record after the action is done, rather than fully
+            // reload the parent view (or something)
+            var _execute_action = self.edition_form.do_execute_action;
+            self.edition_form.do_execute_action = function (action, dataset, record_id, _callback) {
+                return _execute_action.call(this, action, dataset, record_id, function () {
+                    self.view.reload_record(
+                        self.view.records.get(record_id));
+                });
+            };
+
+            self.edition_form.on('blurred', null, function () {
                 if (self.__return_blur) {
                     delete self.__return_blur;
                     return;
@@ -2767,12 +3290,12 @@ openerp.web.form.One2ManyList = openerp.web.ListView.List.extend({
     }
 });
 
-openerp.web.form.One2ManyFormView = openerp.web.FormView.extend({
+instance.web.form.One2ManyFormView = instance.web.FormView.extend({
     form_template: 'One2Many.formview',
     on_loaded: function(data) {
         this._super(data);
         var self = this;
-        this.$form_header.find('button.oe_form_button_create').click(function() {
+        this.$buttons.find('button.oe_form_button_create').click(function() {
             self.do_save().then(self.on_button_new);
         });
     },
@@ -2785,58 +3308,203 @@ openerp.web.form.One2ManyFormView = openerp.web.FormView.extend({
     }
 });
 
-openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
-    template: 'FieldMany2Many',
+var lazy_build_o2m_kanban_view = function() {
+if (! instance.web_kanban || instance.web.form.One2ManyKanbanView)
+    return;
+instance.web.form.One2ManyKanbanView = instance.web_kanban.KanbanView.extend({
+});
+}
+
+instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, instance.web.form.ReinitializeFieldMixin, {
+    template: "FieldMany2ManyTags",
+    init: function() {
+        this._super.apply(this, arguments);
+        instance.web.form.CompletionFieldMixin.init.call(this);
+        this.set({"value": []});
+        this._display_orderer = new instance.web.DropMisordered();
+        this._drop_shown = false;
+    },
+    start: function() {
+        this._super();
+        instance.web.form.ReinitializeFieldMixin.start.call(this);
+        this.on("change:value", this, this.render_value);
+    },
+    initialize_content: function() {
+        if (this.get("effective_readonly"))
+            return;
+        var self = this;
+        self.$text = $("textarea", this.$element);
+        self.$text.textext({
+            plugins : 'tags arrow autocomplete',
+            autocomplete: {
+                render: function(suggestion) {
+                    return $('<span class="text-label"/>').
+                             data('index', suggestion['index']).html(suggestion['label']);
+                }
+            },
+            ext: {
+                autocomplete: {
+                    selectFromDropdown: function() {
+                        $(this).trigger('hideDropdown');
+                        var index = Number(this.selectedSuggestionElement().children().children().data('index'));
+                        var data = self.search_result[index];
+                        if (data.id) {
+                            self.add_id(data.id);
+                        } else {
+                            data.action();
+                        }
+                    },
+                },
+                tags: {
+                    isTagAllowed: function(tag) {
+                        if (! tag.name)
+                            return false;
+                        return true;
+                    },
+                    removeTag: function(tag) {
+                        var id = tag.data("id");
+                        self.set({"value": _.without(self.get("value"), id)});
+                    },
+                    renderTag: function(stuff) {
+                        return $.fn.textext.TextExtTags.prototype.renderTag.
+                            call(this, stuff).data("id", stuff.id);
+                    },
+                },
+                itemManager: {
+                    itemToString: function(item) {
+                        return item.name;
+                    },
+                },
+            },
+        }).bind('getSuggestions', function(e, data) {
+            var _this = this;
+            var str = !!data ? data.query || '' : '';
+            self.get_search_result(str).then(function(result) {
+                self.search_result = result;
+                $(_this).trigger('setSuggestions', {result : _.map(result, function(el, i) {
+                    return _.extend(el, {index:i});
+                })});
+            });
+        }).bind('hideDropdown', function() {
+            self._drop_shown = false;
+        }).bind('showDropdown', function() {
+            self._drop_shown = true;
+        });
+        self.tags = self.$text.textext()[0].tags();
+        $("textarea", this.$element).focusout(function() {
+            self.$text.trigger("setInputData", "");
+        }).keydown(function(e) {
+            if (event.keyCode === 9 && self._drop_shown) {
+                self.$text.textext()[0].autocomplete().selectFromDropdown();
+            }
+        });
+    },
+    set_value: function(value_) {
+        value_ = value_ || [];
+        if (value_.length >= 1 && value_[0] instanceof Array) {
+            value_ = value_[0][2];
+        }
+        this._super(value_);
+    },
+    get_value: function() {
+        var tmp = [commands.replace_with(this.get("value"))];
+        return tmp;
+    },
+    get_search_blacklist: function() {
+        return this.get("value");
+    },
+    render_value: function() {
+        var self = this;
+        var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.view.dataset.get_context());
+        var handle_names = function(data) {
+            var indexed = {};
+            _.each(data, function(el) {
+                indexed[el[0]] = el;
+            });
+            data = _.map(self.get("value"), function(el) { return indexed[el]; });
+            if (! self.get("effective_readonly")) {
+                self.tags.containerElement().children().remove();
+                $("textarea", self.$element).css("padding-left", "3px");
+                self.tags.addTags(_.map(data, function(el) {return {name: el[1], id:el[0]};}));
+            } else {
+                self.$element.html(QWeb.render("FieldMany2ManyTags.box", {elements: data}));
+            }
+        };
+        if (! self.get('values') || self.get('values').length > 0) {
+            this._display_orderer.add(dataset.name_get(self.get("value"))).then(handle_names);
+        } else {
+            handle_names([]);
+        }
+    },
+    add_id: function(id) {
+        this.set({'value': _.uniq(this.get('value').concat([id]))});
+    },
+});
+
+/*
+ * TODO niv: clean those deferred stuff, it could be better
+ */
+instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend({
     multi_selection: false,
-    init: function(view, node) {
-        this._super(view, node);
-        this.list_id = _.uniqueId("many2many");
+    disable_utility_classes: true,
+    init: function(field_manager, node) {
+        this._super(field_manager, node);
         this.is_loaded = $.Deferred();
         this.initial_is_loaded = this.is_loaded;
         this.is_setted = $.Deferred();
     },
     start: function() {
         this._super.apply(this, arguments);
+        this.$element.addClass('oe_form_field_many2many');
 
         var self = this;
 
-        this.dataset = new openerp.web.form.Many2ManyDataSet(this, this.field.relation);
+        this.dataset = new instance.web.form.Many2ManyDataSet(this, this.field.relation);
         this.dataset.m2m = this;
         this.dataset.on_unlink.add_last(function(ids) {
-            self.on_ui_change();
+            self.dataset_changed();
         });
-        
+
         this.is_setted.then(function() {
             self.load_view();
         });
+        this.is_loaded.then(function() {
+            self.on("change:effective_readonly", self, function() {
+                self.is_loaded = self.is_loaded.pipe(function() {
+                    self.list_view.destroy();
+                    return $.when(self.load_view()).then(function() {
+                        self.reload_content();
+                    });
+                });
+            });
+        });
     },
-    set_value: function(value) {
-        value = value || [];
-        if (value.length >= 1 && value[0] instanceof Array) {
-            value = value[0][2];
+    set_value: function(value_) {
+        value_ = value_ || [];
+        if (value_.length >= 1 && value_[0] instanceof Array) {
+            value_ = value_[0][2];
         }
-        this._super(value);
-        this.dataset.set_ids(value);
+        this._super(value_);
+        this.dataset.set_ids(value_);
         var self = this;
         self.reload_content();
         this.is_setted.resolve();
     },
     get_value: function() {
-        return [commands.replace_with(this.dataset.ids)];
-    },
-    validate: function() {
-        this.invalid = this.required && _(this.dataset.ids).isEmpty();
+        return [commands.replace_with(this.get('value'))];
     },
-    is_readonly: function() {
-        return this.readonly || this.force_readonly;
+
+    is_false: function () {
+        return _(this.dataset.ids).isEmpty();
     },
     load_view: function() {
         var self = this;
-        this.list_view = new openerp.web.form.Many2ManyListView(this, this.dataset, false, {
-                    'addable': self.is_readonly() ? null : _t("Add"),
-                    'deletable': self.is_readonly() ? false : true,
+        this.list_view = new instance.web.form.Many2ManyListView(this, this.dataset, false, {
+                    'addable': self.get("effective_readonly") ? null : _t("Add"),
+                    'deletable': self.get("effective_readonly") ? false : true,
                     'selectable': self.multi_selection,
-                    'isClarkGable': self.is_readonly() ? false : true
+                    'sortable': false,
+                    'reorderable': false,
             });
         var embedded = (this.field.views || {}).tree;
         if (embedded) {
@@ -2849,7 +3517,7 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
             loaded.resolve();
         });
         $.async_when().then(function () {
-            self.list_view.appendTo($("#" + self.list_id));
+            self.list_view.appendTo(self.$element);
         });
         return loaded;
     },
@@ -2859,24 +3527,12 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
             return self.list_view.reload_content();
         });
     },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        var self = this;
-        if (this.previous_readonly !== this.readonly) {
-            this.previous_readonly = this.readonly;
-            if (this.list_view) {
-                this.is_loaded = this.is_loaded.pipe(function() {
-                    self.list_view.stop();
-                    return $.when(self.load_view()).then(function() {
-                        self.reload_content();
-                    });
-                });
-            }
-        }
-    }
+    dataset_changed: function() {
+        this.set({'value': this.dataset.ids});
+    },
 });
 
-openerp.web.form.Many2ManyDataSet = openerp.web.DataSetStatic.extend({
+instance.web.form.Many2ManyDataSet = instance.web.DataSetStatic.extend({
     get_context: function() {
         this.context = this.m2m.build_context();
         return this.context;
@@ -2885,25 +3541,25 @@ openerp.web.form.Many2ManyDataSet = openerp.web.DataSetStatic.extend({
 
 /**
  * @class
- * @extends openerp.web.ListView
+ * @extends instance.web.ListView
  */
-openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends openerp.web.form.Many2ManyListView# */{
+instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends instance.web.form.Many2ManyListView# */{
     do_add_record: function () {
-        var pop = new openerp.web.form.SelectCreatePopup(this);
+        var pop = new instance.web.form.SelectCreatePopup(this);
         pop.select_element(
             this.model,
             {
                 title: _t("Add: ") + this.name
             },
-            new openerp.web.CompoundDomain(this.m2m_field.build_domain(), ["!", ["id", "in", this.m2m_field.dataset.ids]]),
+            new instance.web.CompoundDomain(this.m2m_field.build_domain(), ["!", ["id", "in", this.m2m_field.dataset.ids]]),
             this.m2m_field.build_context()
         );
         var self = this;
         pop.on_select_elements.add(function(element_ids) {
-            _.each(element_ids, function(element_id) {
-                if(! _.detect(self.dataset.ids, function(x) {return x == element_id;})) {
-                    self.dataset.set_ids([].concat(self.dataset.ids, [element_id]));
-                    self.m2m_field.on_ui_change();
+            _.each(element_ids, function(one_id) {
+                if(! _.detect(self.dataset.ids, function(x) {return x == one_id;})) {
+                    self.dataset.set_ids([].concat(self.dataset.ids, [one_id]));
+                    self.m2m_field.dataset_changed();
                     self.reload_content();
                 }
             });
@@ -2911,10 +3567,10 @@ openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends open
     },
     do_activate_record: function(index, id) {
         var self = this;
-        var pop = new openerp.web.form.FormOpenPopup(this);
+        var pop = new instance.web.form.FormOpenPopup(this);
         pop.show_element(this.dataset.model, id, this.m2m_field.build_context(), {
             title: _t("Open: ") + this.name,
-            readonly: this.widget_parent.is_readonly()
+            readonly: this.getParent().get("effective_readonly")
         });
         pop.on_write_completed.add_last(function() {
             self.reload_content();
@@ -2922,71 +3578,383 @@ openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends open
     }
 });
 
+instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, {
+    disable_utility_classes: true,
+    init: function(field_manager, node) {
+        this._super(field_manager, node);
+        instance.web.form.CompletionFieldMixin.init.call(this);
+        m2m_kanban_lazy_init();
+        this.is_loaded = $.Deferred();
+        this.initial_is_loaded = this.is_loaded;
+        this.is_setted = $.Deferred();
+    },
+    start: function() {
+        this._super.apply(this, arguments);
+
+        var self = this;
+
+        this.dataset = new instance.web.form.Many2ManyDataSet(this, this.field.relation);
+        this.dataset.m2m = this;
+        this.dataset.on_unlink.add_last(function(ids) {
+            self.dataset_changed();
+        });
+        
+        this.is_setted.then(function() {
+            self.load_view();
+        });
+        this.is_loaded.then(function() {
+            self.on("change:effective_readonly", self, function() {
+                self.is_loaded = self.is_loaded.pipe(function() {
+                    self.kanban_view.destroy();
+                    return $.when(self.load_view()).then(function() {
+                        self.reload_content();
+                    });
+                });
+            });
+        })
+    },
+    set_value: function(value_) {
+        value_ = value_ || [];
+        if (value_.length >= 1 && value_[0] instanceof Array) {
+            value_ = value_[0][2];
+        }
+        this._super(value_);
+        this.dataset.set_ids(value_);
+        var self = this;
+        self.reload_content();
+        this.is_setted.resolve();
+    },
+    load_view: function() {
+        var self = this;
+        this.kanban_view = new instance.web.form.Many2ManyKanbanView(this, this.dataset, false, {
+                    'create_text': _t("Add"),
+                    'creatable': self.get("effective_readonly") ? false : true,
+                    'quick_creatable': self.get("effective_readonly") ? false : true,
+                    'read_only_mode': self.get("effective_readonly") ? true : false,
+                    'confirm_on_delete': false,
+            });
+        var embedded = (this.field.views || {}).kanban;
+        if (embedded) {
+            this.kanban_view.set_embedded_view(embedded);
+        }
+        this.kanban_view.m2m = this;
+        var loaded = $.Deferred();
+        this.kanban_view.on_loaded.add_last(function() {
+            self.initial_is_loaded.resolve();
+            loaded.resolve();
+        });
+        this.kanban_view.do_switch_view.add_last(_.bind(this.open_popup, this));
+        $.async_when().then(function () {
+            self.kanban_view.appendTo(self.$element);
+        });
+        return loaded;
+    },
+    reload_content: function() {
+        var self = this;
+        this.is_loaded = this.is_loaded.pipe(function() {
+            return self.kanban_view.do_search(self.build_domain(), self.dataset.get_context(), []);
+        });
+    },
+    dataset_changed: function() {
+        this.set({'value': [commands.replace_with(this.dataset.ids)]});
+    },
+    open_popup: function(type, unused) {
+        if (type !== "form")
+            return;
+        var self = this;
+        if (this.dataset.index === null) {
+            var pop = new instance.web.form.SelectCreatePopup(this);
+            pop.select_element(
+                this.field.relation,
+                {
+                    title: _t("Add: ") + this.name
+                },
+                new instance.web.CompoundDomain(this.build_domain(), ["!", ["id", "in", this.dataset.ids]]),
+                this.build_context()
+            );
+            pop.on_select_elements.add(function(element_ids) {
+                _.each(element_ids, function(one_id) {
+                    if(! _.detect(self.dataset.ids, function(x) {return x == one_id;})) {
+                        self.dataset.set_ids([].concat(self.dataset.ids, [one_id]));
+                        self.dataset_changed();
+                        self.reload_content();
+                    }
+                });
+            });
+        } else {
+            var id = self.dataset.ids[self.dataset.index];
+            var pop = new instance.web.form.FormOpenPopup(self.view);
+            pop.show_element(self.field.relation, id, self.build_context(), {
+                title: _t("Open: ") + self.name,
+                write_function: function(id, data, options) {
+                    return self.dataset.write(id, data, {}).then(function() {
+                        self.reload_content();
+                    });
+                },
+                alternative_form_view: self.field.views ? self.field.views["form"] : undefined,
+                parent_view: self.view,
+                child_name: self.name,
+                readonly: self.get("effective_readonly")
+            });
+        }
+    },
+    add_id: function(id) {
+        this.quick_create.add_id(id);
+    },
+});
+
+function m2m_kanban_lazy_init() {
+if (instance.web.form.Many2ManyKanbanView)
+    return;
+instance.web.form.Many2ManyKanbanView = instance.web_kanban.KanbanView.extend({
+    quick_create_class: 'instance.web.form.Many2ManyQuickCreate',
+    _is_quick_create_enabled: function() {
+        return this._super() && ! this.group_by;
+    },
+});
+instance.web.form.Many2ManyQuickCreate = instance.web.Widget.extend({
+    template: 'Many2ManyKanban.quick_create',
+    
+    /**
+     * close_btn: If true, the widget will display a "Close" button able to trigger
+     * a "close" event.
+     */
+    init: function(parent, dataset, context, buttons) {
+        this._super(parent);
+        this.m2m = this.getParent().view.m2m;
+        this.m2m.quick_create = this;
+        this._dataset = dataset;
+        this._buttons = buttons || false;
+        this._context = context || {};
+    },
+    start: function () {
+        var self = this;
+        self.$text = this.$element.find('input').css("width", "200px");
+        self.$text.textext({
+            plugins : 'arrow autocomplete',
+            autocomplete: {
+                render: function(suggestion) {
+                    return $('<span class="text-label"/>').
+                             data('index', suggestion['index']).html(suggestion['label']);
+                }
+            },
+            ext: {
+                autocomplete: {
+                    selectFromDropdown: function() {
+                        $(this).trigger('hideDropdown');
+                        var index = Number(this.selectedSuggestionElement().children().children().data('index'));
+                        var data = self.search_result[index];
+                        if (data.id) {
+                            self.add_id(data.id);
+                        } else {
+                            data.action();
+                        }
+                    },
+                },
+                itemManager: {
+                    itemToString: function(item) {
+                        return item.name;
+                    },
+                },
+            },
+        }).bind('getSuggestions', function(e, data) {
+            var _this = this;
+            var str = !!data ? data.query || '' : '';
+            self.m2m.get_search_result(str).then(function(result) {
+                self.search_result = result;
+                $(_this).trigger('setSuggestions', {result : _.map(result, function(el, i) {
+                    return _.extend(el, {index:i});
+                })});
+            });
+        });
+        self.$text.focusout(function() {
+            self.$text.val("");
+        });
+    },
+    focus: function() {
+        this.$text.focus();
+    },
+    add_id: function(id) {
+        var self = this;
+        self.$text.val("");
+        self.trigger('added', id);
+        this.m2m.dataset_changed();
+    },
+});
+}
+
 /**
- * @class
- * @extends openerp.web.OldWidget
+ * Class with everything which is common between FormOpenPopup and SelectCreatePopup.
  */
-openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends openerp.web.form.SelectCreatePopup# */{
-    template: "SelectCreatePopup",
+instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({
+    template: "AbstractFormPopup.render",
     /**
-     * options:
-     * - initial_ids
-     * - initial_view: form or search (default search)
-     * - disable_multiple_selection
+     *  options:
+     *  -readonly: only applicable when not in creation mode, default to false
      * - alternative_form_view
-     * - create_function (defaults to a naive saving behavior)
+     * - write_function
+     * - read_function
+     * - create_function
      * - parent_view
      * - child_name
      * - form_view_options
-     * - list_view_options
-     * - read_function
      */
-    select_element: function(model, options, domain, context) {
-        var self = this;
+    init_popup: function(model, row_id, domain, context, options) {
+        this.row_id = row_id;
         this.model = model;
         this.domain = domain || [];
         this.context = context || {};
-        this.options = _.defaults(options || {}, {"initial_view": "search", "create_function": function() {
-            return self.create_row.apply(self, arguments);
-        }, read_function: null});
-        this.initial_ids = this.options.initial_ids;
-        this.created_elements = [];
-        this.render_element();
-        openerp.web.form.dialog(this.$element, {
-            close: function() {
-                self.check_exit();
-            },
-            title: options.title || ""
+        this.options = options;
+        _.defaults(this.options, {
         });
-        this.start();
     },
-    start: function() {
-        this._super();
+    init_dataset: function() {
         var self = this;
-        this.dataset = new openerp.web.ProxyDataSet(this, this.model,
-            this.context);
-        this.dataset.create_function = function() {
-            return self.options.create_function.apply(null, arguments).then(function(r) {
+        this.created_elements = [];
+        this.dataset = new instance.web.ProxyDataSet(this, this.model, this.context);
+        this.dataset.read_function = this.options.read_function;
+        this.dataset.create_function = function(data, sup) {
+            var fct = self.options.create_function || sup;
+            return fct.call(this, data).then(function(r) {
                 self.created_elements.push(r.result);
             });
         };
-        this.dataset.write_function = function() {
-            return self.write_row.apply(self, arguments);
+        this.dataset.write_function = function(id, data, options, sup) {
+            var fct = self.options.write_function || sup;
+            return fct.call(this, id, data, options).then(self.on_write_completed);
         };
-        this.dataset.read_function = this.options.read_function;
         this.dataset.parent_view = this.options.parent_view;
         this.dataset.child_name = this.options.child_name;
-        this.dataset.on_default_get.add(this.on_default_get);
+    },
+    display_popup: function() {
+        var self = this;
+        this.renderElement();
+        new instance.web.Dialog(this, {
+            width: '90%',
+            min_width: '800px',
+            close: function() {
+                self.check_exit(true);
+            },
+            title: this.options.title || "",
+        }, this.$element).open();
+        this.start();
+    },
+    on_write_completed: function() {},
+    setup_form_view: function() {
+        var self = this;
+        if (this.row_id) {
+            this.dataset.ids = [this.row_id];
+            this.dataset.index = 0;
+        } else {
+            this.dataset.index = null;
+        }
+        var options = _.clone(self.options.form_view_options) || {};
+        if (this.row_id !== null) {
+            options.initial_mode = this.options.readonly ? "view" : "edit";
+        }
+        this.view_form = new instance.web.FormView(this, this.dataset, false, options);
+        if (this.options.alternative_form_view) {
+            this.view_form.set_embedded_view(this.options.alternative_form_view);
+        }
+        this.view_form.appendTo(this.$element.find(".oe-form-view-popup-form-placeholder"));
+        this.view_form.on_loaded.add_last(function() {
+            var $buttons = self.view_form.$element.find(".oe_form_buttons");
+            var multi_select = self.row_id === null && ! self.options.disable_multiple_selection;
+            $buttons.html(QWeb.render("AbstractFormPopup.buttons", {multi_select: multi_select}));
+            var $snbutton = $buttons.find(".oe_abstractformpopup-form-save-new");
+            $snbutton.click(function() {
+                $.when(self.view_form.do_save()).then(function() {
+                    self.view_form.reload_mutex.exec(function() {
+                        self.view_form.on_button_new();
+                    });
+                });
+            });
+            var $sbutton = $buttons.find(".oe_abstractformpopup-form-save");
+            $sbutton.click(function() {
+                $.when(self.view_form.do_save()).then(function() {
+                    self.view_form.reload_mutex.exec(function() {
+                        self.check_exit();
+                    });
+                });
+            });
+            var $cbutton = $buttons.find(".oe_abstractformpopup-form-close");
+            $cbutton.click(function() {
+                self.check_exit();
+            });
+            if (self.row_id !== null && self.options.readonly) {
+                $snbutton.hide();
+                $sbutton.hide();
+                $cbutton.text(_t("Close"));
+            }
+            self.view_form.do_show();
+        });
+    },
+    on_select_elements: function(element_ids) {
+    },
+    check_exit: function(no_destroy) {
+        if (this.created_elements.length > 0) {
+            this.on_select_elements(this.created_elements);
+            this.created_elements = [];
+        }
+        this.destroy();
+    },
+    destroy: function () {
+        this.$element.dialog('close');
+        this._super();
+    },
+});
+
+/**
+ * Class to display a popup containing a form view.
+ */
+instance.web.form.FormOpenPopup = instance.web.form.AbstractFormPopup.extend({
+    show_element: function(model, row_id, context, options) {
+        this.init_popup(model, row_id, [], context,  options);
+        _.defaults(this.options, {
+        });
+        this.display_popup();
+    },
+    start: function() {
+        this._super();
+        this.init_dataset();
+        this.setup_form_view();
+    },
+});
+
+/**
+ * Class to display a popup to display a list to search a row. It also allows
+ * to switch to a form view to create a new row.
+ */
+instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend({
+    /**
+     * options:
+     * - initial_ids
+     * - initial_view: form or search (default search)
+     * - disable_multiple_selection
+     * - list_view_options
+     */
+    select_element: function(model, options, domain, context) {
+        this.init_popup(model, null, domain, context, options);
+        var self = this;
+        _.defaults(this.options, {
+            initial_view: "search",
+        });
+        this.initial_ids = this.options.initial_ids;
+        this.display_popup();
+    },
+    start: function() {
+        var self = this;
+        this.init_dataset();
         if (this.options.initial_view == "search") {
             self.rpc('/web/session/eval_domain_and_context', {
                 domains: [],
                 contexts: [this.context]
             }, function (results) {
                 var search_defaults = {};
-                _.each(results.context, function (value, key) {
+                _.each(results.context, function (value_, key) {
                     var match = /^search_default_(.*)$/.exec(key);
                     if (match) {
-                        search_defaults[match[1]] = value;
+                        search_defaults[match[1]] = value_;
                     }
                 });
                 self.setup_search_view(search_defaults);
@@ -2995,16 +3963,12 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
             this.new_object();
         }
     },
-    stop: function () {
-        this.$element.dialog('close');
-        this._super();
-    },
     setup_search_view: function(search_defaults) {
         var self = this;
         if (this.searchview) {
-            this.searchview.stop();
+            this.searchview.destroy();
         }
-        this.searchview = new openerp.web.SearchView(this,
+        this.searchview = new instance.web.SearchView(this,
                 this.dataset, false,  search_defaults);
         this.searchview.on_search.add(function(domains, contexts, groupbys) {
             if (self.initial_ids) {
@@ -3016,13 +3980,14 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
             }
         });
         this.searchview.on_loaded.add_last(function () {
-            self.view_list = new openerp.web.form.SelectCreateListView(self,
+            self.view_list = new instance.web.form.SelectCreateListView(self,
                     self.dataset, false,
                     _.extend({'deletable': false,
-                        'selectable': !self.options.disable_multiple_selection
+                        'selectable': !self.options.disable_multiple_selection,
+                        'read_only': true,
                     }, self.options.list_view_options || {}));
             self.view_list.popup = self;
-            self.view_list.appendTo($("#" + self.element_id + "_view_list")).pipe(function() {
+            self.view_list.appendTo($(".oe-select-create-popup-view-list", self.$element)).pipe(function() {
                 self.view_list.do_show();
             }).pipe(function() {
                 self.searchview.do_search();
@@ -3032,7 +3997,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
                 $buttons.prepend(QWeb.render("SelectCreatePopup.search.buttons"));
                 var $cbutton = $buttons.find(".oe_selectcreatepopup-search-close");
                 $cbutton.click(function() {
-                    self.stop();
+                    self.destroy();
                 });
                 var $sbutton = $buttons.find(".oe_selectcreatepopup-search-select");
                 if(self.options.disable_multiple_selection) {
@@ -3040,11 +4005,11 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
                 }
                 $sbutton.click(function() {
                     self.on_select_elements(self.selected_ids);
-                    self.stop();
+                    self.destroy();
                 });
             });
         });
-        this.searchview.appendTo($("#" + this.element_id + "_search"));
+        this.searchview.appendTo($(".oe-select-create-popup-view-list", self.$element));
     },
     do_search: function(domains, contexts, groupbys) {
         var self = this;
@@ -3056,22 +4021,6 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
             self.view_list.do_search(results.domain, results.context, results.group_by);
         });
     },
-    create_row: function() {
-        var self = this;
-        var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain);
-        wdataset.parent_view = this.options.parent_view;
-        wdataset.child_name = this.options.child_name;
-        return wdataset.create.apply(wdataset, arguments);
-    },
-    write_row: function() {
-        var self = this;
-        var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain);
-        wdataset.parent_view = this.options.parent_view;
-        wdataset.child_name = this.options.child_name;
-        return wdataset.write.apply(wdataset, arguments);
-    },
-    on_select_elements: function(element_ids) {
-    },
     on_click_element: function(ids) {
         this.selected_ids = ids || [];
         if(this.selected_ids.length > 0) {
@@ -3081,61 +4030,23 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
         }
     },
     new_object: function() {
-        var self = this;
         if (this.searchview) {
             this.searchview.hide();
         }
         if (this.view_list) {
             this.view_list.$element.hide();
         }
-        this.dataset.index = null;
-        this.view_form = new openerp.web.FormView(this, this.dataset, false, self.options.form_view_options);
-        if (this.options.alternative_form_view) {
-            this.view_form.set_embedded_view(this.options.alternative_form_view);
-        }
-        this.view_form.appendTo(this.$element.find("#" + this.element_id + "_view_form"));
-        this.view_form.on_loaded.add_last(function() {
-            var $buttons = self.view_form.$element.find(".oe_form_buttons");
-            $buttons.html(QWeb.render("SelectCreatePopup.form.buttons", {widget:self}));
-            var $nbutton = $buttons.find(".oe_selectcreatepopup-form-save-new");
-            $nbutton.click(function() {
-                $.when(self.view_form.do_save()).then(function() {
-                    self.view_form.reload_mutex.exec(function() {
-                        self.view_form.on_button_new();
-                    });
-                });
-            });
-            var $nbutton = $buttons.find(".oe_selectcreatepopup-form-save");
-            $nbutton.click(function() {
-                $.when(self.view_form.do_save()).then(function() {
-                    self.view_form.reload_mutex.exec(function() {
-                        self.check_exit();
-                    });
-                });
-            });
-            var $cbutton = $buttons.find(".oe_selectcreatepopup-form-close");
-            $cbutton.click(function() {
-                self.check_exit();
-            });
-        });
-        this.view_form.do_show();
-    },
-    check_exit: function() {
-        if (this.created_elements.length > 0) {
-            this.on_select_elements(this.created_elements);
-        }
-        this.stop();
+        this.setup_form_view();
     },
-    on_default_get: function(res) {}
 });
 
-openerp.web.form.SelectCreateListView = openerp.web.ListView.extend({
+instance.web.form.SelectCreateListView = instance.web.ListView.extend({
     do_add_record: function () {
         this.popup.new_object();
     },
     select_record: function(index) {
         this.popup.on_select_elements([this.dataset.ids[index]]);
-        this.popup.stop();
+        this.popup.destroy();
     },
     do_select: function(ids, records) {
         this._super(ids, records);
@@ -3143,135 +4054,11 @@ openerp.web.form.SelectCreateListView = openerp.web.ListView.extend({
     }
 });
 
-/**
- * @class
- * @extends openerp.web.OldWidget
- */
-openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp.web.form.FormOpenPopup# */{
-    template: "FormOpenPopup",
-    /**
-     * options:
-     * - alternative_form_view
-     * - auto_write (default true)
-     * - read_function
-     * - parent_view
-     * - child_name
-     * - form_view_options
-     * - readonly
-     */
-    show_element: function(model, row_id, context, options) {
-        this.model = model;
-        this.row_id = row_id;
-        this.context = context || {};
-        this.options = _.defaults(options || {}, {"auto_write": true});
-        this.render_element();
-        this.$element.dialog({
-            title: options.title || '',
-            modal: true,
-            width: 960,
-            height: 600
-        });
-        this.start();
-    },
-    start: function() {
-        this._super();
-        this.dataset = new openerp.web.form.FormOpenDataset(this, this.model, this.context);
-        this.dataset.fop = this;
-        this.dataset.ids = [this.row_id];
-        this.dataset.index = 0;
-        this.dataset.parent_view = this.options.parent_view;
-        this.dataset.child_name = this.options.child_name;
-        this.setup_form_view();
-    },
-    on_write: function(id, data) {
-        if (!this.options.auto_write)
-            return;
-        var self = this;
-        var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain);
-        wdataset.parent_view = this.options.parent_view;
-        wdataset.child_name = this.options.child_name;
-        wdataset.write(id, data, {}, function(r) {
-            self.on_write_completed();
-        });
-    },
-    on_write_completed: function() {},
-    setup_form_view: function() {
-        var self = this;
-        var FormClass = this.options.readonly
-                ? openerp.web.views.get_object('page')
-                : openerp.web.views.get_object('form');
-        this.view_form = new FormClass(this, this.dataset, false, self.options.form_view_options);
-        if (this.options.alternative_form_view) {
-            this.view_form.set_embedded_view(this.options.alternative_form_view);
-        }
-        this.view_form.appendTo(this.$element.find("#" + this.element_id + "_view_form"));
-        this.view_form.on_loaded.add_last(function() {
-            var $buttons = self.view_form.$element.find(".oe_form_buttons");
-            $buttons.html(QWeb.render("FormOpenPopup.form.buttons"));
-            var $nbutton = $buttons.find(".oe_formopenpopup-form-save");
-            $nbutton.click(function() {
-                self.view_form.do_save().then(function() {
-                    self.stop();
-                });
-            });
-            var $cbutton = $buttons.find(".oe_formopenpopup-form-close");
-            $cbutton.click(function() {
-                self.stop();
-            });
-            if (self.options.readonly) {
-                $nbutton.hide();
-                $cbutton.text(_t("Close"));
-            }
-            self.view_form.do_show();
-        });
-        this.dataset.on_write.add(this.on_write);
-    }
-});
-
-openerp.web.form.FormOpenDataset = openerp.web.ProxyDataSet.extend({
-    read_ids: function() {
-        if (this.fop.options.read_function) {
-            return this.fop.options.read_function.apply(null, arguments);
-        } else {
-            return this._super.apply(this, arguments);
-        }
-    }
-});
-
-openerp.web.form.FieldReference = openerp.web.form.Field.extend({
+instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
     template: 'FieldReference',
-    init: function(view, node) {
-        this._super(view, node);
-        this.fields_view = {
-            fields: {
-                selection: {
-                    selection: view.fields_view.fields[this.name].selection
-                },
-                m2o: {
-                    relation: null
-                }
-            }
-        };
-        this.get_fields_values = view.get_fields_values;
-        this.get_selected_ids = view.get_selected_ids;
-        this.do_onchange = this.on_form_changed = this.do_notify_change = this.on_nop;
-        this.dataset = this.view.dataset;
-        this.widgets_counter = 0;
-        this.view_id = 'reference_' + _.uniqueId();
-        this.widgets = {};
-        this.fields = {};
-        this.fields_order = [];
-        this.selection = new openerp.web.form.FieldSelection(this, { attrs: {
-            name: 'selection',
-            widget: 'selection'
-        }});
+    init: function(field_manager, node) {
+        this._super(field_manager, node);
         this.reference_ready = true;
-        this.selection.on_value_changed.add_last(this.on_selection_changed);
-        this.m2o = new openerp.web.form.FieldMany2One(this, { attrs: {
-            name: 'm2o',
-            widget: 'many2one'
-        }});
-        this.m2o.on_ui_change.add_last(this.on_ui_change);
     },
     on_nop: function() {
     },
@@ -3279,62 +4066,113 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({
         if (this.reference_ready) {
             var sel = this.selection.get_value();
             this.m2o.field.relation = sel;
-            this.m2o.set_value(null);
+            this.m2o.set_value(false);
             this.m2o.$element.toggle(sel !== false);
         }
     },
-    start: function() {
+    destroy_content: function() {
+        if (this.selection) {
+            this.selection.destroy();
+            this.selection = undefined;
+        }
+        if (this.m2o) {
+            this.m2o.destroy();
+            this.m2o = undefined;
+        }
+    },
+    initialize_content: function() {
         var self = this;
-        this._super();
+        this.selection = new instance.web.form.FieldSelection(this, { attrs: {
+            name: 'selection'
+        }});
+        this.selection.view = this.view;
+        this.selection.set({force_readonly: this.get('effective_readonly')});
+        this.selection.on("change:value", this, this.on_selection_changed);
+        this.selection.$element = $(".oe_form_view_reference_selection", this.$element);
+        this.selection.renderElement();
         this.selection.start();
+        this.selection
+            .on('focused', null, function () {self.trigger('focused')})
+            .on('blurred', null, function () {self.trigger('blurred')});
+
+        this.m2o = new instance.web.form.FieldMany2One(this, { attrs: {
+            name: 'm2o'
+        }});
+        this.m2o.view = this.view;
+        this.m2o.set({force_readonly: this.get("effective_readonly")});
+        this.m2o.on("change:value", this, this.data_changed);
+        this.m2o.$element = $(".oe_form_view_reference_m2o", this.$element);
+        this.m2o.renderElement();
         this.m2o.start();
-        $(this.selection).add($(this.m2o)).bind({
-            'focus': function () { $(self).trigger('widget-focus'); },
-            'blur': function () { $(self).trigger('widget-blur'); }
-        })
+        this.m2o
+            .on('focused', null, function () {self.trigger('focused')})
+            .on('blurred', null, function () {self.trigger('blurred')});
     },
-    is_valid: function() {
-        return this.required === false || typeof(this.get_value()) === 'string';
+    is_false: function() {
+        return typeof(this.get_value()) !== 'string';
     },
-    is_dirty: function() {
-        return this.selection.is_dirty() || this.m2o.is_dirty();
+    set_value: function(value_) {
+        this._super(value_);
+        this.render_value();
     },
-    set_value: function(value) {
-        this._super(value);
+    render_value: function() {
         this.reference_ready = false;
         var vals = [], sel_val, m2o_val;
-        if (typeof(value) === 'string') {
-            vals = value.split(',');
+        if (typeof(this.get('value')) === 'string') {
+            vals = this.get('value').split(',');
         }
         sel_val = vals[0] || false;
-        m2o_val = vals[1] ? parseInt(vals[1], 10) : false;
-        this.selection.set_value(sel_val);
+        m2o_val = vals[1] ? parseInt(vals[1], 10) : vals[1];
+        if (!this.get("effective_readonly")) {
+            this.selection.set_value(sel_val);
+        }
         this.m2o.field.relation = sel_val;
         this.m2o.set_value(m2o_val);
-        this.m2o.$element.toggle(sel_val !== false);
         this.reference_ready = true;
     },
-    get_value: function() {
+    data_changed: function() {
         var model = this.selection.get_value(),
             id = this.m2o.get_value();
         if (typeof(model) === 'string' && typeof(id) === 'number') {
-            return model + ',' + id;
+            this.set({'value': model + ',' + id});
         } else {
-            return false;
+            this.set({'value': false});
         }
-    }
+    },
+    get_field: function(name) {
+        if (name === "selection") {
+            return {
+                selection: this.view.fields_view.fields[this.name].selection,
+                type: "selection",
+            };
+        } else if (name === "m2o") {
+            return {
+                relation: null,
+                type: "many2one",
+            };
+        }
+        throw Exception("Should not happen");
+    },
 });
 
-openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
-    init: function(view, node) {
-        this._super(view, node);
-        this.iframe = this.element_id + '_iframe';
+instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
+    init: function(field_manager, node) {
+        var self = this;
+        this._super(field_manager, node);
         this.binary_value = false;
+        this.fileupload_id = _.uniqueId('oe_fileupload');
+        $(window).on(this.fileupload_id, function() {
+            var args = [].slice.call(arguments).slice(1);
+            self.on_file_uploaded.apply(self, args);
+        });
     },
-    start: function() {
+    stop: function() {
+        $(window).off(this.fileupload_id);
         this._super.apply(this, arguments);
+    },
+    initialize_content: function() {
         this.$element.find('input.oe-binary-file').change(this.on_file_change);
-        this.$element.find('button.oe-binary-file-save').click(this.on_save_as);
+        this.$element.find('button.oe_binary_file_save').click(this.on_save_as);
         this.$element.find('.oe-binary-file-clear').click(this.on_clear);
     },
     human_filesize : function(size) {
@@ -3350,8 +4188,8 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
         // TODO: on modern browsers, we could directly read the file locally on client ready to be used on image cropper
         // http://www.html5rocks.com/tutorials/file/dndfiles/
         // http://deepliquid.com/projects/Jcrop/demos.php?demo=handler
-        window[this.iframe] = this.on_file_uploaded;
-        if ($(e.target).val() != '') {
+
+        if ($(e.target).val() !== '') {
             this.$element.find('form.oe-binary-form input[name=session_id]').val(this.session.session_id);
             this.$element.find('form.oe-binary-form').submit();
             this.$element.find('.oe-binary-progress').show();
@@ -3359,34 +4197,46 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
         }
     },
     on_file_uploaded: function(size, name, content_type, file_base64) {
-        delete(window[this.iframe]);
         if (size === false) {
             this.do_warn("File Upload", "There was a problem while uploading your file");
             // TODO: use openerp web crashmanager
             console.warn("Error while uploading file : ", name);
         } else {
+            this.filename = name;
             this.on_file_uploaded_and_valid.apply(this, arguments);
-            this.on_ui_change();
         }
         this.$element.find('.oe-binary-progress').hide();
         this.$element.find('.oe-binary').show();
     },
     on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
     },
-    on_save_as: function() {
-        $.blockUI();
-        this.session.get_file({
-            url: '/web/binary/saveas_ajax',
-            data: {data: JSON.stringify({
-                model: this.view.dataset.model,
-                id: (this.view.datarecord.id || ''),
-                field: this.name,
-                filename_field: (this.node.attrs.filename || ''),
-                context: this.view.dataset.get_context()
-            })},
-            complete: $.unblockUI,
-            error: openerp.webclient.crashmanager.on_rpc_error
-        });
+    on_save_as: function(ev) {
+        var value = this.get('value');
+        if (!value) {
+            this.do_warn(_t("Save As..."), _t("The field is empty, there's nothing to save !"));
+            ev.stopPropagation();
+        } else if (this._dirty_flag) {
+            var link = this.$('.oe_binary_file_save_data')[0];
+            link.download = this.filename || "download.bin"; // Works on only on Google Chrome
+            //link.target = '_blank';
+            link.href = "data:application/octet-stream;base64," + value;
+        } else {
+            $.blockUI();
+            this.session.get_file({
+                url: '/web/binary/saveas_ajax',
+                data: {data: JSON.stringify({
+                    model: this.view.dataset.model,
+                    id: (this.view.datarecord.id || ''),
+                    field: this.name,
+                    filename_field: (this.node.attrs.filename || ''),
+                    context: this.view.dataset.get_context()
+                })},
+                complete: $.unblockUI,
+                error: instance.webclient.crashmanager.on_rpc_error
+            });
+            ev.stopPropagation();
+            return false;
+        }
     },
     set_filename: function(value) {
         var filename = this.node.attrs.filename;
@@ -3396,39 +4246,62 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
         }
     },
     on_clear: function() {
-        if (this.value !== false) {
-            this.value = false;
+        if (this.get('value') !== false) {
             this.binary_value = false;
-            this.on_ui_change();
+            this.set({'value': false});
         }
         return false;
     }
 });
 
-openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({
+instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({
     template: 'FieldBinaryFile',
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.$element.find('.oe-binary-file-set, .oe-binary-file-clear').toggle(!this.readonly);
-        this.$element.find('input[type=text]').prop('readonly', this.readonly);
+    initialize_content: function() {
+        this._super();
+        if (this.get("effective_readonly")) {
+            var self = this;
+            this.$element.find('a').click(function() {
+                if (self.get('value')) {
+                    self.on_save_as();
+                }
+                return false;
+            });
+        }
     },
-    set_value: function(value) {
+    set_value: function(value_) {
         this._super.apply(this, arguments);
-        var show_value;
-        if (this.node.attrs.filename) {
-            show_value = this.view.datarecord[this.node.attrs.filename] || '';
+        this.render_value();
+    },
+    render_value: function() {
+        if (!this.get("effective_readonly")) {
+            var show_value;
+            if (this.node.attrs.filename) {
+                show_value = this.view.datarecord[this.node.attrs.filename] || '';
+            } else {
+                show_value = (this.get('value') != null && this.get('value') !== false) ? this.get('value') : '';
+            }
+            this.$element.find('input').eq(0).val(show_value);
         } else {
-            show_value = (value != null && value !== false) ? value : '';
+            this.$element.find('a').show(!!this.get('value'));
+            if (this.get('value')) {
+                var show_value = _t("Download") + " " + (this.view.datarecord[this.node.attrs.filename] || '');
+                this.$element.find('a').text(show_value);
+            }
         }
-        this.$element.find('input').eq(0).val(show_value);
     },
     on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
-        this.value = file_base64;
         this.binary_value = true;
+        this.set({'value': file_base64});
         var show_value = name + " (" + this.human_filesize(size) + ")";
         this.$element.find('input').eq(0).val(show_value);
         this.set_filename(name);
     },
+    set_filename: function(value_) {
+        var filename = this.node.attrs.filename;
+        if (this.view.fields[filename]) {
+            this.view.fields[filename].set({value: value_});
+        }
+    },
     on_clear: function() {
         this._super.apply(this, arguments);
         this.$element.find('input').eq(0).val('');
@@ -3436,171 +4309,200 @@ openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({
     }
 });
 
-openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({
+instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({
     template: 'FieldBinaryImage',
-    start: function() {
+    set_value: function(value_) {
         this._super.apply(this, arguments);
-        this.$image = this.$element.find('img.oe-binary-image');
-    },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.$element.find('.oe-binary').toggle(!this.readonly);
-    },
-    set_value: function(value) {
-        this._super.apply(this, arguments);
-        this.set_image_maxwidth();
-        var url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' +
-            this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime());
-        this.$image.attr('src', url);
-    },
-    set_image_maxwidth: function() {
-        this.$image.css('max-width', this.$element.width());
+        this.render_value();
+    },
+    render_value: function() {
+        var url;
+        if (this.get('value') && this.get('value').substr(0, 10).indexOf(' ') == -1) {
+            url = 'data:image/png;base64,' + this.get('value');
+        } else if (this.get('value')) {
+            url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' +
+                this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime());
+        } else {
+            url = "/web/static/src/img/placeholder.png";
+        }
+        var img = QWeb.render("FieldBinaryImage-img", { widget: this, url: url });
+        this.$element.find('> img').remove();
+        this.$element.prepend(img);
     },
     on_file_change: function() {
-        this.set_image_maxwidth();
+        this.render_value();
         this._super.apply(this, arguments);
     },
     on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
-        this.value = file_base64;
+        this.set({'value': file_base64});
         this.binary_value = true;
-        this.$image.attr('src', 'data:' + (content_type || 'image/png') + ';base64,' + file_base64);
-        this.set_filename(name);
+        this.render_value();
     },
     on_clear: function() {
         this._super.apply(this, arguments);
-        this.$image.attr('src', '/web/static/src/img/placeholder.png');
-        this.set_filename('');
+        this.render_value();
     }
 });
 
-openerp.web.form.FieldStatus = openerp.web.form.Field.extend({
-    template: "EmptyComponent",
+instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
+    tagName: "span",
     start: function() {
         this._super();
         this.selected_value = null;
+        // preview in start only for selection fields, because of the dynamic behavior of many2one fields.
+        if (this.field.type in ['selection']) {
+            this.render_list();
+        }
+    },
+    set_value: function(value_) {
+        var self = this;
+        this._super(value_);
+        // find selected value:
+        // - many2one: [2, "New"] -> 2
+        // - selection: new -> new
+        if (this.field.type == "many2one") {
+            this.selected_value = value_[0];
+        } else {
+            this.selected_value = value_;
+        }
+        // trick to be sure all values are loaded in the form, therefore
+        // enabling the evaluation of dynamic domains
+        $.async_when().then(function() {
+            return self.render_list();
+        });
+    },
 
-        this.render_list();
+    /** Get the status list and render them
+     *  to_show: [[identifier, value_to_display]] where
+     *   - identifier = key for a selection, id for a many2one
+     *   - display_val = label that will be displayed
+     *   - ex: [[0, "New"]] (many2one) or [["new", "In Progress"]] (selection)
+     */
+    render_list: function() {
+        var self = this;
+        // get selection values, filter them and render them
+        var selection_done = this.get_selection().pipe(self.proxy('filter_selection')).pipe(self.proxy('render_elements'));
     },
-    set_value: function(value) {
-        this._super(value);
-        this.selected_value = value;
 
-        this.render_list();
+    /** Get the selection list to be displayed in the statusbar widget.
+     *  For selection fields: this is directly given by this.field.selection
+     *  For many2one fields :
+     *  - perform a search on the relation of the many2one field (given by
+     *    field.relation )
+     *  - get the field domain for the search
+     *    - self.build_domain() gives the domain given by the view or by
+     *      the field
+     *    - if the optional statusbar_fold attribute is set to true, make
+     *      an AND with build_domain to hide all 'fold=true' columns
+     *    - make an OR with current value, to be sure it is displayed,
+     *      with the correct order, even if it is folded
+     */
+    get_selection: function() {
+        var self = this;
+        if (this.field.type == "many2one") {
+            this.selection = [];
+            // get fold information from widget
+            var fold = ((this.node.attrs || {}).statusbar_fold || true);
+            // build final domain: if fold option required, add the 
+            if (fold == true) {
+                var domain = new instance.web.CompoundDomain(['|'], ['&'], self.build_domain(), [['fold', '=', false]], [['id', '=', self.selected_value]]);
+            } else {
+                var domain = new instance.web.CompoundDomain(['|'], self.build_domain(), [['id', '=', self.selected_value]]);
+            }
+            // get a DataSetSearch on the current field relation (ex: crm.lead.stage_id -> crm.case.stage)
+            var model_ext = new instance.web.DataSetSearch(this, this.field.relation, self.build_context(), domain);
+            // fetch selection
+            var read_defer = model_ext.read_slice(['name'], {}).pipe( function (records) {
+                _(records).each(function (record) {
+                    self.selection.push([record.id, record.name]);
+                });
+            });
+        } else {
+            this.selection = this.field.selection;
+            var read_defer = new $.Deferred().resolve();
+        }
+        return read_defer;
     },
-    render_list: function() {
+
+    /** Filters this.selection, according to values coming from the statusbar_visible
+     *  attribute of the field. For example: statusbar_visible="draft,open"
+     *  Currently, the key of (key, label) pairs has to be used in the
+     *  selection of visible items. This feature is not meant to be used
+     *  with many2one fields.
+     */
+    filter_selection: function() {
         var self = this;
         var shown = _.map(((this.node.attrs || {}).statusbar_visible || "").split(","),
             function(x) { return _.str.trim(x); });
         shown = _.select(shown, function(x) { return x.length > 0; });
-
+        
         if (shown.length == 0) {
-            this.to_show = this.field.selection;
+            this.to_show = this.selection;
         } else {
-            this.to_show = _.select(this.field.selection, function(x) {
+            this.to_show = _.select(this.selection, function(x) {
                 return _.indexOf(shown, x[0]) !== -1 || x[0] === self.selected_value;
             });
         }
+    },
 
-        var content = openerp.web.qweb.render("FieldStatus.content", {widget: this, _:_});
+    /** Renders the widget. This function also checks for statusbar_colors='{"pending": "blue"}'
+     *  attribute in the widget. This allows to set a given color to a given
+     *  state (given by the key of (key, label)).
+     */
+    render_elements: function () {
+        var content = instance.web.qweb.render("FieldStatus.content", {widget: this, _:_});
         this.$element.html(content);
 
         var colors = JSON.parse((this.node.attrs || {}).statusbar_colors || "{}");
         var color = colors[this.selected_value];
         if (color) {
-            var elem = this.$element.find("li.oe-arrow-list-selected span");
-            elem.css("border-color", color);
-            if (this.check_white(color))
-                elem.css("color", "white");
-            elem = this.$element.find("li.oe-arrow-list-selected .oe-arrow-list-before");
-            elem.css("border-left-color", "transparent");
-            elem = this.$element.find("li.oe-arrow-list-selected .oe-arrow-list-after");
-            elem.css("border-color", "transparent");
-            elem.css("border-left-color", color);
-        }
-    },
-    check_white: function(color) {
-        var div = $("<div></div>");
-        div.css("display", "none");
-        div.css("color", color);
-        div.appendTo($("body"));
-        var ncolor = div.css("color");
-        div.remove();
-        var res = /^\s*rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/.exec(ncolor);
-        if (!res) {
-            return false;
+            var elem = this.$element.find("li.oe_form_steps_active span");
+            elem.css("color", color);
         }
-        var comps = [parseInt(res[1]), parseInt(res[2]), parseInt(res[3])];
-        var lum = comps[0] * 0.3 + comps[1] * 0.59 + comps[1] * 0.11;
-        if (lum < 128) {
-            return true;
-        }
-        return false;
-    }
-});
-
-openerp.web.form.WidgetHtml = openerp.web.form.Widget.extend({
-    render: function () {
-        var $root = $('<div class="oe_form_html_view">');
-        this.render_children(this, $root);
-        return $root.html();
     },
-    render_children: function (object, $into) {
-        var self = this,
-            fields = this.view.fields_view.fields;
-        _(object.children).each(function (child) {
-            if (typeof child === 'string') {
-                $into.text(child);
-            } else if (child.tag === 'field') {
-                $into.append(
-                    new (self.view.registry.get_object('frame'))(
-                        self.view, {tag: 'ueule', attrs: {}, children: [child] })
-                            .render());
-            } else {
-                var $child = $(document.createElement(child.tag))
-                        .attr(child.attrs)
-                        .appendTo($into);
-                self.render_children(child, $child);
-            }
-        });
-    }
 });
 
-
 /**
- * Registry of form widgets, called by :js:`openerp.web.FormView`
+ * Registry of form fields, called by :js:`instance.web.FormView`.
+ *
+ * All referenced classes must implement FieldInterface. Those represent the classes whose instances
+ * will substitute to the <field> tags as defined in OpenERP's views.
  */
-openerp.web.form.widgets = new openerp.web.Registry({
-    'frame' : 'openerp.web.form.WidgetFrame',
-    'group' : 'openerp.web.form.WidgetGroup',
-    'notebook' : 'openerp.web.form.WidgetNotebook',
-    'notebookpage' : 'openerp.web.form.WidgetNotebookPage',
-    'separator' : 'openerp.web.form.WidgetSeparator',
-    'label' : 'openerp.web.form.WidgetLabel',
-    'button' : 'openerp.web.form.WidgetButton',
-    'char' : 'openerp.web.form.FieldChar',
-    'id' : 'openerp.web.form.FieldID',
-    'email' : 'openerp.web.form.FieldEmail',
-    'url' : 'openerp.web.form.FieldUrl',
-    'text' : 'openerp.web.form.FieldText',
-    'date' : 'openerp.web.form.FieldDate',
-    'datetime' : 'openerp.web.form.FieldDatetime',
-    'selection' : 'openerp.web.form.FieldSelection',
-    'many2one' : 'openerp.web.form.FieldMany2One',
-    'many2many' : 'openerp.web.form.FieldMany2Many',
-    'one2many' : 'openerp.web.form.FieldOne2Many',
-    'one2many_list' : 'openerp.web.form.FieldOne2Many',
-    'reference' : 'openerp.web.form.FieldReference',
-    'boolean' : 'openerp.web.form.FieldBoolean',
-    'float' : 'openerp.web.form.FieldFloat',
-    'integer': 'openerp.web.form.FieldFloat',
-    'float_time': 'openerp.web.form.FieldFloat',
-    'progressbar': 'openerp.web.form.FieldProgressBar',
-    'image': 'openerp.web.form.FieldBinaryImage',
-    'binary': 'openerp.web.form.FieldBinaryFile',
-    'statusbar': 'openerp.web.form.FieldStatus',
-    'html': 'openerp.web.form.WidgetHtml'
+instance.web.form.widgets = new instance.web.Registry({
+    'char' : 'instance.web.form.FieldChar',
+    'id' : 'instance.web.form.FieldID',
+    'email' : 'instance.web.form.FieldEmail',
+    'url' : 'instance.web.form.FieldUrl',
+    'text' : 'instance.web.form.FieldText',
+    'date' : 'instance.web.form.FieldDate',
+    'datetime' : 'instance.web.form.FieldDatetime',
+    'selection' : 'instance.web.form.FieldSelection',
+    'many2one' : 'instance.web.form.FieldMany2One',
+    'many2many' : 'instance.web.form.FieldMany2Many',
+    'many2many_tags' : 'instance.web.form.FieldMany2ManyTags',
+    'many2many_kanban' : 'instance.web.form.FieldMany2ManyKanban',
+    'one2many' : 'instance.web.form.FieldOne2Many',
+    'one2many_list' : 'instance.web.form.FieldOne2Many',
+    'reference' : 'instance.web.form.FieldReference',
+    'boolean' : 'instance.web.form.FieldBoolean',
+    'float' : 'instance.web.form.FieldFloat',
+    'integer': 'instance.web.form.FieldFloat',
+    'float_time': 'instance.web.form.FieldFloat',
+    'progressbar': 'instance.web.form.FieldProgressBar',
+    'image': 'instance.web.form.FieldBinaryImage',
+    'binary': 'instance.web.form.FieldBinaryFile',
+    'statusbar': 'instance.web.form.FieldStatus'
 });
 
+/**
+ * Registry of widgets usable in the form view that can substitute to any possible
+ * tags defined in OpenERP's form views.
+ *
+ * Every referenced class should extend FormWidget.
+ */
+instance.web.form.tags = new instance.web.Registry({
+    'button' : 'instance.web.form.WidgetButton',
+});
 
 };