* @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));
},
/**
// 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)
*/
saveEdition: function () {
var self = this;
+ // TODO: save:after should be invoked after reload
return this.withEvent('save', {
editor: this.editor,
form: this.editor.form,
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);
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];
--- /dev/null
+$(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");
+ });
+ });
+});
<script src="/web/static/src/js/search.js"></script>
<script src="/web/static/src/js/view_form.js"></script>
<script src="/web/static/src/js/view_list.js"></script>
+ <script src="/web/static/src/js/view_list_editable.js"></script>
</head>
<body id="oe" class="openerp">
<h1 id="qunit-header">OpenERP web Test Suite</h1>
<script type="text/javascript" src="/web/static/test/rpc.js"></script>
<script type="text/javascript" src="/web/static/test/evals.js"></script>
<script type="text/javascript" src="/web/static/test/search.js"></script>
+ <script type="text/javascript" src="/web/static/test/list-editable.js"></script>
</html>
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
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: