[FIX] make edition of existing records kinda-sorta work (if the record is 'saved...
authorXavier Morel <xmo@openerp.com>
Thu, 28 Jun 2012 14:14:03 +0000 (16:14 +0200)
committerXavier Morel <xmo@openerp.com>
Thu, 28 Jun 2012 14:14:03 +0000 (16:14 +0200)
bzr revid: xmo@openerp.com-20120628141403-z8kdg24xy5thmg50

addons/web/static/src/js/view_list.js
addons/web/static/src/js/view_list_editable.js

index 7d17700..49e6ece 100644 (file)
@@ -1133,6 +1133,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
      * @returns {String} QWeb rendering of the selected record
      */
     render_record: function (record) {
+        var self = this;
         var index = this.records.indexOf(record);
         return QWeb.render('ListView.row', {
             columns: this.columns,
@@ -1141,7 +1142,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
             row_parity: (index % 2 === 0) ? 'even' : 'odd',
             view: this.view,
             render_cell: function () {
-                return this.render_cell.apply(this, arguments); }
+                return self.render_cell.apply(self, arguments); }
         });
     },
     /**
@@ -1843,6 +1844,19 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{
     },
 
     // underscore-type methods
+    find: function (callback) {
+        var record = null;
+        for(var section in this._proxies) {
+            if (this._proxies.hasOwnProperty(section)) {
+                record = this._proxies[section].find(callback);
+            }
+            if (record) { return record; }
+        }
+        for(var i=0; i<this.length; ++i) {
+            record = callback(this.records[i]);
+            if (record) { return record; }
+        }
+    },
     each: function (callback) {
         for(var section in this._proxies) {
             if (this._proxies.hasOwnProperty(section)) {
index 74b51c3..fb367c4 100644 (file)
@@ -15,6 +15,7 @@ openerp.web.list_editable = function (instance) {
         init: function () {
             var self = this;
             this._super.apply(this, arguments);
+
             $(this.groups).bind({
                 'edit': function (e, id, dataset) {
                     self.do_edit(dataset.index, id, dataset);
@@ -82,19 +83,14 @@ openerp.web.list_editable = function (instance) {
             this.options.editable = ! this.options.read_only && (data.arch.attrs.editable || this.options.editable);
             var result = this._super(data, grouped);
             if (this.options.editable || true) {
-                // TODO: [Return], [Esc] events
-                this.form = new instance.web.FormView(this, this.dataset, false, {
-                    initial_mode: 'edit',
-                    $buttons: $(),
-                    $pager: $()
-                });
-                this.form.embedded_view = this.view_to_form_view();
-                form_ready = this.form.prependTo(this.$element).then(function () {
-                    self.form.do_hide();
-                });
+                this.editor = new instance.web.list.Editor(this);
+
+                return $.when(
+                    result,
+                    this.editor.prependTo(this.$element));
             }
 
-            return $.when(result, form_ready);
+            return result;
         },
         /**
          * Ensures the editable list is saved (saves any pending edition if
@@ -105,54 +101,30 @@ openerp.web.list_editable = function (instance) {
          * @returns {$.Deferred}
          */
         ensure_saved: function () {
-            return this.groups.ensure_saved();
-        },
-        view_to_form_view: function () {
-            var view = $.extend(true, {}, this.fields_view);
-            view.arch.tag = 'form';
-            _.extend(view.arch.attrs, {
-                'class': 'oe_form_container',
-                version: '7.0'
-            });
-            _(view.arch.children).each(function (widget) {
-                var modifiers = JSON.parse(widget.attrs.modifiers || '{}');
-                widget.attrs.nolabel = true;
-                if (modifiers['tree_invisible'] || widget.tag === 'button') {
-                    modifiers.invisible = true;
-                }
-                widget.attrs.modifiers = JSON.stringify(modifiers);
-            });
-            return view;
+            if (!this.editor.isEditing()) {
+                return $.when();
+            }
+            return this.save_edition();
         },
         /**
          * Set up the edition of a record of the list view "inline"
          *
-         * @param {Number} id id of the record to edit, null for new record
-         * @param {Number} index index of the record to edit in the dataset, null for new record
+         * @param {instance.web.list.Record} record record to edit
          * @param {Object} cells map of field names to the DOM elements used to display these fields for the record being edited
+         * @return {jQuery.Deferred}
          */
