diff options
| author | Christophe Matthieu <chm@odoo.com> | 2017-12-06 10:09:02 +0100 |
|---|---|---|
| committer | Martin Geubelle <mge@odoo.com> | 2018-01-05 17:34:25 +0100 |
| commit | b7ee1aa4b1ea906195c234020cb708a1da9de6a7 (patch) | |
| tree | d6c7263b6b4f40e89ede6b3a55b86909e9ffba15 /addons/web/static/src/js | |
| parent | d3cebdc4a2c8cb5c31cc56a79821b09a4d4712a8 (diff) | |
[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
Diffstat (limited to 'addons/web/static/src/js')
5 files changed, 301 insertions, 149 deletions
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 9d8a7b6e170..c399ffebe88 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -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, diff --git a/addons/web/static/src/js/services/data_manager.js b/addons/web/static/src/js/services/data_manager.js index 501961d6d76..9b11f3a54a7 100644 --- a/addons/web/static/src/js/services/data_manager.js +++ b/addons/web/static/src/js/services/data_manager.js @@ -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) { diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 39f1be0d107..8a9fd496b39 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -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) { @@ -953,6 +939,20 @@ var BasicModel = AbstractModel.extend({ 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 * not sent to the basic model, but the record should still be flagged as @@ -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, @@ -3445,6 +3469,81 @@ var BasicModel = AbstractModel.extend({ }); }, /** + * 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 * were open before. @@ -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 = []; + var def = $.when(); + // 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(), + 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); }); - } 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, - }); - - // add many2one records - self._parseServerData(fieldNames, dataPoint, dataPoint.data); - list._cache[id] = 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); } - // 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; - } - }); - if (_.contains(list.res_ids, id)) { - list.data.push(dataPoint.id); + } + 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; }); }, /** @@ -3690,6 +3764,38 @@ var BasicModel = AbstractModel.extend({ }); }, /** + * 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 * example, a form view with a pager. Clicking on next/previous actually @@ -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. diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js index 88e8a99de53..b581c4354dc 100644 --- a/addons/web/static/src/js/views/form/form_controller.js +++ b/addons/web/static/src/js/views/form/form_controller.js @@ -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', }), @@ -298,6 +299,17 @@ var FormController = BasicController.extend({ 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 * * @override @@ -444,6 +456,17 @@ var FormController = BasicController.extend({ 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 * shouldn't be saved in DB when the user clicks on 'Save' in the dialog, @@ -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]); + }); }, }); diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js index 5c6b7ea414d..982d5456bca 100644 --- a/addons/web/static/src/js/views/list/list_controller.js +++ b/addons/web/static/src/js/views/list/list_controller.js @@ -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. |
