[FIX] web: add support for editable=top/bottom in o2m

Before this commit, we lost the functionality of editing records top or
bottom.  However, it as almost ready, but we needed to change the way
commands are generated, to respect the actual order in the UI.

Also, the mock server needed to be updated, to make sure he also save
the new ids in the correct order.
This commit is contained in:
Géry Debongnie
2017-04-11 19:44:38 +02:00
committed by Aaron Bohy
parent 39ab5712ed
commit e7dfa4af77
4 changed files with 121 additions and 24 deletions
@@ -777,8 +777,8 @@ var FieldOne2Many = FieldX2Many.extend({
return this._super.apply(this, arguments).then(function () {
if (event && event.target === self && self.view.arch.tag === 'tree') {
if (event.data.changes[self.name].operation === 'CREATE') {
// todo: get last record if editable = bottom
var newID = self.value.data[0].id;
var index = self.editable === 'top' ? 0 : self.value.data.length - 1;
var newID = self.value.data[index].id;
self.renderer.editRecord(newID);
}
}
@@ -1058,7 +1058,7 @@ var BasicModel = AbstractModel.extend({
return this.notifyChanges(id, command.data);
}));
} else {
defs.push(this.addDefaultRecord(list.id));
defs.push(this.addDefaultRecord(list.id, {position: command.position}));
}
break;
case 'UPDATE':
@@ -1715,20 +1715,29 @@ var BasicModel = AbstractModel.extend({
var removedIds = _.difference(list.res_ids, relIds);
var addedIds = _.difference(relIds, list.res_ids);
var keptIds = _.intersection(list.res_ids, relIds);
// the didChange variable keeps track of the fact that at
// least one id was updated
var didChange = false;
var command, i, relRecord;
for (i = 0; i < keptIds.length; i++) {
relRecord = _.findWhere(relData, {res_id: keptIds[i]});
if (!_.isEmpty(relRecord._changes)) {
var changes = this._generateChanges(relRecord);
command = x2ManyCommands.update(relRecord.res_id, changes);
didChange = true;
} else {
command = x2ManyCommands.link_to(keptIds[i]);
var command, relRecord;
for (var i = 0; i < relIds.length; i++) {
if (_.contains(keptIds, relIds[i])) {
// this is an id that already existed
relRecord = _.findWhere(relData, {res_id: relIds[i]});
if (!_.isEmpty(relRecord._changes)) {
var changes = this._generateChanges(relRecord);
command = x2ManyCommands.update(relRecord.res_id, changes);
didChange = true;
} else {
command = x2ManyCommands.link_to(relIds[i]);
}
commands[fieldName].push(command);
} else if (_.contains(addedIds, relIds[i])) {
// this is a new id
relRecord = _.findWhere(relData, {res_id: relIds[i]});
relRecord = this.get(relRecord.id, {raw: true});
commands[fieldName].push(x2ManyCommands.create(_.omit(relRecord.data, 'id')));
}
commands[fieldName].push(command);
}
if (changesOnly && !didChange && addedIds.length === 0 && removedIds.length === 0) {
// in this situation, we have no changed ids, no added
@@ -1736,11 +1745,7 @@ var BasicModel = AbstractModel.extend({
// last changes
commands[fieldName] = [];
}
for (i = 0; i < addedIds.length; i++) {
relRecord = _.findWhere(relData, {res_id: addedIds[i]});
relRecord = this.get(relRecord.id, {raw: true});
commands[fieldName].push(x2ManyCommands.create(_.omit(relRecord.data, 'id')));
}
// add delete commands
for (i = 0; i < removedIds.length; i++) {
commands[fieldName].push(x2ManyCommands.delete(removedIds[i]));
}
@@ -2628,6 +2628,98 @@ QUnit.module('relational_fields', {
form.destroy();
});
QUnit.test('one2many list editable = top', function (assert) {
assert.expect(6);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<group>' +
'<field name="turtles">' +
'<tree editable="top">' +
'<field name="turtle_foo"/>' +
'</tree>' +
'</field>' +
'</group>' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
if (args.method === 'write') {
var commands = args.args[1].turtles;
assert.strictEqual(commands[0][0], 0,
"first command is a create");
assert.strictEqual(commands[1][0], 4,
"second command is a link to");
}
return this._super.apply(this, arguments);
},
});
form.$buttons.find('.o_form_button_edit').click();
assert.strictEqual(form.$('.o_data_row').length, 1,
"should start with one data row");
form.$('.o_form_field_x2many_list_row_add a').click();
assert.strictEqual(form.$('.o_data_row').length, 2,
"should have 2 data rows");
assert.ok(form.$('tr.o_data_row:first:contains(My little Foo Value)').length, 1,
"first row should be the new value");
assert.ok(form.$('tr.o_data_row:first').hasClass('o_selected_row'),
"first row should be selected");
form.$buttons.find('.o_form_button_save').click();
form.destroy();
});
QUnit.test('one2many list editable = bottom', function (assert) {
assert.expect(6);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<group>' +
'<field name="turtles">' +
'<tree editable="bottom">' +
'<field name="turtle_foo"/>' +
'</tree>' +
'</field>' +
'</group>' +
'</form>',
res_id: 1,
mockRPC: function (route, args) {
if (args.method === 'write') {
var commands = args.args[1].turtles;
assert.strictEqual(commands[0][0], 4,
"first command is a link to");
assert.strictEqual(commands[1][0], 0,
"second command is a create");
}
return this._super.apply(this, arguments);
},
});
form.$buttons.find('.o_form_button_edit').click();
assert.strictEqual(form.$('.o_data_row').length, 1,
"should start with one data row");
form.$('.o_form_field_x2many_list_row_add a').click();
assert.strictEqual(form.$('.o_data_row').length, 2,
"should have 2 data rows");
assert.ok(form.$('tr.o_data_row:eq(1):contains(My little Foo Value)').length, 1,
"second row should be the new value");
assert.ok(form.$('tr.o_data_row:eq(1)').hasClass('o_selected_row'),
"second row should be selected");
form.$buttons.find('.o_form_button_save').click();
form.destroy();
});
QUnit.module('FieldMany2Many');
QUnit.test('many2many kanban: edition', function (assert) {
@@ -896,26 +896,26 @@ var MockServer = Class.extend({
continue;
}
if (_.contains(['one2many', 'many2many'], field.type)) {
var ids = [];
// convert commands
_.each(value, function (command) {
if (command[0] === 0) { // CREATE
var id = self._mockCreate(field.relation, command[2]);
record[field_changed].push(id);
ids.push(id);
} else if (command[0] === 1) { // UPDATE
self._mockWrite(field.relation, [[command[1]], command[2]]);
} else if (command[0] === 2) { // DELETE
record[field_changed] = _.without(record[field_changed], command[1]);
} else if (command[0] === 4) { // LINK_TO
// nothing to do, this command is called by a one2many,
// and should do nothing in a non concurrent environment.
ids.push(command[1]);
} else if (command[0] === 5) { // DELETE ALL
record[field_changed] = [];
ids = [];
} else if (command[0] === 6) { // REPLACE WITH
record[field_changed] = command[2];
ids = command[2];
} else {
console.error('Command ' + JSON.stringify(command) + ' not supported by the MockServer');
}
});
record[field_changed] = ids;
} else if (field.type === 'many2one') {
if (value) {
var relatedRecord = _.findWhere(this.data[field.relation].records, {