[IMP] merge title of o2m list view into list header
[odoo/odoo.git] / addons / web / static / src / js / view_form.js
index cf5d98b..3963ebc 100644 (file)
@@ -10,6 +10,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
      * view should be displayed (if there is one active).
      */
     searchable: false,
+    readonly : false,
     form_template: "FormView",
     identifier_prefix: 'formview-',
     /**
@@ -32,17 +33,18 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         this.widgets = {};
         this.widgets_counter = 0;
         this.fields = {};
+        this.fields_order = [];
         this.datarecord = {};
         this.show_invalid = true;
-        this.dirty_for_user = false;
         this.default_focus_field = null;
         this.default_focus_button = null;
         this.registry = openerp.web.form.widgets;
         this.has_been_loaded = $.Deferred();
         this.$form_header = null;
         this.translatable_fields = [];
-        _.defaults(this.options, {"always_show_new_button": true,
-            "not_interactible_on_create": false});
+        _.defaults(this.options, {
+            "not_interactible_on_create": false
+        });
         this.mutating_lock = $.Deferred();
         this.initial_mutating_lock = this.mutating_lock;
         this.on_change_lock = $.Deferred().resolve();
@@ -86,10 +88,11 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
     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);
 
-            this.rendered = QWeb.render(this.form_template, { 'frame': frame, 'view': this });
+            this.rendered = QWeb.render(this.form_template, { 'frame': frame, 'widget': this });
         }
         this.$element.html(this.rendered);
         _.each(this.widgets, function(w) {
@@ -101,23 +104,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             self.on_pager_action(action);
         });
 
-        this.$form_header.find('button.oe_form_button_save').click(this.do_save);
-        this.$form_header.find('button.oe_form_button_cancel').click(this.do_cancel);
-        this.$form_header.find('button.oe_form_button_new').click(this.on_button_new);
-        this.$form_header.find('button.oe_form_button_duplicate').click(this.on_button_duplicate);
-        this.$form_header.find('button.oe_form_button_toggle').click(function () {
-            self.translatable_fields = [];
-            self.widgets = {};
-            self.fields = {};
-            self.$form_header.find('button').unbind('click');
-            self.registry = self.registry === openerp.web.form.widgets
-                    ? openerp.web.form.readonly
-                    : openerp.web.form.widgets;
-            self.on_loaded(self.fields_view);
-            self.reload();
-        });
+        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.options.sidebar && this.options.sidebar_id) {
+        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();
@@ -133,7 +123,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             // null index means we should start a new record
             promise = this.on_button_new();
         } else {
-            promise = this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
+            promise = this.dataset.read_index(_.keys(this.fields_view.fields)).pipe(this.on_record_loaded);
         }
         this.$element.show();
         if (this.sidebar) {
@@ -148,50 +138,48 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         }
     },
     on_record_loaded: function(record) {
+        var self = this,
+            deferred_stack = $.Deferred.queue();
         if (!record) {
             throw("Form: No record received");
         }
-        if (!record.id) {
-            this.$form_header.find('.oe_form_on_create').show();
-            this.$form_header.find('.oe_form_on_update').hide();
-            if (!this.options["always_show_new_button"]) {
-                this.$form_header.find('button.oe_form_button_new').hide();
-            }
-        } else {
-            this.$form_header.find('.oe_form_on_create').hide();
-            this.$form_header.find('.oe_form_on_update').show();
-            this.$form_header.find('button.oe_form_button_new').show();
-        }
-        this.dirty_for_user = false;
         this.datarecord = record;
-        for (var f in this.fields) {
-            var field = this.fields[f];
-            field.dirty = false;
-            field.set_value(this.datarecord[f] || false);
-            field.validate();
-        }
-        if (!record.id) {
-            // New record: Second pass in order to trigger the onchanges
-            this.show_invalid = false;
-            for (var f in record) {
-                var field = this.fields[f];
-                if (field) {
-                    field.dirty = true;
-                    this.do_onchange(field);
-                }
+
+        _(this.fields).each(function (field, f) {
+            field.reset();
+            var result = field.set_value(self.datarecord[f] || false);
+            if (result && _.isFunction(result.promise)) {
+                deferred_stack.push(result);
             }
-        }
-        this.on_form_changed();
-        this.initial_mutating_lock.resolve();
-        this.show_invalid = true;
-        this.do_update_pager(record.id == null);
-        if (this.sidebar) {
-            this.sidebar.attachments.do_update();
-            this.sidebar.$element.find('.oe_sidebar_translate').toggleClass('oe_hide', !record.id);
-        }
-        if (this.default_focus_field && !this.embedded_view) {
-            this.default_focus_field.focus();
-        }
+            $.when(result).then(function() {
+                field.validate();
+            });
+        });
+        deferred_stack.push('force resolution if no fields');
+        return deferred_stack.then(function() {
+            if (!record.id) {
+                self.show_invalid = false;
+                // New record: Second pass in order to trigger the onchanges
+                // respecting the fields order defined in the view
+                _.each(self.fields_order, function(field_name) {
+                    if (record[field_name] !== undefined) {
+                        var field = self.fields[field_name];
+                        field.dirty = true;
+                        self.do_onchange(field);
+                    }
+                });
+            }
+            self.on_form_changed();
+            self.initial_mutating_lock.resolve();
+            self.show_invalid = true;
+            self.do_update_pager(record.id == null);
+            if (self.sidebar) {
+                self.sidebar.attachments.do_update();
+            }
+            if (self.default_focus_field && !self.embedded_view) {
+                self.default_focus_field.focus();
+            }
+        });
     },
     on_form_changed: function() {
         for (var w in this.widgets) {
@@ -225,69 +213,99 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         $pager.find('span.oe_pager_index').html(index);
         $pager.find('span.oe_pager_count').html(this.dataset.ids.length);
     },
+    parse_on_change: function (on_change, widget) {
+        var self = this;
+        var onchange = _.str.trim(on_change);
+        var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/);
+        if (!call) {
+            return null;
+        }
+
+        var method = call[1];
+        if (!_.str.trim(call[2])) {
+            return {method: method, args: [], context_index: null}
+        }
+
+        var argument_replacement = {
+            'False': function () {return false;},
+            'True': function () {return true;},
+            'None': function () {return null;},
+            'context': function (i) {
+                context_index = i;
+                var ctx = widget.build_context ? widget.build_context() : {};
+                return ctx;
+            }
+        };
+        var parent_fields = null, context_index = null;
+        var args = _.map(call[2].split(','), function (a, i) {
+            var field = _.str.trim(a);
+
+            // literal constant or context
+            if (field in argument_replacement) {
+                return argument_replacement[field](i);
+            }
+            // literal number
+            if (/^-?\d+(\.\d+)?$/.test(field)) {
+                return Number(field);
+            }
+            // form field
+            if (self.fields[field]) {
+                var value = self.fields[field].get_on_change_value();
+                return value == null ? false : value;
+            }
+            // parent field
+            var splitted = field.split('.');
+            if (splitted.length > 1 && _.str.trim(splitted[0]) === "parent" && self.dataset.parent_view) {
+                if (parent_fields === null) {
+                    parent_fields = self.dataset.parent_view.get_fields_values();
+                }
+                var p_val = parent_fields[_.str.trim(splitted[1])];
+                if (p_val !== undefined) {
+                    return p_val == null ? false : p_val;
+                }
+            }
+            // string literal
+            var first_char = field[0], last_char = field[field.length-1];
+            if ((first_char === '"' && last_char === '"')
+                || (first_char === "'" && last_char === "'")) {
+                return field.slice(1, -1);
+            }
+
+            throw new Error("Could not get field with name '" + field +
+                            "' for onchange '" + onchange + "'");
+        });
+
+        return {
+            method: method,
+            args: args,
+            context_index: context_index
+        };
+    },
     do_onchange: function(widget, processed) {
         var self = this;
         var act = function() {
             try {
-            processed = processed || [];
-            if (widget.node.attrs.on_change) {
-                var onchange = _.trim(widget.node.attrs.on_change);
-                var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/);
-                if (call) {
-                    var method = call[1], args = [];
-                    var context_index = null;
-                    var argument_replacement = {
-                        'False' : function() {return false;},
-                        'True' : function() {return true;},
-                        'None' : function() {return null;},
-                        'context': function(i) {
-                            context_index = i;
-                            var ctx = widget.build_context ? widget.build_context() : {};
-                            return ctx;
-                        }
-                    };
-                    var parent_fields = null;
-                    _.each(call[2].split(','), function(a, i) {
-                        var field = _.trim(a);
-                        if (field in argument_replacement) {
-                            args.push(argument_replacement[field](i));
-                            return;
-                        } else if (self.fields[field]) {
-                            var value = self.fields[field].get_on_change_value();
-                            args.push(value == null ? false : value);
-                            return;
-                        } else {
-                            var splitted = field.split('.');
-                            if (splitted.length > 1 && _.trim(splitted[0]) === "parent" && self.dataset.parent_view) {
-                                if (parent_fields === null) {
-                                    parent_fields = self.dataset.parent_view.get_fields_values();
-                                }
-                                var p_val = parent_fields[_.trim(splitted[1])];
-                                if (p_val !== undefined) {
-                                    args.push(p_val == null ? false : p_val);
-                                    return;
-                                }
-                            }
-                        }
-                        throw "Could not get field with name '" + field +
-                            "' for onchange '" + onchange + "'";
-                    });
-                    var ajax = {
-                        url: '/web/dataset/call',
-                        async: false
-                    };
-                    return self.rpc(ajax, {
-                        model: self.dataset.model,
-                        method: method,
-                        args: [(self.datarecord.id == null ? [] : [self.datarecord.id])].concat(args),
-                        context_id: context_index === null ? null : context_index + 1
-                    }).pipe(function(response) {
-                        return self.on_processed_onchange(response, processed);
-                    });
-                } else {
-                    console.log("Wrong on_change format", on_change);
+                processed = processed || [];
+                var on_change = widget.node.attrs.on_change;
+                if (on_change) {
+                    var change_spec = self.parse_on_change(on_change, widget);
+                    if (change_spec) {
+                        var ajax = {
+                            url: '/web/dataset/call',
+                            async: false
+                        };
+                        return self.rpc(ajax, {
+                            model: self.dataset.model,
+                            method: change_spec.method,
+                            args: [(self.datarecord.id == null ? [] : [self.datarecord.id])].concat(change_spec.args),
+                            context_id: change_spec.context_index == undefined ? null : change_spec.context_index + 1
+                        }).pipe(function(response) {
+                            return self.on_processed_onchange(response, processed);
+                        });
+                    } else {
+                        console.warn("Wrong on_change format", on_change);
+                    }
                 }
-            }
             } catch(e) {
                 console.error(e);
                 return $.Deferred().reject();
@@ -308,7 +326,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                     processed.push(field.name);
                     if (field.get_value() != value) {
                         field.set_value(value);
-                        field.dirty = this.dirty_for_user = true;
+                        field.dirty = true;
                         if (_.indexOf(processed, field.name) < 0) {
                             this.do_onchange(field, processed);
                         }
@@ -336,6 +354,15 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             return $.Deferred().reject();
         }
     },
+    on_button_save: function() {
+        var self = this;
+        return this.do_save().then(function(result) {
+            self.do_prev_view(result.created);
+        });
+    },
+    on_button_cancel: function() {
+        return this.do_prev_view();
+    },
     on_button_new: function() {
         var self = this;
         var def = $.Deferred();
@@ -343,33 +370,21 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             if (self.can_be_discarded()) {
                 var keys = _.keys(self.fields_view.fields);
                 if (keys.length) {
-                    self.dataset.default_get(keys).then(self.on_record_loaded).then(function() {
+                    self.dataset.default_get(keys).pipe(self.on_record_loaded).then(function() {
                         def.resolve();
                     });
                 } else {
-                    self.on_record_loaded({});
-                    def.resolve();
+                    self.on_record_loaded({}).then(function() {
+                        def.resolve();
+                    });
                 }
             }
         });
         return def.promise();
     },
-    on_button_duplicate: function() {
-        var self = this;
-        var def = $.Deferred();
-        $.when(this.has_been_loaded).then(function() {
-            if (self.can_be_discarded()) {
-                self.dataset.call('copy', [self.datarecord.id, {}, self.dataset.context]).then(function(new_id) {
-                    return self.on_created({ result : new_id });
-                }).then(function() {
-                    def.resolve();
-                });
-            }
-        });
-        return def.promise();
-    },
     can_be_discarded: function() {
-        return !this.dirty_for_user || confirm(_t("Warning, the record has been modified, your changes will be discarded."));
+        return true; // FIXME: Disabled until the page view and button refactoring is done
+        return !this.is_dirty() || confirm(_t("Warning, the record has been modified, your changes will be discarded."));
     },
     /**
      * Triggers saving the form's record. Chooses between creating a new
@@ -385,8 +400,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             try {
             if (!self.initial_mutating_lock.isResolved() && !self.initial_mutating_lock.isRejected())
                 return;
-            var form_dirty = false,
-                form_invalid = false,
+            var form_invalid = false,
                 values = {},
                 first_invalid_field = null;
             for (var f in self.fields) {
@@ -397,8 +411,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                     if (!first_invalid_field) {
                         first_invalid_field = f;
                     }
-                } else if (f.is_dirty()) {
-                    form_dirty = true;
+                } else if (f.name !== 'id' && !f.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
                     values[f.name] = f.get_value();
                 }
             }
@@ -407,16 +423,22 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                 self.on_invalid();
                 return $.Deferred().reject();
             } else {
-                console.log("About to save", values);
+                var save_deferral;
                 if (!self.datarecord.id) {
-                    return self.dataset.create(values).pipe(function(r) {
+                    openerp.log("FormView(", self, ") : About to create", values);
+                    save_deferral = self.dataset.create(values).pipe(function(r) {
                         return self.on_created(r, undefined, prepend_on_create);
-                    }).then(success);
+                    }, null);
+                } else if (_.isEmpty(values)) {
+                    openerp.log("FormView(", self, ") : Nothing to save");
+                    save_deferral = $.Deferred().resolve({}).promise();
                 } else {
-                    return self.dataset.write(self.datarecord.id, values, {}).pipe(function(r) {
+                    openerp.log("FormView(", self, ") : About to save", values);
+                    save_deferral = self.dataset.write(self.datarecord.id, values, {}).pipe(function(r) {
                         return self.on_saved(r);
-                    }).then(success);
+                    }, null);
                 }
+                return save_deferral.then(success);
             }
             } catch (e) {
                 console.error(e);
@@ -426,10 +448,6 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
         this.mutating_lock = this.mutating_lock.pipe(action, action);
         return this.mutating_lock;
     },
-    switch_readonly: function() {
-    },
-    switch_editable: function() {
-    },
     on_invalid: function() {
         var msg = "<ul>";
         _.each(this.fields, function(f) {
@@ -445,8 +463,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             // should not happen in the server, but may happen for internal purpose
             return $.Deferred().reject();
         } else {
-            this.reload();
-            return $.when(r).then(success);
+            return $.when(this.reload()).pipe(function () {
+                return $.when(r).then(success); }, null);
         }
     },
     /**
@@ -479,7 +497,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
             if (this.sidebar) {
                 this.sidebar.attachments.do_update();
             }
-            console.debug("The record has been created with id #" + this.datarecord.id);
+            openerp.log("The record has been created with id #" + this.datarecord.id);
             this.reload();
             return $.when(_.extend(r, {created: true})).then(success);
         }
@@ -487,16 +505,13 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
     on_action: function (action) {
         console.debug('Executing action', action);
     },
-    do_cancel: function () {
-        console.debug("Cancelling form");
-    },
     reload: function() {
         var self = this;
         var act = function() {
             if (self.dataset.index == null || self.dataset.index < 0) {
                 return $.when(self.on_button_new());
             } else {
-                return self.dataset.read_index(_.keys(self.fields_view.fields), self.on_record_loaded);
+                return self.dataset.read_index(_.keys(self.fields_view.fields)).pipe(self.on_record_loaded);
             }
         };
         this.reload_lock = this.reload_lock.pipe(act, act);
@@ -521,6 +536,11 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                 return self.dataset.parent_view.recursive_save();
         });
     },
+    is_dirty: function() {
+        return _.any(this.fields, function (value) {
+            return value.is_dirty();
+        });
+    },
     is_interactible_record: function() {
         var id = this.datarecord.id;
         if (!id) {
@@ -531,6 +551,9 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
                 return false;
         }
         return true;
+    },
+    sidebar_context: function () {
+        return this.do_save().pipe($.proxy(this, 'get_fields_values'));
     }
 });
 openerp.web.FormDialog = openerp.web.Dialog.extend({
@@ -606,7 +629,7 @@ openerp.web.form.SidebarAttachments = openerp.web.Widget.extend({
     },
     on_attachment_delete: function(e) {
         var self = this, $e = $(e.currentTarget);
-        var name = _.trim($e.parent().find('a.oe-sidebar-attachments-link').text());
+        var name = _.str.trim($e.parent().find('a.oe-sidebar-attachments-link').text());
         if (confirm("Do you really want to delete the attachment " + name + " ?")) {
             this.rpc('/web/dataset/unlink', {
                 model: 'ir.attachment',
@@ -676,7 +699,7 @@ openerp.web.form.compute_domain = function(expr, fields) {
                 stack.push(!_(val).contains(field_value));
                 break;
             default:
-                console.log("Unsupported operator in modifiers :", op);
+                console.warn("Unsupported operator in modifiers :", op);
         }
     }
     return _.all(stack, _.identity);
@@ -715,6 +738,16 @@ openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form.
         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;
     },
     start: function() {
@@ -734,11 +767,35 @@ openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form.
         var template = this.template;
         return QWeb.render(template, { "widget": this });
     },
+    do_attach_tooltip: function(widget, trigger, options) {
+        widget = widget || this;
+        trigger = trigger || this.$element;
+        options = _.extend({
+                delay: 1000,
+                maxWidth: openerp.connection.debug ? '300px' : '200px',
+                content: function() {
+                    var template = widget.template + '.tooltip';
+                    if (!QWeb.has_template(template)) {
+                        template = 'WidgetLabel.tooltip';
+                    }
+                    return QWeb.render(template, {
+                        debug: openerp.connection.debug,
+                        widget: widget
+                    });
+                }
+            }, options || {});
+        trigger.tipTip(options);
+    },
     _build_view_fields_values: function() {
         var a_dataset = this.view.dataset;
         var fields_values = this.view.get_fields_values();
-        var parent_values = a_dataset.parent_view ? a_dataset.parent_view.get_fields_values() : {};
-        fields_values.parent = parent_values;
+        var active_id = a_dataset.ids[a_dataset.index];
+        _.extend(fields_values, {
+            active_id: active_id || false,
+            active_ids: active_id ? [active_id] : [],
+            active_model: a_dataset.model,
+            parent: a_dataset.parent_view ? a_dataset.parent_view.get_fields_values() : {}
+        });
         return fields_values;
     },
     _build_eval_context: function() {
@@ -841,8 +898,9 @@ openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({
         var type = {};
         if (node.tag == 'field') {
             type = this.view.fields_view.fields[node.attrs.name] || {};
-            if (node.attrs.widget == 'statusbar') {
+            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';
             }
         }
@@ -874,6 +932,10 @@ openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({
     }
 });
 
+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) {
@@ -902,6 +964,11 @@ openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({
         });
         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'), {
+                defaultPosition: 'top'
+            });
+        }
     },
     do_select_first_visible_tab: function() {
         for (var i = 0; i < this.pages.length; i++) {
@@ -965,6 +1032,9 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
     start: function() {
         this._super.apply(this, arguments);
         this.$element.find("button").click(this.on_click);
+        if (this.help || openerp.connection.debug) {
+            this.do_attach_tooltip();
+        }
     },
     on_click: function() {
         var self = this;
@@ -973,6 +1043,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
         this.execute_action().always(function() {
             self.force_disabled = false;
             self.check_disable();
+            $.tipTipClear();
         });
     },
     execute_action: function() {
@@ -1044,10 +1115,14 @@ openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
 
         this._super(view, node);
 
-        // TODO fme: support for attrs.align
-        if (this.node.tag == 'label' && (this.node.attrs.colspan || (this.string && this.string.length > 32))) {
+        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';
+            }
         } else {
             this.colspan = 1;
             this.width = '1%';
@@ -1065,9 +1140,12 @@ openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
     start: function() {
         this._super();
         var self = this;
+        if (this['for'] && (this['for'].help || openerp.connection.debug)) {
+            this.do_attach_tooltip(self['for']);
+        }
         this.$element.find("label").dblclick(function() {
             var widget = self['for'] || self;
-            console.log(widget.element_class , widget);
+            openerp.log(widget.element_class , widget);
             window.w = widget;
         });
     }
@@ -1085,6 +1163,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
         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;
 
@@ -1099,8 +1178,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
         this.nolabel = (this.field.nolabel || node.attrs.nolabel) === '1';
         this.readonly = this.modifiers['readonly'] === true;
         this.required = this.modifiers['required'] === true;
-        this.invalid = false;
-        this.dirty = false;
+        this.invalid = this.dirty = false;
 
         this.classname = 'oe_form_field_' + this.type;
     },
@@ -1110,6 +1188,11 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
             this.view.translatable_fields.push(this);
             this.$element.find('.oe_field_translate').click(this.on_translate);
         }
+        if (this.nolabel && openerp.connection.debug) {
+            this.do_attach_tooltip(this, this.$element, {
+                defaultPosition: 'top'
+            });
+        }
     },
     set_value: function(value) {
         this.value = value;
@@ -1151,7 +1234,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
         }
     },
     on_ui_change: function() {
-        this.dirty = this.view.dirty_for_user = true;
+        this.dirty = true;
         this.validate();
         if (this.is_valid()) {
             this.set_value_from_ui();
@@ -1165,6 +1248,9 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
         this.invalid = false;
     },
     focus: function() {
+    },
+    reset: function() {
+        this.dirty = false;
     }
 });
 
@@ -1254,7 +1340,6 @@ openerp.web.form.FieldFloat = openerp.web.form.FieldChar.extend({
         if (value === false || value === undefined) {
             // As in GTK client, floats default to 0
             value = 0;
-            this.dirty = true;
         }
         this._super.apply(this, [value]);
     }
@@ -1264,54 +1349,58 @@ openerp.web.DateTimeWidget = openerp.web.Widget.extend({
     template: "web.datetimepicker",
     jqueryui_object: 'datetimepicker',
     type_of_date: "datetime",
+    init: function(parent) {
+        this._super(parent);
+        this.name = parent.name;
+    },
     start: function() {
         var self = this;
-        this.$element.find('input').change(this.on_change);
+        this.$input = this.$element.find('input.oe_datepicker_master');
+        this.$input_picker = this.$element.find('input.oe_datepicker_container');
+        this.$input.change(this.on_change);
         this.picker({
             onSelect: this.on_picker_select,
             changeMonth: true,
             changeYear: true,
             showWeek: true,
-            showButtonPanel: false
+            showButtonPanel: true
         });
         this.$element.find('img.oe_datepicker_trigger').click(function() {
             if (!self.readonly) {
                 self.picker('setDate', self.value ? openerp.web.auto_str_to_date(self.value) : new Date());
-                self.$element.find('.oe_datepicker').toggle();
+                self.$input_picker.show();
+                self.picker('show');
+                self.$input_picker.hide();
             }
         });
-        this.$element.find('.ui-datepicker-inline').removeClass('ui-widget-content ui-corner-all');
-        this.$element.find('button.oe_datepicker_close').click(function() {
-            self.$element.find('.oe_datepicker').hide();
-        });
         this.set_readonly(false);
         this.value = false;
     },
     picker: function() {
-        return $.fn[this.jqueryui_object].apply(this.$element.find('.oe_datepicker_container'), arguments);
+        return $.fn[this.jqueryui_object].apply(this.$input_picker, arguments);
     },
     on_picker_select: function(text, instance) {
         var date = this.picker('getDate');
-        this.$element.find('input').val(date ? this.format_client(date) : '').change();
+        this.$input.val(date ? this.format_client(date) : '').change();
     },
     set_value: function(value) {
         this.value = value;
-        this.$element.find('input').val(value ? this.format_client(value) : '');
+        this.$input.val(value ? this.format_client(value) : '');
     },
     get_value: function() {
         return this.value;
     },
     set_value_from_ui: function() {
-        var value = this.$element.find('input').val() || false;
+        var value = this.$input.val() || false;
         this.value = this.parse_client(value);
     },
     set_readonly: function(readonly) {
         this.readonly = readonly;
-        this.$element.find('input').attr('disabled', this.readonly);
+        this.$input.attr('disabled', this.readonly);
         this.$element.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly);
     },
     is_valid: function(required) {
-        var value = this.$element.find('input').val();
+        var value = this.$input.val();
         if (value === "") {
             return !required;
         } else {
@@ -1324,7 +1413,7 @@ openerp.web.DateTimeWidget = openerp.web.Widget.extend({
         }
     },
     focus: function() {
-        this.$element.find('input').focus();
+        this.$input.focus();
     },
     parse_client: function(v) {
         return openerp.web.parse_value(v, {"widget": this.type_of_date});
@@ -1341,11 +1430,7 @@ openerp.web.DateTimeWidget = openerp.web.Widget.extend({
 
 openerp.web.DateWidget = openerp.web.DateTimeWidget.extend({
     jqueryui_object: 'datepicker',
-    type_of_date: "date",
-    on_picker_select: function(text, instance) {
-        this._super(text, instance);
-        this.$element.find('.oe_datepicker').hide();
-    }
+    type_of_date: "date"
 });
 
 openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
@@ -1357,7 +1442,7 @@ openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
         var self = this;
         this._super.apply(this, arguments);
         this.datewidget = this.build_widget();
-        this.datewidget.on_change.add(this.on_ui_change);
+        this.datewidget.on_change.add_last(this.on_ui_change);
         this.datewidget.appendTo(this.$element);
     },
     set_value: function(value) {
@@ -1470,7 +1555,7 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
     init: function(view, node) {
         var self = this;
         this._super(view, node);
-        this.values = this.field.selection;
+        this.values = _.clone(this.field.selection);
         _.each(this.values, function(v, i) {
             if (v[0] === false && v[1] === '') {
                 self.values.splice(i, 1);
@@ -1745,7 +1830,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
             if (search_val.length > 0 &&
                 !_.include(raw_result, search_val) &&
                 (!self.value || search_val !== self.value[1])) {
-                values.push({label: _.sprintf(_t('<em>   Create "<strong>%s</strong>"</em>'),
+                values.push({label: _.str.sprintf(_t('<em>   Create "<strong>%s</strong>"</em>'),
                         $('<span />').text(search_val).html()), action: function() {
                     self._quick_create(search_val);
                 }});
@@ -1818,7 +1903,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
             self.original_value = undefined;
             self._change_int_ext_value(rval);
         };
-        if(typeof(value) === "number") {
+        if (value && !(value instanceof Array)) {
             var dataset = new openerp.web.DataSetStatic(this, this.field.relation, self.build_context());
             dataset.name_get([value], function(data) {
                 real_set_value(data[0]);
@@ -1921,7 +2006,8 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
     multi_selection: false,
     init: function(view, node) {
         this._super(view, node);
-        this.is_started = $.Deferred();
+        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;
@@ -1939,6 +2025,16 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
             self.on_ui_change();
         });
 
+        this.is_setted.then(function() {
+            self.load_views();
+        });
+    },
+    is_readonly: function() {
+        return this.readonly || this.force_readonly;
+    },
+    load_views: function() {
+        var self = this;
+        
         var modes = this.node.attrs.mode;
         modes = !!modes ? modes.split(",") : ["tree"];
         var views = [];
@@ -1953,7 +2049,14 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
             }
             if(view.view_type === "list") {
                 view.options.selectable = self.multi_selection;
+                if (self.is_readonly()) {
+                    view.options.addable = null;
+                    view.options.deletable = null;
+                }
             } else if (view.view_type === "form") {
+                if (self.is_readonly()) {
+                    view.view_type = 'page';
+                }
                 view.options.not_interactible_on_create = true;
             }
             views.push(view);
@@ -1961,54 +2064,72 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
         this.views = views;
 
         this.viewmanager = new openerp.web.ViewManager(this, this.dataset, views);
+        this.viewmanager.template = 'One2Many.viewmanager';
         this.viewmanager.registry = openerp.web.views.clone({
             list: 'openerp.web.form.One2ManyListView',
-            form: 'openerp.web.form.One2ManyFormView'
+            form: 'openerp.web.FormView',
+            page: 'openerp.web.PageView'
         });
         var once = $.Deferred().then(function() {
             self.init_form_last_update.resolve();
         });
+        var def = $.Deferred().then(function() {
+            self.initial_is_loaded.resolve();
+        });
         this.viewmanager.on_controller_inited.add_last(function(view_type, controller) {
             if (view_type == "list") {
                 controller.o2m = self;
-            } else if (view_type == "form") {
+                if (self.is_readonly())
+                    controller.set_editable(false);
+            } else if (view_type == "form" || view_type == 'page') {
+                if (view_type == 'page') {
+                    controller.$element.find(".oe_form_buttons").hide();
+                }
                 controller.on_record_loaded.add_last(function() {
                     once.resolve();
                 });
                 controller.on_pager_action.add_first(function() {
-                    self.save_form_view();
+                    self.save_any_view();
                 });
                 controller.$element.find(".oe_form_button_save").hide();
             } else if (view_type == "graph") {
                 self.reload_current_view()
             }
-            self.is_started.resolve();
+            def.resolve();
         });
-        this.viewmanager.on_mode_switch.add_first(function() {
-            self.save_form_view();
+        this.viewmanager.on_mode_switch.add_first(function(n_mode, b, c, d, e) {
+            $.when(self.save_any_view()).then(function() {
+                if(n_mode === "list")
+                    setTimeout(function() {self.reload_current_view();}, 0);
+            });
         });
         this.is_setted.then(function() {
             setTimeout(function () {
                 self.viewmanager.appendTo(self.$element);
             }, 0);
         });
+        return def;
     },
     reload_current_view: function() {
         var self = this;
-        var view = self.viewmanager.views[self.viewmanager.active_view].controller;
-        if(self.viewmanager.active_view === "list") {
-            view.reload_content();
-        } else if (self.viewmanager.active_view === "form") {
-            if (this.dataset.index === null && this.dataset.ids.length >= 1) {
-                this.dataset.index = 0;
-            }
-            var act = function() {
-                return view.do_show();
+        return self.is_loaded = self.is_loaded.pipe(function() {
+            var active_view = self.viewmanager.active_view;
+            var view = self.viewmanager.views[active_view].controller;
+            if(active_view === "list") {
+                return view.reload_content();
+            } else if (active_view === "form" || active_view === 'page') {
+                if (self.dataset.index === null && self.dataset.ids.length >= 1) {
+                    self.dataset.index = 0;
+                }
+                var act = function() {
+                    return view.do_show();
+                };
+                self.form_last_update = self.form_last_update.pipe(act, act);
+                return self.form_last_update;
+            } else if (active_view === "graph") {
+                return view.do_search(self.build_domain(), self.dataset.get_context(), []);
             }
-            this.form_last_update = this.form_last_update.pipe(act, act);;
-        } else if (self.viewmanager.active_view === "graph") {
-            view.do_search(this.build_domain(), this.dataset.get_context(), []);
-        }
+        }, undefined);
     },
     set_value: function(value) {
         value = value || [];
@@ -2065,15 +2186,14 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
         if (this.dataset.index === null && this.dataset.ids.length > 0) {
             this.dataset.index = 0;
         }
-        $.when(this.is_started).then(function() {
-            self.reload_current_view();
-        });
-        this.is_setted.resolve();
+        self.is_setted.resolve();
+        return self.reload_current_view();
     },
     get_value: function() {
         var self = this;
         if (!this.dataset)
             return [];
+        this.save_any_view();
         var val = this.dataset.delete_all ? [commands.delete_all()] : [];
         val = val.concat(_.map(this.dataset.ids, function(id) {
             var alter_order = _.detect(self.dataset.to_create, function(x) {return x.id === id;});
@@ -2090,7 +2210,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
             this.dataset.to_delete, function(x) {
                 return commands['delete'](x.id);}));
     },
-    save_form_view: function() {
+    save_any_view: function() {
         if (this.viewmanager && this.viewmanager.views && this.viewmanager.active_view &&
             this.viewmanager.views[this.viewmanager.active_view] &&
             this.viewmanager.views[this.viewmanager.active_view].controller) {
@@ -2102,6 +2222,13 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
                     console.warn("Asynchronous get_value() is not supported in form view.");
                 }*/
                 return res;