-        edit_record: function (id, index, cells) {
-            // TODO: save previous edition if any
+        start_edition: function (record, cells) {
             var self = this;
-            var record = this.records.get(id);
-            var e = {
-                id: id,
-                record: record,
-                cancel: false
-            };
-            this.trigger('edit:before', e);
-            if (e.cancel) {
-                return;
-            }
-            return this.form.on_record_loaded(record.attributes).pipe(function () {
-                return self.form.do_show({reload: false});
-            }).then(function () {
-                // TODO: [Save] button
-                // TODO: save on action button?
-                _(cells).each(function (cell, field_name) {
+            return this.ensure_saved().pipe(function () {
+                return self.withEvent('edit', {
+                    record: record.attributes,
+                    cancel: false
+                }, self.editor.edit,
+                [record.attributes, function (field_name, field) {
+                    var cell = cells[field_name];
+                    if (!cell) { return; }
                     var $cell = $(cell);
                     var position = $cell.position();
-                    var field = self.form.fields[field_name];
 
                     // FIXME: this is shit. Is it possible to prefilter?
                     if (field.get('effective_readonly')) {
@@ -167,12 +139,195 @@ openerp.web.list_editable = function (instance) {
                         width: $cell.outerWidth(),
                         minHeight: $cell.outerHeight()
                     });
+                }],
+                [record.attributes]);
+            });
+        },
+        /**
+         * @return {jQuery.Deferred}
+         */
+        save_edition: function () {
+            var self = this;
+            return this.withEvent('save', {
+                editor: this.editor,
+                form: this.editor.form,
+                cancel: false
+            }, this.editor.save).then(function (attrs) {
+                var record = self.records.get(attrs.id);
+                if (!record) {
+                    // new record
+                    record = self.records.find(function (r) {
+                        return !r.get('id');
+                    });
+                    record.set('id', attrs.id, {silent: true});
+                }
+                self.reload_record(record);
+            });
+        },
+        /**
+         * @return {jQuery.Deferred}
+         */
+        cancel_edition: function () {
+            var self = this;
+            return this.withEvent('cancel', {
+                editor: this.editor,
+                form: this.editor.form,
+                cancel: false
+            }, this.editor.cancel).then(function (attrs) {
+                if (!attrs.id) {
+                    var to_delete = self.records.find(function (r) {
+                        return !r.get('id');
+                    });
+                    if (to_delete) {
+                        self.records.remove(to_delete);
+                    }
+                }
+            });
+        },
+        /**
+         * Executes an action on the view's editor bracketed by a cancellable
+         * event of the name provided.
+         *
+         * The event name provided will be post-fixed with ``:before`` and
+         * ``:after``, the ``event`` parameter will be passed alongside the
+         * ``:before`` variant and if the parameter's ``cancel`` key is set to
+         * ``true`` the action *will not be called* and the method will return
+         * a rejection
+         *
+         * @param {String} event_name name of the event
+         * @param {Object} event event object, provided to ``:before`` sub-event
+         * @param {Function} action callable, called with the view's editor as its context
+         * @param {Array} [args] supplementary arguments provided to the action
+         * @param {Array} [trigger_params] supplementary arguments provided to the ``:after`` sub-event, before anything fetched by the ``action`` function
+         * @return {jQuery.Deferred}
+         */
+        withEvent: function (event_name, event, action, args, trigger_params) {
+            var self = this;
+            event = event || {};
+            this.trigger(event_name + ':before', event);
+            if (event.cancel) {
+                return $.Deferred().reject();
+            }
+            return $.when(action.apply(this.editor, args || [])).then(function () {
+                self.trigger.apply(self, [event_name + ':after']
+                        .concat(trigger_params || [])
+                        .concat(_.toArray(arguments)));
+            });
+        },
+        editionView: function (editor) {
+            var view = $.extend(true, {}, this.fields_view);
+            view.arch.tag = 'form';
+            _.extend(view.arch.attrs, {
+                'class': 'oe_form_container',
+                version: '7.0'
+            });
+            _(view.arch.children).each(function (widget) {
+                var modifiers = JSON.parse(widget.attrs.modifiers || '{}');
+                widget.attrs.nolabel = true;
+                if (modifiers['tree_invisible'] || widget.tag === 'button') {
+                    modifiers.invisible = true;
+                }
+                widget.attrs.modifiers = JSON.stringify(modifiers);
+            });
+            return view;
+        }
+    });
+
+    instance.web.list.Editor = instance.web.Widget.extend({
+        /**
+         * @constructs instance.web.list.Editor
+         * @extends instance.web.Widget
+         *
+         * Adapter between listview and formview for editable-listview purposes
+         *
+         * @param {instance.web.Widget} parent
+         * @param {Object} options
+         * @param {instance.web.FormView} [options.formView=instance.web.FormView]
+         */
+        init: function (parent, options) {
+            this._super(parent);
+            this.options = options || {};
+            _.defaults(this.options, {
+                formView: instance.web.FormView
+            });
+
+            this.record = null;
+
+            this.form = new (this.options.formView)(
+                this, this.getParent().dataset, false, {
+                    initial_mode: 'edit',
+                    $buttons: $(),
+                    $pager: $()
+            });
+        },
+        start: function () {
+            var self = this;
+            var _super = this._super();
+            this.form.embedded_view = this.getParent().editionView(this);
+            var form_ready = this.form.appendTo(this.$element).then(function () {
+                self.form.do_hide();
+                self.form.$element.on('keyup', function (e) {
+                    switch (e.which) {
+                    case KEY_RETURN:
+                        self.save();
+                        break;
+                    case KEY_ESCAPE:
+                        self.cancel();
+                        break;
+                    }
                 });
-                // TODO: actually focus clicked field (if editable)
-                self.form.fields[self.form.fields_order[0]].focus();
-                self.trigger('edit:after', record, self.form)
             });
+            return $.when(_super, form_ready);
+        },
 
+        isEditing: function () {
+            return !!this.record;
+        },
+        edit: function (record, configureField) {
+            var self = this;
+            var form = self.form;
+            form.on_record_loaded(record).pipe(function () {
+                return form.do_show({reload: false});
+            }).then(function () {
+                self.record = record;
+                // TODO: [Save] button
+                // TODO: save on action button?
+                _(form.fields).each(function (field, name) {
+                    configureField(name, field);
+                });
+                // TODO: actually focus clicked field (if editable)
+                _(form.fields_order).detect(function (name) {
+                    // look for first visible field in fields_order, focus it
+                    var field = form.fields[name];
+                    if (!field.$element.is(':visible')) {
+                        return false;
+                    }
+                    field.focus();
+                    return true;
+                });
+                return form;
+            });
+        },
+        save: function () {
+            var self = this;
+            return this.form
+                .do_save(null, this.getParent().options.editable === 'top')
+                .pipe(function (result) {
+                    var created = result.created && !self.record.id;
+                    if (created) {
+                        self.record.id = result.result;
+                    }
+                    return self.cancel();
+                });
+        },
+        cancel: function () {
+            var record = this.record;
+            this.record = null;
+            if (!this.form.can_be_discarded()) {
+                return $.Deferred.reject();
+            }
+            this.form.do_hide();
+            return $.when(record);
         }
     });
 
@@ -263,20 +418,24 @@ openerp.web.list_editable = function (instance) {
                 break;
             }
         },
-        render_row_as_form: function (row) {
-            var record_id = $(row).data('id');
-            var index = _(this.dataset.ids).indexOf(record_id);
+        render_row_as_form: function ($row) {
+            var record;
+            if (!$row || $row.length === 0) {
+                record = new instance.web.list.Record();
+                this.records.add(
+                    record, {at: this.options.editable === 'top' ? 0 : null});
+                $row = this.$current.find(':not([data-id])');
+            } else {
+                record = this.records.get($row.data('id'));
+            }
 
             var cells = {};
-            row.children('td').each(function (index, el) {
+            $row.children('td').each(function (index, el) {
                 cells[el.getAttribute('data-field')] = el
             });
 
             // TODO: creation (record_id === null?)
-            return this.view.edit_record(
-                record_id,
-                index !== -1 ? index : null,
-                cells);
+            return this.view.start_edition(record, cells);
         },
         handle_onwrite: function (source_record_id) {
             var self = this;