[FIX] web: sort and onchange in tabbed editable x2m
Two attempts of dealing with sorting (and resequencing) in tabbed (multi-pages) x2many editable lists (see odoo/odoo@c2563db and odoo/odoo@983c7ae) have been previously done, using a static attribute (`keepChangesUnsorted`) on the datapoint. This solution sadly only covers both cases separately, not if they are combined. Morever, onchanges were not correctly managed in neither of these two cases. A new approach is used here, by "freezing" and "unfreezing" the datapoint when needed, which is more flexible. Some modifications have been introduced: - when ordering a x2many, all records are now considered (and not only the records on the current page) ; an additional read is thus done on the sorted field for all records in the x2many relation - `setSort` is now asynchronous as a `read` might be needed
This commit is contained in:
committed by
Martin Geubelle
parent
d3cebdc4a2
commit
b7ee1aa4b1
@@ -1018,6 +1018,7 @@ var FieldX2Many = AbstractField.extend({
|
||||
*/
|
||||
_onEditLine: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.trigger_up('freeze_order', {id: this.value.id});
|
||||
var editedRecord = this.value.data[ev.data.index];
|
||||
this.renderer.setRowMode(editedRecord.id, 'edit')
|
||||
.done(ev.data.onSuccess);
|
||||
@@ -1104,6 +1105,7 @@ var FieldX2Many = AbstractField.extend({
|
||||
*/
|
||||
_onResequence: function (event) {
|
||||
var self = this;
|
||||
this.trigger_up('freeze_order', {id: this.value.id});
|
||||
var rowIDs = event.data.rowIDs.slice();
|
||||
var rowID = rowIDs.pop();
|
||||
var defs = _.map(rowIDs, function (rowID, index) {
|
||||
@@ -1123,6 +1125,11 @@ var FieldX2Many = AbstractField.extend({
|
||||
operation: 'UPDATE',
|
||||
id: rowID,
|
||||
data: _.object([event.data.handleField], [event.data.offset + rowIDs.length]),
|
||||
}).always(function () {
|
||||
self.trigger_up('toggle_column_order', {
|
||||
id: self.value.id,
|
||||
name: event.data.handleField,
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -1229,6 +1236,7 @@ var FieldOne2Many = FieldX2Many.extend({
|
||||
}
|
||||
} else if (!this.creatingRecord) {
|
||||
this.creatingRecord = true;
|
||||
this.trigger_up('freeze_order', {id: this.value.id});
|
||||
this._setValue({
|
||||
operation: 'CREATE',
|
||||
position: this.editable,
|
||||
|
||||
@@ -365,12 +365,6 @@ return core.Class.extend({
|
||||
child.attrs.modifiers.column_invisible || false;
|
||||
}
|
||||
});
|
||||
|
||||
// detect editables list has they behave differently with respect
|
||||
// to the sorting (changes are not sorted directly)
|
||||
if (mode === 'list' && view.arch.attrs.editable) {
|
||||
attrs.keepChangesUnsorted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (attrs.Widget.prototype.fieldsToFetch) {
|
||||
|
||||
@@ -37,6 +37,7 @@ odoo.define('web.BasicModel', function (require) {
|
||||
* offset: {integer},
|
||||
* openGroupByDefault: {boolean},
|
||||
* orderedBy: {Object[]},
|
||||
* orderedResIDs: {integer[]},
|
||||
* parentID: {string},
|
||||
* rawContext: {Object},
|
||||
* relationField: {string},
|
||||
@@ -410,29 +411,11 @@ var BasicModel = AbstractModel.extend({
|
||||
return record;
|
||||
}
|
||||
|
||||
// here, type === 'list'
|
||||
// apply potential changes (only for x2many lists):
|
||||
// for list having the flag _keepChangesUnsorted set to true (typically,
|
||||
// editable lists), we sort before apply changes by default (to keep the
|
||||
// changes unsorted, i.e. the edited line at the same place). However, if
|
||||
// the user forces a sort, all changes done so far are sorted (this is the
|
||||
// purpose of the _keepChangesUnsortedCount key), and changes done later
|
||||
// on won't be sorted.
|
||||
var count;
|
||||
if (element._keepChangesUnsorted) {
|
||||
// only apply a subset (by default 0) of changes before sorting
|
||||
count = element._keepChangesUnsortedCount || 0;
|
||||
element = this._applyX2ManyOperations(element, {to: count, position: 'bottom'});
|
||||
} else {
|
||||
// apply all changes before sorting
|
||||
element = this._applyX2ManyOperations(element, {position: 'bottom'});
|
||||
}
|
||||
// apply potential changes (only for x2many lists)
|
||||
element = this._applyX2ManyOperations(element);
|
||||
this._sortList(element);
|
||||
if (element._keepChangesUnsorted) {
|
||||
// apply the remaining changes after the sort
|
||||
element = this._applyX2ManyOperations(element, {from: count, position: 'bottom'});
|
||||
}
|
||||
if (element._changes) {
|
||||
|
||||
if (!element.orderedResIDs && element._changes) {
|
||||
_.each(element._changes, function (change) {
|
||||
if (change.operation === 'ADD' && change.isNew) {
|
||||
element.data = _.without(element.data, change.id);
|
||||
@@ -444,6 +427,7 @@ var BasicModel = AbstractModel.extend({
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var list = {
|
||||
aggregateValues: _.extend({}, element.aggregateValues),
|
||||
context: _.extend({}, element.context),
|
||||
@@ -921,6 +905,8 @@ var BasicModel = AbstractModel.extend({
|
||||
// Erase changes as they have been applied
|
||||
record._changes = {};
|
||||
|
||||
self.unfreezeOrder(record.id);
|
||||
|
||||
// Update the data directly or reload them
|
||||
if (shouldReload) {
|
||||
self._fetchRecord(record).then(function (record) {
|
||||
@@ -952,6 +938,20 @@ var BasicModel = AbstractModel.extend({
|
||||
record.fields = _.extend({}, record.fields, viewInfo.fields);
|
||||
record.fieldsInfo = _.extend({}, record.fieldsInfo, viewInfo.fieldsInfo);
|
||||
},
|
||||
/**
|
||||
* For list resources, this freezes the current records order.
|
||||
*
|
||||
* @param {string} listID a valid element ID of type list
|
||||
*/
|
||||
freezeOrder: function (listID) {
|
||||
var list = this.localData[listID];
|
||||
if (list.type === 'record') {
|
||||
return;
|
||||
}
|
||||
list = this._applyX2ManyOperations(list);
|
||||
this._sortList(list);
|
||||
this.localData[listID].orderedResIDs = list.res_ids;
|
||||
},
|
||||
/**
|
||||
* Manually sets a resource as dirty. This is used to notify that a field
|
||||
* has been modified, but with an invalid value. In that case, the value is
|
||||
@@ -968,7 +968,7 @@ var BasicModel = AbstractModel.extend({
|
||||
*
|
||||
* @param {string} list_id id for the list resource
|
||||
* @param {string} fieldName valid field name
|
||||
* @returns {this} so we can chain call this method
|
||||
* @returns {Deferred}
|
||||
*/
|
||||
setSort: function (list_id, fieldName) {
|
||||
var list = this.localData[list_id];
|
||||
@@ -979,21 +979,26 @@ var BasicModel = AbstractModel.extend({
|
||||
delete change.isNew;
|
||||
});
|
||||
}
|
||||
// the user manually selected a sort order, so we sort the list with all
|
||||
// the changes he made so far, even if it is editable, however, changes
|
||||
// he'll made after won't be sorted if the list is editable
|
||||
list._keepChangesUnsortedCount = list._changes ? list._changes.length : 0;
|
||||
if (list.orderedBy.length === 0) {
|
||||
list.orderedBy.push({name: fieldName, asc: true});
|
||||
} else if (list.orderedBy[0].name === fieldName){
|
||||
list.orderedBy[0].asc = !list.orderedBy[0].asc;
|
||||
if (!list.orderedResIDs) {
|
||||
list.orderedBy[0].asc = !list.orderedBy[0].asc;
|
||||
}
|
||||
} else {
|
||||
var orderedBy = _.reject(list.orderedBy, function (o) {
|
||||
return o.name === fieldName;
|
||||
});
|
||||
list.orderedBy = [{name: fieldName, asc: true}].concat(orderedBy);
|
||||
}
|
||||
return this;
|
||||
|
||||
list.orderedResIDs = null;
|
||||
if (list.static) {
|
||||
// sorting might require to fetch the field for records where the
|
||||
// sort field is still unknown (i.e. on other pages for example)
|
||||
return this._fetchUngroupedList(list);
|
||||
}
|
||||
return $.when();
|
||||
},
|
||||
/**
|
||||
* Toggle the active value of given records (to archive/unarchive them)
|
||||
@@ -1048,6 +1053,34 @@ var BasicModel = AbstractModel.extend({
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* For a list datapoint, unfreezes the current records order and sorts it.
|
||||
* For a record datapoint, unfreezes the x2many list datapoints.
|
||||
*
|
||||
* @param {string} elementID a valid element ID
|
||||
*/
|
||||
unfreezeOrder: function (elementID) {
|
||||
var list = this.localData[elementID];
|
||||
if (list.type === 'record') {
|
||||
var data = _.extend({}, list.data, list._changes);
|
||||
for (var fieldName in data) {
|
||||
var field = list.fields[fieldName];
|
||||
if (!field || !data[fieldName]) {
|
||||
continue;
|
||||
}
|
||||
if (field.type === 'one2many' || field.type === 'many2many') {
|
||||
var recordlist = this.localData[data[fieldName]];
|
||||
recordlist.orderedResIDs = null;
|
||||
for (var index in recordlist.data) {
|
||||
this.unfreezeOrder(recordlist.data[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
list.orderedResIDs = null;
|
||||
this._sortList(list);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
@@ -1060,7 +1093,7 @@ var BasicModel = AbstractModel.extend({
|
||||
* not by list or kanban controllers.
|
||||
*
|
||||
* @private
|
||||
* @param {string} listID a valid handle for a list object
|
||||
* @param {Object} list a valid list object
|
||||
* @param {Object} [options]
|
||||
* @param {string} [options.position=top] if the new record should be added
|
||||
* on top or on bottom of the list
|
||||
@@ -1080,6 +1113,12 @@ var BasicModel = AbstractModel.extend({
|
||||
list._changes.push({operation: 'ADD', id: id, position: position, isNew: true});
|
||||
var record = self.localData[id];
|
||||
list._cache[record.res_id] = id;
|
||||
if (list.orderedResIDs) {
|
||||
var index = list.offset + (position !== 'top' ? list.limit : 0);
|
||||
list.orderedResIDs.splice(index, 0, record.res_id);
|
||||
// list could be a copy of the original one
|
||||
self.localData[list.id].orderedResIDs = list.orderedResIDs;
|
||||
}
|
||||
return id;
|
||||
});
|
||||
},
|
||||
@@ -1341,7 +1380,6 @@ var BasicModel = AbstractModel.extend({
|
||||
// TODO: before registering the changes, verify that the x2many
|
||||
// value has changed
|
||||
record._changes[name] = list.id;
|
||||
var shouldLoad = false;
|
||||
list._changes = list._changes || [];
|
||||
|
||||
// save it in case of a [5] which will remove the _changes
|
||||
@@ -1361,11 +1399,7 @@ var BasicModel = AbstractModel.extend({
|
||||
}
|
||||
if (command[0] === 1 && command[1]) {
|
||||
// updating an existing record
|
||||
recID = _.find(list.data, function (childID) {
|
||||
var child = self.localData[childID];
|
||||
return child.res_id === command[1];
|
||||
});
|
||||
rec = self.localData[recID];
|
||||
rec = self.localData[list._cache[command[1]]];
|
||||
}
|
||||
if (!rec) {
|
||||
var params = {
|
||||
@@ -1391,18 +1425,25 @@ var BasicModel = AbstractModel.extend({
|
||||
defs.push(self._applyOnChange(command[2], rec));
|
||||
} else if (command[0] === 4) {
|
||||
// LINK TO
|
||||
// the dataPoint id will be set when the record will be fetched (for now,
|
||||
// this dataPoint doesn't exist yet)
|
||||
list._changes.push({operation: 'ADD', id: null, resID: command[1]});
|
||||
shouldLoad = true;
|
||||
rec = self.localData[list._cache[command[1]]];
|
||||
if (rec) {
|
||||
// modifications done on a record are discarded if
|
||||
// the onchange uses a LINK TO
|
||||
self.discardChanges(rec.id);
|
||||
}
|
||||
// the dataPoint id will be set when the record will be
|
||||
// fetched (for now, this dataPoint may not exist yet)
|
||||
list._changes.push({
|
||||
operation: 'ADD',
|
||||
id: rec ? rec.id : null,
|
||||
resID: command[1],
|
||||
});
|
||||
} else if (command[0] === 5) {
|
||||
// DELETE ALL
|
||||
list._changes = [{operation: 'REMOVE_ALL'}];
|
||||
}
|
||||
});
|
||||
if (shouldLoad) {
|
||||
defs.push(self._readUngroupedList(list));
|
||||
}
|
||||
defs.push(self._readUngroupedList(list));
|
||||
} else {
|
||||
var newValue = self._parseServerValue(field, val);
|
||||
if (newValue !== oldValue) {
|
||||
@@ -1540,12 +1581,6 @@ var BasicModel = AbstractModel.extend({
|
||||
if (idInCommands && change.operation === 'ADD') {
|
||||
idsToRemove = _.without(idsToRemove, change.id);
|
||||
}
|
||||
// decrement _keepChangesUnsortedCount if we filter out an
|
||||
// operation whose index is smaller than the count (as there
|
||||
// will be one operation less to apply before sorting)
|
||||
if (idInCommands && index < list._keepChangesUnsortedCount) {
|
||||
list._keepChangesUnsortedCount--;
|
||||
}
|
||||
return idInCommands;
|
||||
});
|
||||
_.each(idsToRemove, function (id) {
|
||||
@@ -1653,18 +1688,7 @@ var BasicModel = AbstractModel.extend({
|
||||
break;
|
||||
}
|
||||
});
|
||||
var idsInRange;
|
||||
if (list.limit) {
|
||||
idsInRange = list.res_ids.slice(list.offset, list.offset + list.limit);
|
||||
} else {
|
||||
idsInRange = list.res_ids;
|
||||
}
|
||||
list.data = [];
|
||||
_.each(idsInRange, function (id) {
|
||||
if (list._cache[id]) {
|
||||
list.data.push(list._cache[id]);
|
||||
}
|
||||
});
|
||||
this._setDataInRange(list);
|
||||
return list;
|
||||
},
|
||||
/**
|
||||
@@ -2344,7 +2368,6 @@ var BasicModel = AbstractModel.extend({
|
||||
rawContext: rawContext,
|
||||
relationField: field.relation_field,
|
||||
viewType: view ? view.type : fieldInfo.viewType,
|
||||
_keepChangesUnsorted: fieldInfo.keepChangesUnsorted,
|
||||
});
|
||||
record.data[fieldName] = list.id;
|
||||
if (!fieldInfo.__no_fetch) {
|
||||
@@ -2624,6 +2647,7 @@ var BasicModel = AbstractModel.extend({
|
||||
}
|
||||
});
|
||||
list = this._applyX2ManyOperations(list);
|
||||
this._sortList(list);
|
||||
if (type === 'many2many' || list._forceM2MLink) {
|
||||
var relRecordCreated = _.filter(relRecordAdded, function (rec) {
|
||||
return typeof rec.res_id === 'string';
|
||||
@@ -3065,7 +3089,7 @@ var BasicModel = AbstractModel.extend({
|
||||
offset: params.offset || (type === 'record' ? _.indexOf(res_ids, res_id) : 0),
|
||||
openGroupByDefault: params.openGroupByDefault,
|
||||
orderedBy: params.orderedBy || [],
|
||||
_keepChangesUnsorted: params._keepChangesUnsorted,
|
||||
orderedResIDs: params.orderedResIDs,
|
||||
parentID: params.parentID,
|
||||
rawContext: params.rawContext,
|
||||
ref: params.ref || res_id,
|
||||
@@ -3444,6 +3468,81 @@ var BasicModel = AbstractModel.extend({
|
||||
return record;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Reads data from server for all missing fields.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} list a valid resource object
|
||||
* @param {interger[]} resIDs
|
||||
* @param {string[]} fieldNames to check and read if missing
|
||||
* @returns {Deferred<Object>}
|
||||
*/
|
||||
_readMissingFields: function (list, resIDs, fieldNames) {
|
||||
var self = this;
|
||||
|
||||
var missingIDs = [];
|
||||
for (var i = 0, len = resIDs.length; i < len; i++) {
|
||||
var resId = resIDs[i];
|
||||
var dataPointID = list._cache[resId];
|
||||
if (!dataPointID) {
|
||||
missingIDs.push(resId);
|
||||
continue;
|
||||
}
|
||||
var record = self.localData[dataPointID];
|
||||
var data = _.extend({}, record.data, record._changes);
|
||||
if (_.difference(fieldNames, _.keys(data)).length) {
|
||||
missingIDs.push(resId);
|
||||
}
|
||||
}
|
||||
|
||||
var def;
|
||||
if (missingIDs.length && fieldNames.length) {
|
||||
def = self._rpc({
|
||||
model: list.model,
|
||||
method: 'read',
|
||||
args: [missingIDs, fieldNames],
|
||||
context: list.getContext(),
|
||||
});
|
||||
} else {
|
||||
def = $.when(_.map(missingIDs, function (id) {
|
||||
return {id:id};
|
||||
}));
|
||||
}
|
||||
return def.then(function (records) {
|
||||
_.each(resIDs, function (id) {
|
||||
var dataPoint;
|
||||
var data = _.findWhere(records, {id: id});
|
||||
if (id in list._cache) {
|
||||
dataPoint = self.localData[list._cache[id]];
|
||||
if (data) {
|
||||
self._parseServerData(fieldNames, dataPoint, data);
|
||||
_.extend(dataPoint.data, data);
|
||||
}
|
||||
} else {
|
||||
dataPoint = self._makeDataPoint({
|
||||
context: list.context,
|
||||
data: data,
|
||||
fieldsInfo: list.fieldsInfo,
|
||||
fields: list.fields,
|
||||
modelName: list.model,
|
||||
parentID: list.id,
|
||||
viewType: list.viewType,
|
||||
});
|
||||
self._parseServerData(fieldNames, dataPoint, dataPoint.data);
|
||||
|
||||
// add many2one records
|
||||
list._cache[id] = dataPoint.id;
|
||||
}
|
||||
// set the dataPoint id in potential 'ADD' operation adding the current record
|
||||
_.each(list._changes, function (change) {
|
||||
if (change.operation === 'ADD' && !change.id && change.resID === id) {
|
||||
change.id = dataPoint.id;
|
||||
}
|
||||
});
|
||||
});
|
||||
return list;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* For a grouped list resource, this method fetches all group data by
|
||||
* performing a /read_group. It also tries to read open subgroups if they
|
||||
@@ -3503,6 +3602,7 @@ var BasicModel = AbstractModel.extend({
|
||||
aggregateValues: aggregateValues,
|
||||
groupedBy: list.groupedBy.slice(1),
|
||||
orderedBy: list.orderedBy,
|
||||
orderedResIDs: list.orderedResIDs,
|
||||
limit: list.limit,
|
||||
openGroupByDefault: list.openGroupByDefault,
|
||||
parentID: list.id,
|
||||
@@ -3555,70 +3655,44 @@ var BasicModel = AbstractModel.extend({
|
||||
*/
|
||||
_readUngroupedList: function (list) {
|
||||
var self = this;
|
||||
var def;
|
||||
var ids = [];
|
||||
var missingIds = [];
|
||||
// generate the current count and res_ids list by applying the changes
|
||||
var listWithChanges = this._applyX2ManyOperations(list);
|
||||
var currentCount = listWithChanges.count;
|
||||
var currentResIDs = listWithChanges.res_ids;
|
||||
var upper_bound = list.limit ? Math.min(list.offset + list.limit, currentCount) : currentCount;
|
||||
var fieldNames = list.getFieldNames();
|
||||
for (var i = list.offset; i < upper_bound; i++) {
|
||||
var id = currentResIDs[i];
|
||||
ids.push(id);
|
||||
if (!list._cache[id]) {
|
||||
missingIds.push(id);
|
||||
}
|
||||
}
|
||||
if (missingIds.length) {
|
||||
if (fieldNames.length) {
|
||||
def = this._rpc({
|
||||
model: list.model,
|
||||
method: 'read',
|
||||
args: [missingIds, fieldNames],
|
||||
context: list.getContext(),
|
||||
});
|
||||
} else {
|
||||
def = $.when(_.map(missingIds, function (id) {
|
||||
return {id:id};
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
def = $.when();
|
||||
}
|
||||
return def.then(function (records) {
|
||||
list.data = [];
|
||||
_.each(ids, function (id) {
|
||||
var dataPoint;
|
||||
if (id in list._cache) {
|
||||
dataPoint = self.localData[list._cache[id]];
|
||||
} else {
|
||||
dataPoint = self._makeDataPoint({
|
||||
context: list.context,
|
||||
data: _.findWhere(records, {id: id}),
|
||||
fieldsInfo: list.fieldsInfo,
|
||||
fields: list.fields,
|
||||
modelName: list.model,
|
||||
parentID: list.id,
|
||||
viewType: list.viewType,
|
||||
});
|
||||
var def = $.when();
|
||||
|
||||
// add many2one records
|
||||
self._parseServerData(fieldNames, dataPoint, dataPoint.data);
|
||||
list._cache[id] = dataPoint.id;
|
||||
}
|
||||
// set the dataPoint id in potential 'ADD' operation adding the current record
|
||||
_.each(list._changes, function (change) {
|
||||
if (change.operation === 'ADD' && !change.id && change.resID === id) {
|
||||
change.id = dataPoint.id;
|
||||
}
|
||||
// generate the current count and res_ids list by applying the changes
|
||||
list = this._applyX2ManyOperations(list);
|
||||
|
||||
// for multi-pages list datapoints, we might need to read the
|
||||
// order field first to apply the order on all pages
|
||||
if (list.res_ids.length > list.limit) {
|
||||
if (!list.orderedResIDs && list.orderedBy.length) {
|
||||
var fieldNames = _.pluck(list.orderedBy, 'name');
|
||||
def = this._readMissingFields(list, _.filter(list.res_ids, _.isNumber), fieldNames);
|
||||
def.then(function () {
|
||||
self._sortList(list);
|
||||
});
|
||||
if (_.contains(list.res_ids, id)) {
|
||||
list.data.push(dataPoint.id);
|
||||
}
|
||||
}
|
||||
return def.then(function () {
|
||||
var resIDs = [];
|
||||
// generate the current count and res_ids list by applying the changes
|
||||
var currentCount = list.count;
|
||||
var currentResIDs = list.res_ids;
|
||||
var upperBound = list.limit ? Math.min(list.offset + list.limit, currentCount) : currentCount;
|
||||
var fieldNames = list.getFieldNames();
|
||||
for (var i = list.offset; i < upperBound; i++) {
|
||||
var resId = currentResIDs[i];
|
||||
if (_.isNumber(resId)) {
|
||||
resIDs.push(resId);
|
||||
}
|
||||
}
|
||||
return self._readMissingFields(list, resIDs, fieldNames).then(function () {
|
||||
if (list.res_ids.length <= list.limit) {
|
||||
self._sortList(list);
|
||||
} else {
|
||||
// sortList has already been applied after first the read
|
||||
self._setDataInRange(list);
|
||||
}
|
||||
return list;
|
||||
});
|
||||
return list;
|
||||
});
|
||||
},
|
||||
/**
|
||||
@@ -3689,6 +3763,38 @@ var BasicModel = AbstractModel.extend({
|
||||
return list;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Set data in range, i.e. according to the list offset and limit.
|
||||
*
|
||||
* @param {Object} list
|
||||
*/
|
||||
_setDataInRange: function (list) {
|
||||
var idsInRange;
|
||||
if (list.limit) {
|
||||
idsInRange = list.res_ids.slice(list.offset, list.offset + list.limit);
|
||||
} else {
|
||||
idsInRange = list.res_ids;
|
||||
}
|
||||
list.data = [];
|
||||
_.each(idsInRange, function (id) {
|
||||
if (list._cache[id]) {
|
||||
list.data.push(list._cache[id]);
|
||||
}
|
||||
});
|
||||
|
||||
// display newly created record in addition to the displayed records
|
||||
if (list.limit) {
|
||||
for (var i = list.offset + list.limit; i < list.res_ids.length; i++) {
|
||||
var id = list.res_ids[i];
|
||||
var dataPointID = list._cache[id];
|
||||
if (_.findWhere(list._changes, {isNew: true, id: dataPointID})) {
|
||||
list.data.push(dataPointID);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Change the offset of a record. Note that this does not reload the data.
|
||||
* The offset is used to load a different record in a list of record (for
|
||||
@@ -3720,14 +3826,28 @@ var BasicModel = AbstractModel.extend({
|
||||
// only sort x2many lists
|
||||
return;
|
||||
}
|
||||
var self = this;
|
||||
|
||||
if (list.orderedBy.length) {
|
||||
var self = this;
|
||||
|
||||
if (list.orderedResIDs) {
|
||||
var orderedResIDs = {};
|
||||
for (var k = 0; k < list.orderedResIDs.length; k++) {
|
||||
orderedResIDs[list.orderedResIDs[k]] = k;
|
||||
}
|
||||
list.res_ids.sort(function compareResIdIndexes (resId1, resId2) {
|
||||
if (!(resId1 in orderedResIDs) && !(resId2 in orderedResIDs)) {
|
||||
return 0;
|
||||
}
|
||||
if (!(resId1 in orderedResIDs)) {
|
||||
return Infinity;
|
||||
}
|
||||
if (!(resId2 in orderedResIDs)) {
|
||||
return -Infinity;
|
||||
}
|
||||
return orderedResIDs[resId1] - orderedResIDs[resId2];
|
||||
});
|
||||
} else if (list.orderedBy.length) {
|
||||
// sort records according to ordered_by[0]
|
||||
var data = list.data;
|
||||
var res_ids = list.res_ids;
|
||||
var compareRecords = function (record1ID, record2ID, level) {
|
||||
var compareRecords = function (resId1, resId2, level) {
|
||||
if(!level) {
|
||||
level = 0;
|
||||
}
|
||||
@@ -3735,6 +3855,17 @@ var BasicModel = AbstractModel.extend({
|
||||
return 0;
|
||||
}
|
||||
var order = list.orderedBy[level];
|
||||
var record1ID = list._cache[resId1];
|
||||
var record2ID = list._cache[resId2];
|
||||
if (!record1ID && !record2ID) {
|
||||
return 0;
|
||||
}
|
||||
if (!record1ID) {
|
||||
return Infinity;
|
||||
}
|
||||
if (!record2ID) {
|
||||
return -Infinity;
|
||||
}
|
||||
var r1 = self.localData[record1ID];
|
||||
var r2 = self.localData[record2ID];
|
||||
var data1 = _.extend({}, r1.data, r1._changes);
|
||||
@@ -3747,17 +3878,9 @@ var BasicModel = AbstractModel.extend({
|
||||
}
|
||||
return compareRecords(record1ID, record2ID, level + 1);
|
||||
};
|
||||
data.sort(compareRecords);
|
||||
|
||||
// sort res_ids accordingly (only the current range of ids, the one
|
||||
// mapping the data, needs to be sorted)
|
||||
var preRangeIDs = res_ids.slice(0, list.offset); // resIDs before the range
|
||||
var postRangeIDs = res_ids.slice(list.offset + list.limit); // resIDs after the range
|
||||
var rangeIDs = _.map(data, function (dataPointID) {
|
||||
return self.localData[dataPointID].res_id;
|
||||
});
|
||||
list.res_ids = preRangeIDs.concat(rangeIDs).concat(postRangeIDs);
|
||||
list.res_ids.sort(compareRecords);
|
||||
}
|
||||
this._setDataInRange(list);
|
||||
},
|
||||
/**
|
||||
* Updates the res_ids of the parent of a given element of type list.
|
||||
|
||||
@@ -12,9 +12,10 @@ var qweb = core.qweb;
|
||||
|
||||
var FormController = BasicController.extend({
|
||||
custom_events: _.extend({}, BasicController.prototype.custom_events, {
|
||||
open_one2many_record: '_onOpenOne2ManyRecord',
|
||||
bounce_edit: '_onBounceEdit',
|
||||
button_clicked: '_onButtonClicked',
|
||||
freeze_order: '_onFreezeOrder',
|
||||
open_one2many_record: '_onOpenOne2ManyRecord',
|
||||
open_record: '_onOpenRecord',
|
||||
toggle_column_order: '_onToggleColumnOrder',
|
||||
}),
|
||||
@@ -297,6 +298,17 @@ var FormController = BasicController.extend({
|
||||
state.id = env.currentId;
|
||||
this._super(state);
|
||||
},
|
||||
/**
|
||||
* Calls unfreezeOrder when changing the mode.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_setMode: function (mode, recordID) {
|
||||
if ((recordID || this.handle) === this.handle) {
|
||||
this.model.unfreezeOrder(this.handle);
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Updates the controller's title according to the new state
|
||||
*
|
||||
@@ -443,6 +455,17 @@ var FormController = BasicController.extend({
|
||||
_onEdit: function () {
|
||||
this._setMode('edit');
|
||||
},
|
||||
/**
|
||||
* This method is called when someone tries to freeze the order, most likely
|
||||
* in a x2many list view
|
||||
*
|
||||
* @private
|
||||
* @param {OdooEvent} event
|
||||
*/
|
||||
_onFreezeOrder: function (event) {
|
||||
event.stopPropagation();
|
||||
this.model.freezeOrder(event.data.id);
|
||||
},
|
||||
/**
|
||||
* Opens a one2many record (potentially new) in a dialog. This handler is
|
||||
* o2m specific as in this case, the changes done on the related record
|
||||
@@ -517,10 +540,12 @@ var FormController = BasicController.extend({
|
||||
*/
|
||||
_onToggleColumnOrder: function (event) {
|
||||
event.stopPropagation();
|
||||
this.model.setSort(event.data.id, event.data.name);
|
||||
var field = event.data.field;
|
||||
var state = this.model.get(this.handle);
|
||||
this.renderer.confirmChange(state, state.id, [field]);
|
||||
var self = this;
|
||||
this.model.setSort(event.data.id, event.data.name).then(function () {
|
||||
var field = event.data.field;
|
||||
var state = self.model.get(self.handle);
|
||||
self.renderer.confirmChange(state, state.id, [field]);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -485,8 +485,10 @@ var ListController = BasicController.extend({
|
||||
if (!data.groupedBy) {
|
||||
this.pager.updateState({current_min: 1});
|
||||
}
|
||||
this.model.setSort(data.id, event.data.name);
|
||||
this.update({});
|
||||
var self = this;
|
||||
this.model.setSort(data.id, event.data.name).then(function () {
|
||||
self.update({});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* In a grouped list view, each group can be clicked on to open/close them.
|
||||
|
||||
@@ -2271,7 +2271,7 @@ QUnit.module('relational_fields', {
|
||||
});
|
||||
|
||||
QUnit.test('embedded one2many with handle widget', function (assert) {
|
||||
assert.expect(7);
|
||||
assert.expect(8);
|
||||
|
||||
this.data.partner.records[0].turtles = [1, 2, 3];
|
||||
|
||||
@@ -2321,6 +2321,15 @@ QUnit.module('relational_fields', {
|
||||
"should have the 3 rows in the new order");
|
||||
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
|
||||
assert.deepEqual(_.map(this.data.turtle.records, function(turtle) {
|
||||
return _.pick(turtle, 'id', 'turtle_foo', 'turtle_int');
|
||||
}), [
|
||||
{id: 1, turtle_foo: "yop", turtle_int: 1},
|
||||
{id: 2, turtle_foo: "blip", turtle_int:0},
|
||||
{id: 3, turtle_foo: "kawa", turtle_int:21}
|
||||
], "should have save the changed sequence");
|
||||
|
||||
assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipyopkawa",
|
||||
"should still have the 3 rows in the new order");
|
||||
|
||||
@@ -2415,7 +2424,7 @@ QUnit.module('relational_fields', {
|
||||
res_id: 1,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'write') {
|
||||
assert.deepEqual(args.args[1].turtles, [[1,1,{"turtle_int":2}],[4,2,false],[1,3,{"turtle_int":3}]],
|
||||
assert.deepEqual(args.args[1].turtles, [[4,2,false],[1,1,{"turtle_int":2}],[1,3,{"turtle_int":3}]],
|
||||
"should change all lines that have changed (the first one doesn't change because it has the same sequence)");
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
@@ -2444,9 +2453,20 @@ QUnit.module('relational_fields', {
|
||||
});
|
||||
|
||||
QUnit.test('onchange (with command 5) for embedded one2many with handle widget', function (assert) {
|
||||
assert.expect(2);
|
||||
assert.expect(3);
|
||||
|
||||
this.data.partner.records[0].turtles = [1, 2, 3];
|
||||
var ids = [];
|
||||
for (var i=10; i<50; i++) {
|
||||
var id = 10 + i;
|
||||
ids.push(id);
|
||||
this.data.turtle.records.push({
|
||||
id: id,
|
||||
turtle_int: 0,
|
||||
turtle_foo: "#" + id,
|
||||
});
|
||||
}
|
||||
ids.push(1, 2, 3);
|
||||
this.data.partner.records[0].turtles = ids;
|
||||
this.data.partner.onchanges = {
|
||||
turtles: function (obj) {
|
||||
obj.turtles = [[5]].concat(obj.turtles);
|
||||
@@ -2472,6 +2492,7 @@ QUnit.module('relational_fields', {
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
form.$('.o_pager_next').click();
|
||||
assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "yopblipkawa",
|
||||
"should have the 3 rows in the correct order");
|
||||
|
||||
@@ -2486,7 +2507,10 @@ QUnit.module('relational_fields', {
|
||||
{position: 'top'}
|
||||
);
|
||||
|
||||
assert.strictEqual(form.$('.o_data_cell').text(), "blurpkawablip", "should display to record in 'turtle_int' order");
|
||||
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
form.$('.o_pager_next').click();
|
||||
|
||||
assert.strictEqual(form.$('.o_data_cell:not(.o_handle_cell)').text(), "blurpkawablip",
|
||||
"should display to record in 'turtle_int' order");
|
||||
@@ -2494,6 +2518,126 @@ QUnit.module('relational_fields', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('onchange followed by edition on the second page', function (assert) {
|
||||
assert.expect(20);
|
||||
|
||||
var ids = [];
|
||||
for (var i=1; i<85; i++) {
|
||||
var id = 10 + i;
|
||||
ids.push(id);
|
||||
this.data.turtle.records.push({
|
||||
id: id,
|
||||
turtle_int: id/3|0,
|
||||
turtle_foo: "#" + i,
|
||||
});
|
||||
}
|
||||
ids.splice(41, 0, 1, 2, 3);
|
||||
this.data.partner.records[0].turtles = ids;
|
||||
this.data.partner.onchanges = {
|
||||
turtles: function (obj) {
|
||||
obj.turtles = [[5]].concat(obj.turtles);
|
||||
},
|
||||
};
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch:'<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<group>' +
|
||||
'<field name="turtles">' +
|
||||
'<tree editable="top" default_order="turtle_int">' +
|
||||
'<field name="turtle_int" widget="handle"/>' +
|
||||
'<field name="turtle_foo"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</group>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
form.$('.o_pager_next').click();
|
||||
|
||||
form.$('.o_field_one2many .o_list_view tbody tr:eq(1) td:first').click();
|
||||
form.$('.o_field_one2many .o_list_view tbody tr:eq(1) input:first').val('value 1').trigger('input');
|
||||
form.$('.o_field_one2many .o_list_view tbody tr:eq(2) td:first').click();
|
||||
form.$('.o_field_one2many .o_list_view tbody tr:eq(2) input:first').val('value 2').trigger('input');
|
||||
|
||||
assert.strictEqual(form.$('.o_data_row').length, 40, "should display 40 records");
|
||||
assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#39))').index(), 0, "should display '#39' at the first line");
|
||||
|
||||
form.$('.o_field_x2many_list_row_add a').click();
|
||||
|
||||
assert.strictEqual(form.$('.o_data_row').length, 40, "should display 39 records and the create line");
|
||||
assert.strictEqual(form.$('.o_data_row:first .o_field_char').length, 1, "should display the create line in first position");
|
||||
assert.strictEqual(form.$('.o_data_row:first .o_field_char').val(), "", "should an empty input");
|
||||
assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#39))').index(), 1, "should display '#39' at the second line");
|
||||
|
||||
form.$('.o_data_row input:first').val('value 3').trigger('input');
|
||||
|
||||
assert.strictEqual(form.$('.o_data_row:first .o_field_char').length, 1, "should display the create line in first position after onchange");
|
||||
assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#39))').index(), 1, "should display '#39' at the second line after onchange");
|
||||
|
||||
form.$('.o_field_x2many_list_row_add a').click();
|
||||
|
||||
assert.strictEqual(form.$('.o_data_row').length, 40, "should display 39 records and the create line");
|
||||
assert.strictEqual(form.$('.o_data_row:first .o_field_char').length, 1, "should display the create line in first position");
|
||||
assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(value 3))').index(), 1, "should display the created line at the second position");
|
||||
assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#39))').index(), 2, "should display '#39' at the third line");
|
||||
|
||||
form.destroy();
|
||||
|
||||
// bottom order
|
||||
|
||||
form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch:'<form string="Partners">' +
|
||||
'<sheet>' +
|
||||
'<group>' +
|
||||
'<field name="turtles">' +
|
||||
'<tree editable="bottom" default_order="turtle_int">' +
|
||||
'<field name="turtle_int" widget="handle"/>' +
|
||||
'<field name="turtle_foo"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</group>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
form.$('.o_pager_next').click();
|
||||
|
||||
form.$('.o_field_one2many .o_list_view tbody tr:eq(1) td:first').click();
|
||||
form.$('.o_field_one2many .o_list_view tbody tr:eq(1) input:first').val('value 1').trigger('input');
|
||||
form.$('.o_field_one2many .o_list_view tbody tr:eq(2) td:first').click();
|
||||
form.$('.o_field_one2many .o_list_view tbody tr:eq(2) input:first').val('value 2').trigger('input');
|
||||
|
||||
assert.strictEqual(form.$('.o_data_row').length, 40, "should display 40 records");
|
||||
assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#77))').index(), 39, "should display '#77' at the last line");
|
||||
|
||||
form.$('.o_field_x2many_list_row_add a').click();
|
||||
|
||||
assert.strictEqual(form.$('.o_data_row').length, 40, "should display 39 records and the create line");
|
||||
assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#76))').index(), 38, "should display '#76' at the penultimate line");
|
||||
assert.strictEqual(form.$('.o_data_row:has(.o_field_char)').index(), 39, "should display the create line at the last position");
|
||||
|
||||
form.$('.o_data_row input:first').val('value 3').trigger('input');
|
||||
form.$('.o_field_x2many_list_row_add a').click();
|
||||
|
||||
assert.strictEqual(form.$('.o_data_row').length, 40, "should display 39 records and the create line");
|
||||
assert.strictEqual(form.$('.o_data_row:has(.o_data_cell:contains(#76))').index(), 38, "should display '#76' at the penultimate line");
|
||||
assert.strictEqual(form.$('.o_data_row:has(.o_field_char)').index(), 39, "should display the create line at the last position");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('embedded one2many with handle widget with minimum setValue calls', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(20);
|
||||
@@ -5656,9 +5800,9 @@ QUnit.module('relational_fields', {
|
||||
// enable the many2many onchange and generate it
|
||||
form.$('input.o_field_integer[name="int_field"]').val('10').trigger('input');
|
||||
|
||||
assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xphonexenomorphe",
|
||||
assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xenomorphexphone",
|
||||
'should display the product xphone and xenomorphe');
|
||||
assert.strictEqual(form.$('.o_data_row').text().replace(/\s+/g, ' '), "new linexphone first record firstxenomorphe second record ",
|
||||
assert.strictEqual(form.$('.o_data_row').text().replace(/\s+/g, ' '), "firstxenomorphe second record new linexphone first record ",
|
||||
'should display the name, one2many and many2many value');
|
||||
|
||||
// disable the many2many onchange
|
||||
@@ -5688,19 +5832,19 @@ QUnit.module('relational_fields', {
|
||||
// put list in readonly mode
|
||||
form.$('input.o_field_integer[name="int_field"]').click();
|
||||
|
||||
assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xphonexenomorphe",
|
||||
assert.strictEqual(form.$('.o_data_cell.o_required_modifier').text(), "xenomorphexphone",
|
||||
'should display the product xphone and xenomorphe');
|
||||
assert.strictEqual(form.$('.o_field_many2manytags:not(.o_input) .o_badge_text').text(), "first recordsecond record",
|
||||
assert.strictEqual(form.$('.o_field_many2manytags:not(.o_input) .o_badge_text').text(), "second recordfirst record",
|
||||
'should display the tag in readonly (first record and second record)');
|
||||
|
||||
form.$('input.o_field_integer[name="int_field"]').val('10').trigger('input');
|
||||
|
||||
assert.strictEqual(form.$('.o_data_row').text().replace(/\s+/g, ' '), "new linexphone first record firstxenomorphe second record ",
|
||||
assert.strictEqual(form.$('.o_data_row').text().replace(/\s+/g, ' '), "firstxenomorphe second record new linexphone first record ",
|
||||
'should display the name, one2many and many2many value');
|
||||
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
|
||||
assert.strictEqual(form.$('.o_data_row').text().replace(/\s+/g, ' '), "new linexphone first record firstxenomorphe second record ",
|
||||
assert.strictEqual(form.$('.o_data_row').text().replace(/\s+/g, ' '), "firstxenomorphe second record new linexphone first record ",
|
||||
'should display the name, one2many and many2many value after save');
|
||||
|
||||
form.destroy();
|
||||
@@ -6838,8 +6982,8 @@ QUnit.module('relational_fields', {
|
||||
mockRPC: function (route, args) {
|
||||
assert.step(args.method);
|
||||
if (args.method === 'write') {
|
||||
assert.strictEqual(args.args[1].turtles[42][0], 0);
|
||||
assert.deepEqual(args.args[1].turtles[42][2], {turtle_foo: 'rainbow dash'});
|
||||
assert.strictEqual(args.args[1].turtles[40][0], 0, 'should send a create command');
|
||||
assert.deepEqual(args.args[1].turtles[40][2], {turtle_foo: 'rainbow dash'});
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
@@ -6892,8 +7036,8 @@ QUnit.module('relational_fields', {
|
||||
mockRPC: function (route, args) {
|
||||
assert.step(args.method);
|
||||
if (args.method === 'write') {
|
||||
assert.strictEqual(args.args[1].turtles[0][0], 0);
|
||||
assert.deepEqual(args.args[1].turtles[0][2], {turtle_foo: 'rainbow dash'});
|
||||
assert.strictEqual(args.args[1].turtles[40][0], 0);
|
||||
assert.deepEqual(args.args[1].turtles[40][2], {turtle_foo: 'rainbow dash'});
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
@@ -6909,7 +7053,8 @@ QUnit.module('relational_fields', {
|
||||
form.$('.o_field_x2many_list_row_add a').click();
|
||||
|
||||
assert.strictEqual(form.$('tr.o_data_row').length, 3,
|
||||
"should have 3 data rows on the current page");
|
||||
"should have 3 data rows on the current page (2 records and the created line)");
|
||||
|
||||
assert.ok(form.$('tr.o_data_row').first().hasClass('o_selected_row'),
|
||||
"first row should be selected");
|
||||
|
||||
|
||||
@@ -989,7 +989,7 @@ QUnit.module('Views', {
|
||||
});
|
||||
|
||||
QUnit.test('use default_order on editable tree: sort on demand', function (assert) {
|
||||
assert.expect(8);
|
||||
assert.expect(11);
|
||||
|
||||
this.data.foo.records[0].o2m = [1, 3];
|
||||
this.data.bar.fields = {name: {string: "Name", type: "char", sortable: true}};
|
||||
@@ -1029,12 +1029,21 @@ QUnit.module('Views', {
|
||||
"Value 2 should be third (shouldn't be sorted)");
|
||||
|
||||
form.$('.o_form_sheet_bg').click(); // validate the row before sorting
|
||||
$o2m.find('.o_column_sortable').click();
|
||||
assert.ok(form.$('tbody tr:first td:contains(Value 3)').length,
|
||||
"Value 3 should be first");
|
||||
assert.ok(form.$('tbody tr:eq(1) td:contains(Value 2)').length,
|
||||
|
||||
$o2m.find('.o_column_sortable').click(); // resort list after edition
|
||||
assert.strictEqual(form.$('tbody tr:first').text(), 'Value 1',
|
||||
"Value 1 should be first");
|
||||
assert.strictEqual(form.$('tbody tr:eq(1)').text(), 'Value 2',
|
||||
"Value 2 should be second (should be sorted after saving)");
|
||||
assert.ok(form.$('tbody tr:eq(2) td:contains(Value 1)').length,
|
||||
assert.strictEqual(form.$('tbody tr:eq(2)').text(), 'Value 3',
|
||||
"Value 3 should be third");
|
||||
|
||||
$o2m.find('.o_column_sortable').click();
|
||||
assert.strictEqual(form.$('tbody tr:first').text(), 'Value 3',
|
||||
"Value 3 should be first");
|
||||
assert.strictEqual(form.$('tbody tr:eq(1)').text(), 'Value 2',
|
||||
"Value 2 should be second (should be sorted after saving)");
|
||||
assert.strictEqual(form.$('tbody tr:eq(2)').text(), 'Value 1',
|
||||
"Value 1 should be third");
|
||||
|
||||
form.destroy();
|
||||
@@ -1051,7 +1060,7 @@ QUnit.module('Views', {
|
||||
ids.push(id);
|
||||
this.data.bar.records.push({
|
||||
id: id,
|
||||
name: "Value " + id,
|
||||
name: "Value " + (id < 10 ? '0' : '') + id,
|
||||
});
|
||||
}
|
||||
this.data.foo.records[0].o2m = ids;
|
||||
@@ -1074,15 +1083,15 @@ QUnit.module('Views', {
|
||||
|
||||
// Change page
|
||||
form.$('.o_pager_next').click();
|
||||
assert.ok(form.$('tbody tr:first td:contains(Value 44)').length,
|
||||
assert.strictEqual(form.$('tbody tr:first').text(), 'Value 44',
|
||||
"record 44 should be first");
|
||||
assert.ok(form.$('tbody tr:eq(4) td:contains(Value 48)').length,
|
||||
assert.strictEqual(form.$('tbody tr:eq(4)').text(), 'Value 48',
|
||||
"record 48 should be last");
|
||||
|
||||
form.$('.o_column_sortable').click();
|
||||
assert.ok(form.$('tbody tr:first td:contains(Value 48)').length,
|
||||
assert.strictEqual(form.$('tbody tr:first').text(), 'Value 08',
|
||||
"record 48 should be first");
|
||||
assert.ok(form.$('tbody tr:eq(4) td:contains(Value 44)').length,
|
||||
assert.strictEqual(form.$('tbody tr:eq(4)').text(), 'Value 04',
|
||||
"record 44 should be first");
|
||||
|
||||
form.destroy();
|
||||
|
||||
Reference in New Issue
Block a user