diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index a59f16faea9..41d48432559 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1999,6 +1999,15 @@ .openerp .oe_form .oe_form_field_many2many > .oe-listview .oe_list_pager_single_page { display: none; } +.openerp .oe-listview { + position: relative; +} +.openerp .oe-listview .oe_form .oe_form_field { + width: auto; + position: absolute; + margin: 0 !important; + padding: 0; +} .openerp .oe-listview-content { width: 100%; } diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 57c4f343713..7135b474aa3 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1588,6 +1588,14 @@ $colour4: #8a89ba display: none // }}} // ListView {{{ + .oe-listview + position: relative + .oe_form .oe_form_field + width: auto + position: absolute + margin: 0 !important // dammit + padding: 0 + .oe-listview-content width: 100% td:first-child, th:first-child diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 95c425a0e1d..7d1770069f8 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -146,7 +146,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi * @returns {$.Deferred} loading promise */ start: function() { - this.$element.addClass('oe-listview'); + this.$element.addClass('oe-listview').css('position: relative'); return this.reload_view(null, null, true); }, /** diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 831169511cf..4109a5bd6d5 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -53,7 +53,7 @@ openerp.web.list_editable = function (instance) { // otherwise rely on view default // view' @editable is handled separately as we have not yet // fetched and processed the view at this point. - this.options.editable = ( + this.options.editable = true || ( ! this.options.read_only && ((force && "bottom") || this.defaults.editable)); }, /** @@ -77,9 +77,24 @@ openerp.web.list_editable = function (instance) { } }, on_loaded: function (data, grouped) { + var self = this, form_ready = $.when(); // tree/@editable takes priority on everything else if present. this.options.editable = ! this.options.read_only && (data.arch.attrs.editable || this.options.editable); - return this._super(data, grouped); + 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(); + }); + } + + return $.when(result, form_ready); }, /** * Ensures the editable list is saved (saves any pending edition if @@ -91,6 +106,72 @@ openerp.web.list_editable = function (instance) { */ 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; + }, + /** + * 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 {Object} cells map of field names to the DOM elements used to display these fields for the record being edited + */ + edit_record: function (id, index, cells) { + // TODO: save previous edition if any + 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: automatic focus of ?first field + // TODO: [Save] button + // TODO: save on action button? + _(cells).each(function (cell, field_name) { + 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')) { + // Readonly fields can just remain the list's, form's + // usually don't have backgrounds &al + field.$element.hide(); + return; + } + field.$element.show().css({ + top: position.top, + left: position.left, + width: $cell.outerWidth(), + minHeight: $cell.outerHeight() + }); + }); + self.trigger('edit:after', record, self.form) + }); + } }); @@ -147,24 +228,6 @@ openerp.web.list_editable = function (instance) { this.pad_table_to(5); return cancelled; }, - /** - * Adapts this list's view description to be suitable to the inner form - * view of a row being edited. - * - * @returns {Object} fields_view_get's view section suitable for putting into form view of editable rows. - */ - get_form_fields_view: function () { - // deep copy of view - var view = $.extend(true, {}, this.group.view.fields_view); - _(view.arch.children).each(function (widget) { - widget.attrs.nolabel = true; - if (widget.tag === 'button') { - delete widget.attrs.string; - } - }); - view.arch.attrs.col = 2 * view.arch.children.length; - return view; - }, on_row_keyup: function (e) { var self = this; switch (e.which) { @@ -200,81 +263,19 @@ openerp.web.list_editable = function (instance) { } }, render_row_as_form: function (row) { - var self = this; - return this.ensure_saved().pipe(function () { - var record_id = $(row).data('id'); - var $new_row = $('', { - id: _.uniqueId('oe-editable-row-'), - 'data-id': record_id, - 'class': (row ? $(row).attr('class') : ''), - click: function (e) {e.stopPropagation();} - }) - .addClass('oe_form oe_form_container') - .delegate('button.oe-edit-row-save', 'click', function () { - self.save_row(); - }) - .delegate('button', 'keyup', function (e) { - e.stopImmediatePropagation(); - }) - .keyup(function () { - return self.on_row_keyup.apply(self, arguments); }) - .keydown(function (e) { e.stopPropagation(); }) - .keypress(function (e) { - if (e.which === KEY_RETURN) { - return false; - } - }); + var record_id = $(row).data('id'); + var index = _(this.dataset.ids).indexOf(record_id); - if (row) { - $new_row.replaceAll(row); - } else if (self.options.editable) { - var $last_child = self.$current.children('tr:last'); - if (self.records.length) { - if (self.options.editable === 'top') { - $new_row.insertBefore( - self.$current.children('[data-id]:first')); - } else { - $new_row.insertAfter( - self.$current.children('[data-id]:last')); - } - } else { - $new_row.prependTo(self.$current); - } - if ($last_child.is(':not([data-id])')) { - $last_child.remove(); - } - } - self.edition = true; - self.edition_id = record_id; - self.dataset.index = _(self.dataset.ids).indexOf(record_id); - if (self.dataset.index === -1) { - self.dataset.index = null; - } - self.edition_form = _.extend(new instance.web.ListEditableFormView(self.view, self.dataset, false), { - $element: $new_row, - editable_list: self - }); - // put in $.when just in case FormView.on_loaded becomes asynchronous - return $.when(self.edition_form.on_loaded(self.get_form_fields_view())).then(function () { - $new_row.find('> td') - .end() - .find('td:last').removeClass('oe-field-cell').end(); - // pad in case of groupby - _(self.columns).each(function (column) { - if (column.meta) { - $new_row.prepend(''); - } - }); - // Add column for the save, if - // there is none in the list - if (!self.options.deletable) { - self.view.pad_columns( - 1, {except: $new_row}); - } - - self.edition_form.do_show(); - }); + var cells = {}; + 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); }, handle_onwrite: function (source_record_id) { var self = this; @@ -368,76 +369,6 @@ openerp.web.list_editable = function (instance) { }, new_record: function () { this.render_row_as_form(); - }, - render_record: function (record) { - var index = this.records.indexOf(record), - self = this; - // FIXME: context dict should probably be extracted cleanly - return QWeb.render('ListView.row', { - columns: this.columns, - options: this.options, - record: record, - row_parity: (index % 2 === 0) ? 'even' : 'odd', - view: this.view, - render_cell: function () { - return self.render_cell.apply(self, arguments); }, - edited: !!this.edition_form - }); } }); - - instance.web.ListEditableFormView = instance.web.FormView.extend({ - init: function() { - this._super.apply(this, arguments); - this.rendering_engine = new instance.web.ListEditableRenderingEngine(this); - this.options.initial_mode = "edit"; - }, - renderElement: function() {} - }); - - instance.web.ListEditableRenderingEngine = instance.web.form.FormRenderingEngineInterface.extend({ - init: function(view) { - this.view = view; - }, - set_fields_view: function(fields_view) { - this.fvg = fields_view; - }, - set_tags_registry: function(tags_registry) { - this.tags_registry = tags_registry; - }, - set_fields_registry: function(fields_registry) { - this.fields_registry = fields_registry; - }, - render_to: function($element) { - var self = this; - - var xml = instance.web.json_node_to_xml(this.fvg.arch); - var $xml = $(xml); - - if (this.view.editable_list.options.selectable) - $("").appendTo($element); - - $xml.children().each(function(i, el) { - var modifiers = JSON.parse($(el).attr("modifiers") || "{}"); - var $td = $(""); - if (modifiers.tree_invisible === true) - $td.hide(); - var tag_name = el.tagName.toLowerCase(); - var w; - if (tag_name === "field") { - var name = $(el).attr("name"); - var key = $(el).attr('widget') || self.fvg.fields[name].type; - var obj = self.view.fields_registry.get_object(key); - w = new (obj)(self.view, instance.web.xml_to_json(el)); - self.view.register_field(w, $(el).attr("name")); - } else { - var obj = self.tags_registry.get_object(tag_name); - w = new (obj)(self.view, instance.web.xml_to_json(el)); - } - w.appendTo($td); - $td.appendTo($element); - }); - $(QWeb.render('ListView.row.save')).appendTo($element); - }, - }); }; diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index c0463260405..a7c99e65b2c 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1472,15 +1472,6 @@ - - - - - - diff --git a/doc/form-notes.rst b/doc/form-notes.rst new file mode 100644 index 00000000000..fc450a336e1 --- /dev/null +++ b/doc/form-notes.rst @@ -0,0 +1,49 @@ +Notes on the usage of the Form View as a sub-widget +=================================================== + +Undocumented stuff +------------------ + +* ``initial_mode`` *option* defines the starting mode of the form + view, one of ``view`` and ``edit`` (?). Default value is ``view`` + (non-editable form). + +* ``embedded_view`` *attribute* has to be set separately when + providing a view directly, no option available for that usage. + + * View arch **must** contain node with + ``@class="oe_form_container"``, otherwise everything will break + without any info + + * Root element of view arch not being ``form`` may or may not work + correctly, no idea. + + * Freeform views => ``@version="7.0"`` + +* Form is not entirely loaded (some widgets may not appear) unless + ``on_record_loaded`` is called (or ``do_show``, which itself calls + ``on_record_loaded``). + +* "Empty" form => ``on_button_new`` (...), or manually call + ``default_get`` + ``on_record_loaded`` + +* Form fields default to width: 100%, padding, !important margin, can + be reached via ``.oe_form_field`` + +* Form *will* render buttons and a pager, offers options to locate + both outside of form itself (``$buttons`` and ``$pager``), providing + empty jquery objects (``$()``) seems to stop displaying both but not + sure if there are deleterious side-effects. + + Other options: + + * Pass in ``$(document.createDocumentFragment)`` to ensure it's a + DOM-compatible tree completely outside of the actual DOM. + + * ??? + +* readonly fields probably don't have a background, beware if need of + overlay + + * What is the difference between ``readonly`` and + ``effective_readonly``? diff --git a/doc/index.rst b/doc/index.rst index 9f54b879acc..c5b85326dac 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -18,6 +18,8 @@ Contents: search-view + form-notes + Older stuff -----------