+            } else if (this.viewmanager.active_view === "list") {
+                var res = $.when(view.ensure_saved());
+                // it seems line there are some cases when this happens
+                /*if (!res.isResolved() && !res.isRejected()) {
+                    console.warn("Asynchronous get_value() is not supported in list view.");
+                }*/
+                return res;
             }
         }
         return false;
@@ -2126,12 +2253,23 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
         }
     },
     is_dirty: function() {
-        this.save_form_view();
+        this.save_any_view();
         return this._super();
     },
     update_dom: function() {
         this._super.apply(this, arguments);
-        this.$element.toggleClass('disabled', this.readonly);
+        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();
+                    });
+                });
+            }
+        }
     }
 });
 
@@ -2142,10 +2280,8 @@ openerp.web.form.One2ManyDataSet = openerp.web.BufferedDataSet.extend({
     }
 });
 
-openerp.web.form.One2ManyFormView = openerp.web.FormView.extend({
-});
-
 openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
+    _template: 'One2Many.listview',
     do_add_record: function () {
         if (this.options.editable) {
             this._super.apply(this, arguments);
@@ -2183,7 +2319,8 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
             read_function: function() {
                 return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
             },
-            form_view_options: {'not_interactible_on_create':true}
+            form_view_options: {'not_interactible_on_create':true},
+            readonly: self.o2m.is_readonly()
         });
         pop.on_write.add(function(id, data) {
             self.o2m.dataset.write(id, data, {}, function(r) {
@@ -2199,7 +2336,8 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
     init: function(view, node) {
         this._super(view, node);
         this.list_id = _.uniqueId("many2many");
-        this.is_started = $.Deferred();
+        this.is_loaded = $.Deferred();
+        this.initial_is_loaded = this.is_loaded;
         this.is_setted = $.Deferred();
     },
     start: function() {
@@ -2225,9 +2363,7 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
         this._super(value);
         this.dataset.set_ids(value);
         var self = this;
-        $.when(this.is_started).then(function() {
-            self.list_view.reload_content();
-        });
+        self.reload_content();
         this.is_setted.resolve();
     },
     get_value: function() {
@@ -2236,17 +2372,24 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
     validate: function() {
         this.invalid = false;
     },
+    is_readonly: function() {
+        return this.readonly || this.force_readonly;
+    },
     load_view: function() {
         var self = this;
         this.list_view = new openerp.web.form.Many2ManyListView(this, this.dataset, false, {
-                    'addable': self.readonly ? null : 'Add',
-                    'deletable': self.readonly ? false : true,
+                    'addable': self.is_readonly() ? null : 'Add',
+                    'deletable': self.is_readonly() ? false : true,
                     'selectable': self.multi_selection
             });
+        var embedded = (this.field.views || {}).tree;
+        if (embedded) {
+            this.list_view.set_embedded_view(embedded);
+        }
         this.list_view.m2m_field = this;
         var loaded = $.Deferred();
         this.list_view.on_loaded.add_last(function() {
-            self.is_started.resolve();
+            self.initial_is_loaded.resolve();
             loaded.resolve();
         });
         setTimeout(function () {
@@ -2254,16 +2397,22 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
         }, 0);
         return loaded;
     },
+    reload_content: function() {
+        var self = this;
+        this.is_loaded = this.is_loaded.pipe(function() {
+            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) {
-                $.when(this.is_started).then(function() {
+                this.is_loaded = this.is_loaded.pipe(function() {
                     self.list_view.stop();
-                    $.when(self.load_view()).then(function() {
-                        self.list_view.reload_content();
+                    return $.when(self.load_view()).then(function() {
+                        self.reload_content();
                     });
                 });
             }
@@ -2302,7 +2451,9 @@ 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);
-        pop.show_element(this.dataset.model, id, this.m2m_field.build_context(), {});
+        pop.show_element(this.dataset.model, id, this.m2m_field.build_context(), {
+            readonly: this.widget_parent.is_readonly()
+        });
         pop.on_write_completed.add_last(function() {
             self.reload_content();
         });
@@ -2338,7 +2489,8 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
         }, read_function: null});
         this.initial_ids = this.options.initial_ids;
         this.created_elements = [];
-        openerp.web.form.dialog(this.render(), {close:function() {
+        this.render_element();
+        openerp.web.form.dialog(this.$element, {close:function() {
             self.check_exit();
         }});
         this.start();
@@ -2360,21 +2512,30 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
         this.dataset.parent_view = this.options.parent_view;
         this.dataset.on_default_get.add(this.on_default_get);
         if (this.options.initial_view == "search") {
-            this.setup_search_view();
+            self.rpc('/web/session/eval_domain_and_context', {
+                domains: [],
+                contexts: [this.context]
+            }, function (results) {
+                var search_defaults = {};
+                _.each(results.context, function (value, key) {
+                    var match = /^search_default_(.*)$/.exec(key);
+                    if (match) {
+                        search_defaults[match[1]] = value;
+                    }
+                });
+                self.setup_search_view(search_defaults);
+            });
         } else { // "form"
             this.new_object();
         }
     },
-    setup_search_view: function() {
+    setup_search_view: function(search_defaults) {
         var self = this;
         if (this.searchview) {
             this.searchview.stop();
         }
         this.searchview = new openerp.web.SearchView(this,
-                this.dataset, false, {
-                    "selectable": !this.options.disable_multiple_selection,
-                    "deletable": false
-                });
+                this.dataset, false,  search_defaults);
         this.searchview.on_search.add(function(domains, contexts, groupbys) {
             if (self.initial_ids) {
                 self.do_search(domains.concat([[["id", "in", self.initial_ids]], self.domain]),
@@ -2401,7 +2562,9 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
             });
             self.view_list = new openerp.web.form.SelectCreateListView(self,
                     self.dataset, false,
-                    _.extend({'deletable': false}, self.options.list_view_options || {}));
+                    _.extend({'deletable': false,
+                        '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.do_show();
@@ -2520,13 +2683,15 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
      * - read_function
      * - parent_view
      * - 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});
-        jQuery(this.render()).dialog({title: '',
+        this.render_element();
+        this.$element.dialog({title: '',
                     modal: true,
                     width: 960,
                     height: 600});
@@ -2554,7 +2719,10 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
     on_write_completed: function() {},
     setup_form_view: function() {
         var self = this;
-        this.view_form = new openerp.web.FormView(this, this.dataset, false, self.options.form_view_options);
+        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);
         }
@@ -2572,6 +2740,10 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
             $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);
@@ -2609,10 +2781,12 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({
         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',
@@ -2622,10 +2796,12 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({
     on_nop: function() {
     },
     on_selection_changed: function() {
-        var sel = this.selection.get_value();
-        this.m2o.field.relation = sel;
-        this.m2o.set_value(null);
-        this.m2o.$element.toggle(sel !== false);
+        if (this.reference_ready) {
+            var sel = this.selection.get_value();
+            this.m2o.field.relation = sel;
+            this.m2o.set_value(null);
+            this.m2o.$element.toggle(sel !== false);
+        }
     },
     start: function() {
         this._super();
@@ -2640,11 +2816,18 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({
     },
     set_value: function(value) {
         this._super(value);
+        this.reference_ready = false;
+        var vals = [], sel_val, m2o_val;
         if (typeof(value) === 'string') {
-            var vals = value.split(',');
-            this.selection.set_value(vals[0]);
-            this.m2o.set_value(parseInt(vals[1], 10));
+            vals = value.split(',');
         }
+        sel_val = vals[0] || false;
+        m2o_val = vals[1] ? parseInt(vals[1], 10) : false;
+        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() {
         var model = this.selection.get_value(),
@@ -2806,7 +2989,7 @@ openerp.web.form.FieldStatus = openerp.web.form.Field.extend({
     render_list: function() {
         var self = this;
         var shown = _.map(((this.node.attrs || {}).statusbar_visible || "").split(","),
-            function(x) { return _.trim(x); });
+            function(x) { return _.str.trim(x); });
         shown = _.select(shown, function(x) { return x.length > 0; });
 
         if (shown.length == 0) {
@@ -2854,103 +3037,14 @@ openerp.web.form.FieldStatus = openerp.web.form.Field.extend({
     }
 });
 
-openerp.web.form.FieldReadonly = openerp.web.form.Field.extend({
 
-});
-openerp.web.form.FieldCharReadonly = openerp.web.form.FieldReadonly.extend({
-    template: 'FieldChar.readonly',
-    init: function(view, node) {
-        this._super(view, node);
-        this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1';
-    },
-    set_value: function (value) {
-        this._super.apply(this, arguments);
-        var show_value = openerp.web.format_value(value, this, '');
-        if (this.password) {
-            show_value = new Array(show_value.length + 1).join('*');
-        }
-        this.$element.find('div').text(show_value);
-        return show_value;
-    }
-});
-openerp.web.form.FieldURIReadonly = openerp.web.form.FieldCharReadonly.extend({
-    template: 'FieldURI.readonly',
-    scheme: null,
-    set_value: function (value) {
-        var displayed = this._super.apply(this, arguments);
-        this.$element.find('a')
-                .attr('href', this.scheme + ':' + displayed)
-                .text(displayed);
-    }
-});
-openerp.web.form.FieldEmailReadonly = openerp.web.form.FieldURIReadonly.extend({
-    scheme: 'mailto'
-});
-openerp.web.form.FieldUrlReadonly = openerp.web.form.FieldURIReadonly.extend({
-    set_value: function (value) {
-        var s = /(\w+):(.+)/.exec(value);
-        if (!s || !(s[1] === 'http' || s[1] === 'https')) { return; }
-        this.scheme = s[1];
-        this._super(s[2]);
-    }
-});
-openerp.web.form.FieldBooleanReadonly = openerp.web.form.FieldCharReadonly.extend({
-    set_value: function (value) {
-        this._super(value ? '\u2611' : '\u2610');
-    }
-});
-openerp.web.form.FieldSelectionReadonly = openerp.web.form.FieldReadonly.extend({
-    template: 'FieldChar.readonly',
-    init: function(view, node) {
-        // lifted straight from r/w version
-        var self = this;
-        this._super(view, node);
-        this.values = this.field.selection;
-        _.each(this.values, function(v, i) {
-            if (v[0] === false && v[1] === '') {
-                self.values.splice(i, 1);
-            }
-        });
-        this.values.unshift([false, '']);
-    },
-    set_value: function (value) {
-        value = value === null ? false : value;
-        value = value instanceof Array ? value[0] : value;
-        var option = _(this.values)
-            .detect(function (record) { return record[0] === value; });
-        this._super(value);
-        this.$element.find('div').text(option ? option[1] : this.values[0][1]);
-    }
-});
-openerp.web.form.FieldMany2OneReadonly = openerp.web.form.FieldCharReadonly.extend({
-    set_value: function (value) {
-        value = value || null;
-        this.invalid = false;
-        var self = this;
-        this.value = value;
-        self.update_dom();
-        self.on_value_changed();
-        var real_set_value = function(rval) {
-            self.$element.find('div').text(rval ? rval[1] : '');
-        };
-        if(typeof(value) === "number") {
-            var dataset = new openerp.web.DataSetStatic(
-                    this, this.field.relation, self.build_context());
-            dataset.name_get([value], function(data) {
-                real_set_value(data[0]);
-            }).fail(function() {self.tmp_value = undefined;});
-        } else {
-            setTimeout(function() {real_set_value(value);}, 0);
-        }
-    }
-});
 
 /**
  * Registry of form widgets, called by :js:`openerp.web.FormView`
  */
 openerp.web.form.widgets = new openerp.web.Registry({
     'frame' : 'openerp.web.form.WidgetFrame',
-    'group' : 'openerp.web.form.WidgetFrame',
+    'group' : 'openerp.web.form.WidgetGroup',
     'notebook' : 'openerp.web.form.WidgetNotebook',
     'notebookpage' : 'openerp.web.form.WidgetNotebookPage',
     'separator' : 'openerp.web.form.WidgetSeparator',
@@ -2978,21 +3072,7 @@ openerp.web.form.widgets = new openerp.web.Registry({
     'binary': 'openerp.web.form.FieldBinaryFile',
     'statusbar': 'openerp.web.form.FieldStatus'
 });
-openerp.web.form.readonly = openerp.web.form.widgets.clone({
-    'char': 'openerp.web.form.FieldCharReadonly',
-    'email': 'openerp.web.form.FieldEmailReadonly',
-    'url': 'openerp.web.form.FieldUrlReadonly',
-    'text': 'openerp.web.form.FieldCharReadonly',
-    'text_wiki' : 'openerp.web.form.FieldCharReadonly',
-    'date': 'openerp.web.form.FieldCharReadonly',
-    'datetime': 'openerp.web.form.FieldCharReadonly',
-    'selection' : 'openerp.web.form.FieldSelectionReadonly',
-    'many2one': 'openerp.web.form.FieldMany2OneReadonly',
-    'boolean': 'openerp.web.form.FieldBooleanReadonly',
-    'float': 'openerp.web.form.FieldCharReadonly',
-    'integer': 'openerp.web.form.FieldCharReadonly',
-    'float_time': 'openerp.web.form.FieldCharReadonly'
-});
+
 
 };