[merge]
[odoo/odoo.git] / addons / web / static / src / js / view_form.js
index b7ca275..e517e68 100644 (file)
@@ -11,7 +11,6 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
      * view should be displayed (if there is one active).
      */
     searchable: false,
-    readonly : false,
     template: "FormView",
     display_name: _lt('Form'),
     /**
@@ -34,8 +33,6 @@ 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 = {};
@@ -53,6 +50,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         this.on_change_mutex = new $.Mutex();
         this.reload_mutex = new $.Mutex();
         this.set({"force_readonly": false});
+        this.rendering_engine = new openerp.web.FormRenderingEngine(this);
     },
     start: function() {
         this._super();
@@ -80,7 +78,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             this.sidebar.attachments.destroy();
             this.sidebar.destroy();
         }
-        _.each(this.widgets, function(w) {
+        _.each(this.get_widgets(), function(w) {
             w.destroy();
         });
         this._super();
@@ -100,8 +98,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         this.fields_order = [];
         this.fields_view = data;
 
-        var form = new openerp.web.FormRenderingEngine(this, data);
-        form.appendTo(this.$element.find('.oe_form_content'));
+        this.rendering_engine.set_fields_view(data);
+        this.rendering_engine.render_to(this.$element.find('.oe_form_container'));
 
         this.$form_header = this.$element.find('.oe_form_header:first');
         this.$form_header.find('div.oe_form_pager button[data-pager-action]').click(function() {
@@ -217,14 +215,13 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         });
     },
     on_form_changed: function() {
-        for (var w in this.widgets) {
-            w = this.widgets[w];
+        _.each(this.get_widgets(), function(w) {
             w.process_modifiers();
             if (w.field) {
                 w.validate();
             }
             w.update_dom();
-        }
+        });
     },
     do_notify_change: function() {
         this.$element.addClass('oe_form_dirty');
@@ -292,7 +289,7 @@ 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();
+                var value = self.fields[field].get_value();
                 return value == null ? false : value;
             }
             // parent field
@@ -362,7 +359,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                         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;
 
@@ -499,7 +496,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                     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.is_dirty())) {
                     // Special case 'id' field, do not save this field
                     // on 'create' : save all non readonly fields
                     // on 'edit' : save non readonly modified fields
@@ -603,6 +600,11 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             }
         });
     },
+    get_widgets: function() {
+        return _.filter(this.getChildren(), function(obj) {
+            return obj instanceof openerp.web.form.Widget;
+        });
+    },
     get_fields_values: function(blacklist) {
        blacklist = blacklist || [];
         var values = {};
@@ -655,7 +657,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                 // or m2m
                 if (!value
                         || field.invisible
-                        || field.readonly
+                        || field.get("readonly")
                         || field.field.type === 'one2many'
                         || field.field.type === 'many2many') {
                     return false;
@@ -724,37 +726,45 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
     }
 });
 
-openerp.web.FormRenderingEngine = openerp.web.Widget.extend({
-    init: function(parent, fvg, registry) {
-        var self = this;
-        this._super.apply(this, arguments);
+/**
+ * Interface to be implemented by rendering engines for the form view.
+ */
+openerp.web.FormRenderingEngineInterface = {
+    set_fields_view: function(fields_view) {},
+    render_to: function($element) {},
+};
+
+/**
+ * Default rendering engine for the form view.
+ * 
+ * It is necessary to set the view using set_view() before usage.
+ */
+openerp.web.FormRenderingEngine = openerp.web.Class.extend({
+    init: function(view) {
+        this.view = view;
+        this.legacy_mode = false;
+    },
+    set_fields_view: function(fvg) {
         this.fvg = fvg;
-        this.view = parent;
-        this.fields_prefix = this.view.dataset ? this.view.dataset.model : '';
+        this.legacy_mode = (this.fvg.arch.tag === 'form');
+    },
+    set_registry: function(registry) {
+        this.registry = registry;
+    },
+    render_to: function($element) {
+        var self = this;
+        this.$element = $element;
 
         // 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 = openerp.web.json_node_to_xml(fvg.arch),
-            $form = this.$form = $(xml);
+        var xml = openerp.web.json_node_to_xml(this.fvg.arch);
+        this.$form = $(xml);
 
-        // TODO: extract embeded views before preprocessing
-        _.each(['field', 'group', 'notebook', 'separator', 'label'], function(tag) {
-            var fn = self['process_' + tag];
-            if (registry && registry.contains(tag)) {
-                fn = registry.get_object(tag);
-            }
-            $form.find(tag).each(function() {
-                fn.call(self, $(this), $form);
-            });
-        });
-    },
-    start: function() {
-        var self = this;
-        this._super.apply(this, arguments);
-        this.$form.children().appendTo(this.$element);
+        this.process(this.$form);
+
+        this.$form.appendTo(this.$element);
         // OpenERP views spec :
         //      - @width is obsolete ?
-        // TODO: modifiers invisible. Add a special attribute, eg: data-invisible  that should be used in order to create openerp.form.InvisibleWidgetG
 
         this.$element.find('field, button').each(function() {
             var $elem = $(this),
@@ -762,45 +772,112 @@ openerp.web.FormRenderingEngine = openerp.web.Widget.extend({
             if (self.view.registry.contains(key)) {
                 var obj = self.view.registry.get_object(key);
                 var w = new (obj)(self.view, openerp.web.xml_to_json($elem[0]));
+                self.alter_field(w);
                 w.replace($elem);
             }
         });
+        $('<button>Debug layout</button>').appendTo(this.$element).click($.proxy(this.toggle_layout_debugging, this));
+    },
+    render_element: function(template, dict) {
+        dict = dict || {};
+        dict.legacy_mode = this.legacy_mode;
+        return $(QWeb.render(template, dict));
+    },
+    alter_field: function(field) {},
+    toggle_layout_debugging: function() {
+        if (!this.$element.has('.oe_layout_debug_cell:first').length) {
+            this.$element.find('.oe_form_group_cell').each(function() {
+                var text = 'W:' + ($(this).attr('width') || '') + ' - C:' + $(this).attr('colspan'),
+                    $span = $('<span class="oe_layout_debug_cell"/>').text(text);
+                $span.prependTo($(this));
+            });
+        }
+        this.$element.toggleClass('oe_layout_debugging');
+
+    },
+    process: function($tag) {
+        var self = this;
+        var tagname = $tag[0].nodeName.toLowerCase();
+        var fn = self['process_' + tagname]; 
+        if (this.registry && this.registry.contains(tagname)) {
+            fn = this.registry.get_object(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));
+            });
+            return $tag;
+        }
     },
-    process_field: function($field, $form) {
+    process_form: function($form) {
+        var $new_form = this.render_element('FormRenderingForm', $form.getAttributes());
+            $dst = this.legacy_mode ? $new_form.find('group:first') : $new_form;
+        $form.children().appendTo($dst);
+        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);
+    },
+    preprocess_field: function($field) {
         var name = $field.attr('name'),
             field_orm = this.fvg.fields[name],
             field_string = $field.attr('string') || field_orm.string || '',
             field_help = $field.attr('help') || field_orm.help || '',
             field_colspan = parseInt($field.attr('colspan'), 10);
-
-        if (!field_orm) {
-            throw new Error("Field '" + name + "' specified in view could not be found.");
-        }
-
         if ($field.attr('nolabel') !== '1') {
-            var $label = $form.find('label[for="' + name + '"]');
+            $field.attr('nolabel', '1');
+            var $label = this.$form.find('label[for="' + name + '"]');
             if (!$label.length) {
                 field_string = $label.attr('string') || $label.text() || field_string;
                 field_help = $label.attr('help') || field_help;
-                $('<label/>').attr({
+                $label = $('<label/>').attr({
                     'for' : name,
                     'string' : field_string,
                     'help' :  field_help
-                }).insertBefore($field).text();
+                });
+                $label.insertBefore($field);
                 if (field_colspan > 1) {
                     $field.attr('colspan', field_colspan - 1);
                 }
             }
+            return $label;
+        }
+    },
+    process_field: function($field) {
+        var $label = this.preprocess_field($field);
+        if ($label)
+            this.process($label);
+        var name = $field.attr('name'),
+            field_orm = this.fvg.fields[name],
+            field_string = $field.attr('string') || field_orm.string || '',
+            field_help = $field.attr('help') || field_orm.help || '',
+            field_colspan = parseInt($field.attr('colspan'), 10);
+
+        if (!field_orm) {
+            throw new Error("Field '" + name + "' specified in view could not be found.");
         }
+
         $field.attr({
             'widget' : $field.attr('widget') || field_orm.type,
             'string' : field_string,
             'help' : field_help
         });
+        return $field;
     },
-    process_group: function($group, $form) {
-        var self = this,
-            $new_group = $(QWeb.render('FormRenderingGroup', $group.getAttributes())),
+    process_group: function($group) {
+        var self = this;
+        $group.children('field').each(function() {
+            self.preprocess_field($(this));
+        });
+        var $new_group = $(QWeb.render('FormRenderingGroup', $group.getAttributes())),
             $table;
         if ($new_group.is('table')) {
             $table = $new_group;
@@ -808,11 +885,12 @@ openerp.web.FormRenderingEngine = openerp.web.Widget.extend({
             $table = $new_group.find('table:first');
         }
         $table.addClass('oe_form_group');
-        var $tr,
+        var $tr, $td,
             cols = parseInt($group.attr('col') || 4, 10),
             row_cols = cols;
 
-        $group.children().each(function() {
+        var children = [];
+        $group.children().each(function(a,b,c) {
             var $child = $(this),
                 colspan = parseInt($child.attr('colspan') || 1, 10),
                 tagName = $child[0].tagName.toLowerCase();
@@ -825,14 +903,19 @@ openerp.web.FormRenderingEngine = openerp.web.Widget.extend({
                 row_cols = cols;
             }
             row_cols -= colspan;
-            var $td = $('<td/>').addClass('oe_form_group_cell').attr('colspan', colspan);
+            $td = $('<td/>').addClass('oe_form_group_cell').attr('colspan', colspan);
             $tr.append($td.append($child));
+            children.push($child[0]);
         });
+        if (row_cols) {
+            $td.attr('colspan', parseInt($td.attr('colspan'), 10) + row_cols);
+        }
         $group.before($new_group).remove();
 
         // Now compute width of cells
         $table.find('tbody > tr').each(function() {
             var to_compute = [],
+                row_cols = cols,
                 total = 100;
             $(this).children().each(function() {
                 var $td = $(this),
@@ -842,13 +925,13 @@ openerp.web.FormRenderingEngine = openerp.web.Widget.extend({
                         if ($child.attr('orientation') === 'vertical') {
                             $td.addClass('oe_vertical_separator').attr('width', '1');
                             $td.empty();
-                            cols--;
+                            row_cols--;
                         }
                         break;
                     case 'label':
                         if ($child.attr('for')) {
                             $td.attr('width', '1%');
-                            cols--;
+                            row_cols--;
                             total--;
                         }
                         break;
@@ -856,35 +939,46 @@ openerp.web.FormRenderingEngine = openerp.web.Widget.extend({
                         to_compute.push($td);
                 }
             });
-            var unit = Math.floor(total / cols);
+            var unit = Math.floor(total / row_cols);
             _.each(to_compute, function($td, i) {
                 var width = parseInt($td.attr('colspan'), 10) * unit;
                 $td.attr('width', ((i == to_compute.length - 1) ? total : width) + '%');
                 total -= width;
             });
         });
+        _.each(children, function(el) {
+            self.process($(el));
+        });
+        return $new_group;
     },
-    process_notebook: function($notebook, $form) {
+    process_notebook: function($notebook) {
+        var self = this;
         var pages = [];
         $notebook.find('> page').each(function() {
             var $page = $(this),
                 page_attrs = $page.getAttributes();
             page_attrs.id = _.uniqueId('notebook_page_');
             pages.push(page_attrs);
-            var $new_page = $(QWeb.render('FormRenderingNotebookPage', page_attrs));
-            $page.children().appendTo($new_page);
+            var $new_page = self.render_element('FormRenderingNotebookPage', page_attrs),
+                $dst = self.legacy_mode ? $new_page.find('group:first') : $new_page;
+            $page.children().appendTo($dst);
             $page.before($new_page).remove();
         });
         var $new_notebook = $(QWeb.render('FormRenderingNotebook', { pages : pages }));
         $notebook.children().appendTo($new_notebook);
         $notebook.before($new_notebook).remove();
+        $new_notebook.children().each(function() {
+            self.process($(this));
+        });
         $new_notebook.tabs();
+        return $new_notebook;
     },
-    process_separator: function($separator, $form) {
+    process_separator: function($separator) {
         var $new_separator = $(QWeb.render('FormRenderingSeparator', $separator.getAttributes()));
         $separator.before($new_separator).remove();
+        return $new_separator;
     },
-    process_label: function($label, $form) {
+    process_label: function($label) {
         var dict = $label.getAttributes();
         var align = parseFloat(dict.align);
         if (isNaN(align) || align === 1) {
@@ -897,6 +991,7 @@ openerp.web.FormRenderingEngine = openerp.web.Widget.extend({
         dict.align = align;
         var $new_label = $(QWeb.render('FormRenderingLabel', dict));
         $label.before($new_label).remove();
+        return $new_label;
     }
 });
 
@@ -1081,9 +1176,13 @@ openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form.
     },
     process_modifiers: function() {
         var compute_domain = openerp.web.form.compute_domain;
+        var to_set = {};
         for (var a in this.modifiers) {
-            this[a] = compute_domain(this.modifiers[a], this.view.fields);
+            var val = compute_domain(this.modifiers[a], this.view.fields);
+            this[a] = val;
+            to_set[a] = val;
         }
+        this.set(to_set);
     },
     update_dom: function() {
         this.$element.toggle(!this.invisible);
@@ -1241,7 +1340,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
         this.check_disable();
     },
     check_disable: function() {
-        var disabled = (this.readonly || this.force_disabled || !this.view.is_interactible_record());
+        var disabled = (this.force_disabled || !this.view.is_interactible_record());
         this.$element.prop('disabled', disabled);
         this.$element.css('color', disabled ? 'grey' : '');
     }
@@ -1252,8 +1351,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
  * able to provide the features necessary for the fields to work.
  * 
  * Properties:
- *     - force_readonly: boolean, When it is true, all the fields should always appear
- *      in read only mode, no matter what the value of their "readonly" property can be.
+ *     - ...
  */
 
 openerp.web.form.FieldManagerInterface = {
@@ -1265,7 +1363,8 @@ openerp.web.form.FieldManagerInterface = {
  * 
  * 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:
  *     - ...
  * 
@@ -1333,7 +1432,7 @@ openerp.web.form.AbstractField = openerp.web.form.Widget.extend(/** @lends opene
     init: function(field_manager, node) {
         this._super(field_manager, node);
         this.name = this.node.attrs.name;
-        this.value = undefined;
+        this.value = false;
         this.view.fields[this.name] = this;
         this.view.fields_order.push(this.name);
         this.type = this.node.attrs.widget;
@@ -1341,19 +1440,17 @@ openerp.web.form.AbstractField = openerp.web.form.Widget.extend(/** @lends opene
         this.required = this.modifiers['required'] === true;
         this.invalid = this.dirty = false;
         
-        // because I'm lazy to refactor right now
-        this.on("change:readonly", this, function() {this.readonly = this.get("readonly");});
-        
         // 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.view.get("force_readonly")});
+            this.set({"effective_readonly": this.get("readonly") || this.get("force_readonly")});
         };
-        this.view.on("change:readonly", this, test_effective_readonly);
-        this.view.on("change:force_readonly", this, test_effective_readonly);
+        this.on("change:readonly", this, test_effective_readonly);
+        this.on("change:force_readonly", this, test_effective_readonly);
         _.bind(test_effective_readonly, this)();
 
+        // TODO: do something good about this
         if (this.view) {
             this.$label = this.view.$element.find('label[for="' + this.name + '"]');
             if (this.$label.length) {
@@ -1396,10 +1493,7 @@ openerp.web.form.AbstractField = openerp.web.form.Widget.extend(/** @lends opene
         return !this.invalid;
     },
     is_dirty: function() {
-        return this.dirty && !this.readonly;
-    },
-    get_on_change_value: function() {
-        return this.get_value();
+        return this.dirty && !this.get("effective_readonly");
     },
     update_dom: function(show_invalid) {
         this._super.apply(this, arguments);
@@ -1407,14 +1501,12 @@ openerp.web.form.AbstractField = openerp.web.form.Widget.extend(/** @lends opene
             this.$element.find('.oe_field_translate').toggle(!!this.view.datarecord.id);
         }
         if (!this.disable_utility_classes) {
-            this.$element.toggleClass('disabled', this.readonly);
+            this.$element.toggleClass('disabled', this.get("effective_readonly"));
             this.$element.toggleClass('required', this.required);
             if (show_invalid) {
                 this.$element.toggleClass('invalid', !this.is_valid());
             }
         }
-        // one more shit code to avoid refactoring this.readonly right now
-        this.set({"readonly": this.readonly});
     },
     on_ui_change: function() {
         this.dirty = true;
@@ -1450,22 +1542,50 @@ openerp.web.form.AbstractField = openerp.web.form.Widget.extend(/** @lends opene
     }
 });
 
-openerp.web.form.FieldChar = openerp.web.form.AbstractField.extend({
-    template: 'FieldChar',
-    init: function (view, node) {
-        this._super(view, node);
-        this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1';
-    },
+/**
+ * A mixin to apply on any field that has to completely re-render when its readonly state
+ * switch.
+ */
+openerp.web.form.ReinitializeFieldMixin =  {
+    /**
+     * Default implementation of start(), use it or call explicitly initialize_field().
+     */
     start: function() {
-        this._super.apply(this, arguments);
-        this.bind_events();
+        this._super();
+        this.initialize_field();
+    },
+    initialize_field: function() {
         this.on("change:effective_readonly", this, function() {
+            this.destroy_content();
             this.renderElement();
-            this.bind_events();
+            this.initialize_content();
             this.render_value();
         });
+        this.initialize_content();
+        this.render_value();
     },
-    bind_events: function() {
+    /**
+     * 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() {},
+};
+
+openerp.web.form.FieldChar = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
+    template: 'FieldChar',
+    init: function (view, node) {
+        this._super(view, node);
+        this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1';
+    },
+    initialize_content: function() {
         this.$element.find('input').change(this.on_ui_change);
     },
     set_value: function(value) {
@@ -1489,28 +1609,27 @@ openerp.web.form.FieldChar = openerp.web.form.AbstractField.extend({
     },
     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;
+        if (!this.get("effective_readonly")) {
+            try {
+                var value = openerp.web.parse_value(this.$element.find('input').val(), this, '');
+                this.invalid = this.required && value === '';
+            } catch(e) {
+                this.invalid = true;
+            }
         }
     },
     focus: function($element) {
         this._super($element || 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);
-    }
+    
 });
 
 openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({
     template: 'FieldEmail',
-    bind_events: function() {
+    initialize_content: function() {
         this._super();
         this.$element.find('button').click(this.on_button_clicked);
     },
@@ -1534,7 +1653,7 @@ openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({
 
 openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({
     template: 'FieldUrl',
-    bind_events: function() {
+    initialize_content: function() {
         this._super();
         this.$element.find('button').click(this.on_button_clicked);
     },
@@ -1562,17 +1681,13 @@ openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({
 openerp.web.form.FieldFloat = openerp.web.form.FieldChar.extend({
     init: function (view, node) {
         this._super(view, node);
+        this.value = 0;
         if (this.node.attrs.digits) {
-            this.parse_digits(this.node.attrs.digits);
+            this.digits = py.eval(this.node.attrs.digits).toJSON();
         } else {
             this.digits = this.field.digits;
         }
     },
-    parse_digits: function (digits_attr) {
-        // could use a Python parser instead.
-        var match = /^\s*[\(\[](\d+),\s*(\d+)/.exec(digits_attr);
-        return [parseInt(match[1], 10), parseInt(match[2], 10)];
-    },
     set_value: function(value) {
         if (value === false || value === undefined) {
             // As in GTK client, floats default to 0
@@ -1603,7 +1718,7 @@ 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.picker('setDate', self.value ? openerp.web.auto_str_to_date(self.value) : new Date());
                 self.$input_picker.show();
                 self.picker('show');
@@ -1667,36 +1782,49 @@ openerp.web.DateWidget = openerp.web.DateTimeWidget.extend({
     type_of_date: "date"
 });
 
-openerp.web.form.FieldDatetime = openerp.web.form.AbstractField.extend({
+openerp.web.form.FieldDatetime = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
     template: "EmptyComponent",
     build_widget: function() {
         return new openerp.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);
+    destroy_content: function() {
+        if (this.datewidget) {
+            this.datewidget.destroy();
+            this.datewidget = undefined;
+        }
+    },
+    initialize_content: function() {
+        if (!this.get("effective_readonly")) {
+            this.datewidget = this.build_widget();
+            this.datewidget.on_change.add_last(this.on_ui_change);
+            this.datewidget.appendTo(this.$element);
+        }
     },
     set_value: function(value) {
         this._super(value);
-        this.datewidget.set_value(value);
+        this.render_value();
     },
-    get_value: function() {
-        return this.datewidget.get_value();
+    render_value: function() {
+        if (!this.get("effective_readonly")) {
+            this.datewidget.set_value(this.value);
+        } else {
+            this.$element.text(openerp.web.format_value(this.value, this, ''));
+        }
     },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.datewidget.set_readonly(this.readonly);
+    get_value: function() {
+        if (!this.get("effective_readonly")) {
+            return this.datewidget.get_value();
+        } else {
+            return this.value;
+        }
     },
     validate: function() {
-        this.invalid = !this.datewidget.is_valid(this.required);
+        this.invalid = this.get("effective_readonly") || !this.datewidget.is_valid(this.required);
     },
     focus: function($element) {
-        this._super($element || this.datewidget.$input);
+        this._super($element || (this.datewidget && this.datewidget.$input));
     }
-});
+}));
 
 openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({
     build_widget: function() {
@@ -1704,25 +1832,30 @@ openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({
     }
 });
 
-openerp.web.form.FieldText = openerp.web.form.AbstractField.extend({
+openerp.web.form.FieldText = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
     template: 'FieldText',
-    start: function() {
-        this._super.apply(this, arguments);
-        this.$textarea = this.$element.find('textarea').change(this.on_ui_change);
-        this.resized = false;
+    initialize_content: function() {
+        this.$textarea = undefined;
+        if (!this.get("effective_readonly")) {
+            this.$textarea = this.$element.find('textarea').change(this.on_ui_change);
+            this.resized = false;
+        }
     },
     set_value: function(value) {
         this._super.apply(this, arguments);
-        var show_value = openerp.web.format_value(value, this, '');
-        this.$textarea.val(show_value);
-        if (!this.resized && this.view.options.resize_textareas) {
-            this.do_resize(this.view.options.resize_textareas);
-            this.resized = true;
-        }
+        this.render_value();
     },
-    update_dom: function() {
-        this._super.apply(this, arguments);
-        this.$textarea.prop('readonly', this.readonly);
+    render_value: function() {
+        var show_value = openerp.web.format_value(this.value, this, '');
+        if (!this.get("effective_readonly")) {
+            this.$textarea.val(show_value);
+            if (!this.resized && this.view.options.resize_textareas) {
+                this.do_resize(this.view.options.resize_textareas);
+                this.resized = true;
+            }
+        } else {
+            this.$element.text(show_value);
+        }
     },
     set_value_from_ui: function() {
         this.value = openerp.web.parse_value(this.$textarea.val(), this);
@@ -1730,11 +1863,13 @@ openerp.web.form.FieldText = openerp.web.form.AbstractField.extend({
     },
     validate: function() {
         this.invalid = false;
-        try {
-            var value = openerp.web.parse_value(this.$textarea.val(), this, '');
-            this.invalid = this.required && value === '';
-        } catch(e) {
-            this.invalid = true;
+        if (!this.get("effective_readonly")) {
+            try {
+                var value = openerp.web.parse_value(this.$textarea.val(), this, '');
+                this.invalid = this.required && value === '';
+            } catch(e) {
+                this.invalid = true;
+            }
         }
     },
     focus: function($element) {
@@ -1763,7 +1898,7 @@ openerp.web.form.FieldText = openerp.web.form.AbstractField.extend({
     reset: function() {
         this.resized = false;
     }
-});
+}));
 
 openerp.web.form.FieldBoolean = openerp.web.form.AbstractField.extend({
     template: 'FieldBoolean',
@@ -1796,7 +1931,7 @@ openerp.web.form.FieldProgressBar = openerp.web.form.AbstractField.extend({
         this._super.apply(this, arguments);
         this.$element.progressbar({
             value: this.value,
-            disabled: this.readonly
+            disabled: this.get("effective_readonly")
         });
     },
     set_value: function(value) {
@@ -1814,7 +1949,7 @@ openerp.web.form.FieldTextXml = openerp.web.form.AbstractField.extend({
 // to replace view editor
 });
 
-openerp.web.form.FieldSelection = openerp.web.form.AbstractField.extend({
+openerp.web.form.FieldSelection = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
     template: 'FieldSelection',
     init: function(view, node) {
         var self = this;
@@ -1827,16 +1962,7 @@ openerp.web.form.FieldSelection = openerp.web.form.AbstractField.extend({
         });
         this.values.unshift([false, '']);
     },
-    start: function() {
-        this._super.apply(this, arguments);
-        this.bind_events();
-        this.on("change:effective_readonly", this, function() {
-            this.renderElement();
-            this.bind_events();
-            this.render_value();
-        });
-    },
-    bind_events: 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
@@ -1894,7 +2020,7 @@ openerp.web.form.FieldSelection = openerp.web.form.AbstractField.extend({
     focus: function($element) {
         this._super($element || this.$element.find('select:first'));
     }
-});
+}));
 
 // jquery autocomplete tweak to allow html
 (function() {
@@ -1938,8 +2064,8 @@ openerp.web.form.dialog = function(content, options) {
     return dialog.$element;
 };
 
-openerp.web.form.FieldMany2One = openerp.web.form.AbstractField.extend({
-    template: 'EmptyComponent',
+openerp.web.form.FieldMany2One = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
+    template: "FieldMany2One",
     init: function(view, node) {
         this._super(view, node);
         this.limit = 7;
@@ -1948,23 +2074,12 @@ openerp.web.form.FieldMany2One = openerp.web.form.AbstractField.extend({
         this.last_search = [];
         this.tmp_value = undefined;
     },
-    start: function() {
-        this._super();
-        this.render_content();
-        this.on("change:effective_readonly", this, function() {
-            this.render_content();
-        });
-    },
-    render_content: function() {
-        this.$element.html("");
+    initialize_content: function() {
         if (!this.get("effective_readonly"))
             this.render_editable();
-        else
-            this.render_readonly();
         this.render_value();
     },
     render_editable: function() {
-        this.$element.html(QWeb.render("FieldMany2One", {widget: this}));
         var self = this;
         this.$input = this.$element.find("input");
         this.$drop_down = this.$element.find(".oe-m2o-drop-down-button");
@@ -2103,9 +2218,6 @@ openerp.web.form.FieldMany2One = openerp.web.form.AbstractField.extend({
             isSelecting = false;
         });
     },
-    render_readonly: function() {
-        this.$element.html(QWeb.render("FieldMany2One_readonly"));
-    },
     // autocomplete component content handling
     get_search_result: function(request, response) {
         var search_val = request.term;
@@ -2300,8 +2412,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.AbstractField.extend({
     focus: function ($element) {
         this._super($element || this.$input);
     }
-});
-openerp.web.check_interface(openerp.web.form.FieldMany2One, openerp.web.form.FieldInterface);
+}));
 
 /*
 # Values: (0, 0,  { fields })    create
@@ -2349,7 +2460,6 @@ var commands = {
     }
 };
 openerp.web.form.FieldOne2Many = openerp.web.form.AbstractField.extend({
-    template: 'FieldOne2Many',
     multi_selection: false,
     init: function(view, node) {
         this._super(view, node);
@@ -2718,11 +2828,9 @@ openerp.web.form.One2ManyFormView = openerp.web.FormView.extend({
  * TODO niv: clean those deferred stuff, it could be better
  */
 openerp.web.form.FieldMany2Many = openerp.web.form.AbstractField.extend({
-    template: 'FieldMany2Many',
     multi_selection: false,
     init: function(view, node) {
         this._super(view, node);
-        this.list_id = _.uniqueId("many2many");
         this.is_loaded = $.Deferred();
         this.initial_is_loaded = this.is_loaded;
         this.is_setted = $.Deferred();
@@ -2788,7 +2896,7 @@ openerp.web.form.FieldMany2Many = openerp.web.form.AbstractField.extend({
             loaded.resolve();
         });
         $.async_when().then(function () {
-            self.list_view.appendTo($("#" + self.list_id));
+            self.list_view.appendTo(self.$element);
         });
         return loaded;
     },
@@ -2824,9 +2932,9 @@ openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends open
         );
         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]));
+            _.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.on_ui_change();
                     self.reload_content();
                 }
@@ -2919,6 +3027,10 @@ 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) {
@@ -2942,7 +3054,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
                         'selectable': !self.options.disable_multiple_selection
                     }, 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();
@@ -2964,7 +3076,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
                 });
             });
         });
-        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;
@@ -3013,7 +3125,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
         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.appendTo(this.$element.find(".oe-select-create-popup-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}));
@@ -3124,7 +3236,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
         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.appendTo(this.$element.find(".oe-form-open-popup-form-view"));
         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"));
@@ -3158,7 +3270,7 @@ openerp.web.form.FormOpenDataset = openerp.web.ProxyDataSet.extend({
     }
 });
 
-openerp.web.form.FieldReference = openerp.web.form.AbstractField.extend({
+openerp.web.form.FieldReference = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
     template: 'FieldReference',
     init: function(view, node) {
         this._super(view, node);
@@ -3176,22 +3288,10 @@ openerp.web.form.FieldReference = openerp.web.form.AbstractField.extend({
         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'
-        }});
         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() {
     },
@@ -3203,12 +3303,34 @@ openerp.web.form.FieldReference = openerp.web.form.AbstractField.extend({
             this.m2o.$element.toggle(sel !== false);
         }
     },
-    start: function() {
-        this._super();
-        // TODO: not niv compliant
-        this.selection.$element = $(".oe_form_view_reference_selection", this.$element);
-        this.selection.renderElement();
-        this.selection.start();
+    destroy_content: function() {
+        if (this.selection) {
+            this.selection.destroy();
+            this.selection = undefined;
+        }
+        if (this.m2o) {
+            this.m2o.destroy();
+            this.m2o.undefined;
+        }
+    },
+    initialize_content: function() {
+        if (!this.get("effective_readonly")) {
+            this.selection = new openerp.web.form.FieldSelection(this, { attrs: {
+                name: 'selection',
+                widget: 'selection'
+            }});
+            this.selection.on_value_changed.add_last(this.on_selection_changed);
+            
+            this.selection.$element = $(".oe_form_view_reference_selection", this.$element);
+            this.selection.renderElement();
+            this.selection.start();
+        }
+        this.m2o = new openerp.web.form.FieldMany2One(this, { attrs: {
+            name: 'm2o',
+            widget: 'many2one'
+        }});
+        this.m2o.set({"readonly": this.get("effective_readonly")});
+        this.m2o.on_ui_change.add_last(this.on_ui_change);
         this.m2o.$element = $(".oe_form_view_reference_m2o", this.$element);
         this.m2o.renderElement();
         this.m2o.start();
@@ -3221,14 +3343,19 @@ openerp.web.form.FieldReference = openerp.web.form.AbstractField.extend({
     },
     set_value: function(value) {
         this._super(value);
+        this.render_value();
+    },
+    render_value: function() {
         this.reference_ready = false;
         var vals = [], sel_val, m2o_val;
-        if (typeof(value) === 'string') {
-            vals = value.split(',');
+        if (typeof(this.value) === 'string') {
+            vals = this.value.split(',');
         }
         sel_val = vals[0] || false;
         m2o_val = vals[1] ? parseInt(vals[1], 10) : false;
-        this.selection.set_value(sel_val);
+        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);
@@ -3243,16 +3370,15 @@ openerp.web.form.FieldReference = openerp.web.form.AbstractField.extend({
             return false;
         }
     }
-});
+}));
 
-openerp.web.form.FieldBinary = openerp.web.form.AbstractField.extend({
+openerp.web.form.FieldBinary = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
     init: function(view, node) {
         this._super(view, node);
         this.iframe = this.element_id + '_iframe';
         this.binary_value = false;
     },
-    start: function() {
-        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('.oe-binary-file-clear').click(this.on_clear);
@@ -3316,24 +3442,42 @@ openerp.web.form.FieldBinary = openerp.web.form.AbstractField.extend({
         }
         return false;
     }
-});
+}));
 
 openerp.web.form.FieldBinaryFile = openerp.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.value) {
+                    self.on_save_as();
+                }
+                return false;
+            });
+        }
     },
     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.value != null && this.value !== false) ? this.value : '';
+            }
+            this.$element.find('input').eq(0).val(show_value);
         } else {
-            show_value = (value != null && value !== false) ? value : '';
+            this.$element.find('a').show(!!this.value);
+            if (this.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;
@@ -3358,42 +3502,43 @@ openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({
 
 openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({
     template: 'FieldBinaryImage',
-    start: function() {
-        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);
+    initialize_content: function() {
+        this._super();
+        this.$placeholder = $(".oe_form_field-binary-image-placeholder", this.$element);
+        if (!this.get("effective_readonly"))
+            this.$element.find('.oe-binary').show();
+        else
+            this.$element.find('.oe-binary').hide();
     },
     set_value: function(value) {
         this._super.apply(this, arguments);
-        this.set_image_maxwidth();
-
+        this.render_value();
+    },
+    render_value: function() {
         var url;
-        if (value && value.substr(0, 10).indexOf(' ') == -1) {
+        if (this.value && this.value.substr(0, 10).indexOf(' ') == -1) {
             url = 'data:image/png;base64,' + this.value;
-        } else {
+        } else if (this.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";
         }
-        this.$image.attr('src', url);
-    },
-    set_image_maxwidth: function() {
-        this.$image.css('max-width', this.$element.width());
+        var rendered = QWeb.render("FieldBinaryImage-img", {widget: this, url: url});;
+        this.$placeholder.html(rendered);
     },
     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.binary_value = true;
-        this.$image.attr('src', 'data:' + (content_type || 'image/png') + ';base64,' + file_base64);
+        this.render_value();
     },
     on_clear: function() {
         this._super.apply(this, arguments);
-        this.$image.attr('src', '/web/static/src/img/placeholder.png');
+        this.render_value();
     }
 });