diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 197cb3dda30..a02383a5670 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -50,11 +50,12 @@ openerp.web.list_editable = function (instance) { * @param {Boolean} [force] forces the list to editability. Sets new row edition status to "bottom". */ set_editable: function (force) { + // TODO: fix handling of editability status to be simpler & clearer & more coherent // If ``force``, set editability to bottom // 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 = true || ( + this.options.editable = ( ! this.options.read_only && ((force && "bottom") || this.defaults.editable)); }, /** @@ -82,7 +83,7 @@ openerp.web.list_editable = function (instance) { // tree/@editable takes priority on everything else if present. 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) { + if (this.options.editable) { this.editor = new instance.web.list.Editor(this); var editor_ready = this.editor.prependTo(this.$element) @@ -168,6 +169,7 @@ openerp.web.list_editable = function (instance) { */ saveEdition: function () { var self = this; + // TODO: save:after should be invoked after reload return this.withEvent('save', { editor: this.editor, form: this.editor.form, @@ -349,16 +351,44 @@ openerp.web.list_editable = function (instance) { start: function () { var self = this; var _super = this._super(); - this.form.embedded_view = this.delegate.editionView(this); + this.form.embedded_view = this._validateView( + this.delegate.editionView(this)); var form_ready = this.form.appendTo(this.$element).then( self.form.proxy('do_hide')); return $.when(_super, form_ready); }, + _validateView: function (edition_view) { + if (!edition_view) { + throw new Error("editor delegate's #editionView must return " + + "a view descriptor"); + } + var arch = edition_view.arch; + if (!(arch && arch.children instanceof Array)) { + throw new Error("Editor delegate's #editionView must have a" + + " non-empty arch") + } + if (!(arch.tag === "form")) { + throw new Error("Editor delegate's #editionView must have a" + + " 'form' root node"); + } + if (!(arch.attrs && arch.attrs.version === "7.0")) { + throw new Error("Editor delegate's #editionView must be a" + + " version 7 view"); + } + if (!/\boe_form_container\b/.test(arch.attrs['class'])) { + throw new Error("Editor delegate's #editionView must have the" + + " class 'oe_form_container' on its root" + + " element"); + } + + return edition_view; + }, isEditing: function () { return !!this.record; }, edit: function (record, configureField) { + // TODO: specify sequence of edit calls var self = this; var form = self.form; record = _.extend({}, record); diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index e82d3d0ae9b..35901e90165 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -1238,7 +1238,9 @@ instance.web.json_node_to_xml = function(node, human_readable, indent) { if (typeof(node) === 'string') { return sindent + node; } else if (typeof(node.tag) !== 'string' || !node.children instanceof Array || !node.attrs instanceof Object) { - throw("Node a json node"); + throw new Error( + _.str.sprintf("Node [%s] is not a JSONified XML node", + JSON.stringify(node))); } for (var attr in node.attrs) { var vattr = node.attrs[attr]; diff --git a/addons/web/static/test/list-editable.js b/addons/web/static/test/list-editable.js new file mode 100644 index 00000000000..6ac3d96df2c --- /dev/null +++ b/addons/web/static/test/list-editable.js @@ -0,0 +1,71 @@ +$(document).ready(function () { + var $fix = $('#qunit-fixture'); + var xhr = QWeb2.Engine.prototype.get_xhr(); + xhr.open('GET', '/web/static/src/xml/base.xml', false); + xhr.send(null); + var doc = xhr.responseXML; + + var noop = function () {}; + /** + * Make connection RPC responses mockable by setting keys on the + * Connection#responses object (key is the URL, value is the function to + * call with the RPC request payload) + * + * @param {openerp.web.Connection} connection connection instance to mockify + * @param {Object} [responses] url:function mapping to seed the mock connection + */ + var mockifyRPC = function (connection, responses) { + connection.responses = responses || {}; + connection.rpc_function = function (url, payload) { + if (!(url.url in this.responses)) { + return $.Deferred().reject({}, 'failed', _.str.sprintf("Url %s not found in mock responses", url.url)).promise(); + } + return $.when(this.responses[url.url](payload)); + }; + }; + + var instance; + var baseSetup = function () { + instance = window.openerp.init([]); + window.openerp.web.corelib(instance); + window.openerp.web.coresetup(instance); + window.openerp.web.chrome(instance); + window.openerp.web.data(instance); + window.openerp.web.views(instance); + window.openerp.web.list(instance); + window.openerp.web.form(instance); + window.openerp.web.list_editable(instance); + + instance.web.qweb.add_template(doc); + + mockifyRPC(instance.connection); + }; + module('editor', { + setup: baseSetup + }); + asyncTest('base-state', 2, function () { + var e = new instance.web.list.Editor({ + dataset: {}, + editionView: function () { + return { + arch: { + tag: 'form', + attrs: { + version: '7.0', + 'class': 'oe_form_container' + }, + children: [] + } + }; + } + }); + e.appendTo($fix) + .always(start) + .fail(function (error) { ok(false, error && error.message); }) + .done(function () { + ok(!e.isEditing(), "should not be editing"); + ok(e.form instanceof instance.web.FormView, + "should use default form type"); + }); + }); +}); diff --git a/addons/web/static/test/test.html b/addons/web/static/test/test.html index e352f63c17c..f06d68ed4da 100644 --- a/addons/web/static/test/test.html +++ b/addons/web/static/test/test.html @@ -38,6 +38,7 @@ +

OpenERP web Test Suite

@@ -55,4 +56,5 @@ + diff --git a/doc/list-view.rst b/doc/list-view.rst index e71c6a828d6..28f22ab7ad4 100644 --- a/doc/list-view.rst +++ b/doc/list-view.rst @@ -163,9 +163,6 @@ view provides a number of dedicated events to its lifecycle. Invoked after a save has been completed - .. todo:: currently invoked before the record has reloaded, which - is kinda shitty - ``cancel:before`` *cancellable* Invoked before cancelling a pending edition, provided with the @@ -193,8 +190,6 @@ formview, delegating instead to its e.g. :js:func:`~openerp.web.list.Editor.edit` multiple times in a row without saving or cancelling each edit is undefined. - .. todo:: define this behavior - :param parent: :type parent: :js:class:`~openerp.web.Widget` :param EditorOptions options: