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
-----------
|