summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-01-03 14:05:48 +0100
committerChristophe Simonis <chs@odoo.com>2018-01-03 14:05:48 +0100
commit55b810dc70cccb23144986d54fd28bb5f91f2b1b (patch)
treea83dda56dac7f02be26f6821dea5b2f1926ea7b0 /addons/web/static/src/js
parent8bc7cebd9c19ae8f77378d115a8401d7b7de56b0 (diff)
parent726b91b9fbc994c6b2f2cb45e36d4c224497f11b (diff)
[MERGE] forward port branch saas-11.1 up to 726b91b9fbc
Diffstat (limited to 'addons/web/static/src/js')
-rw-r--r--addons/web/static/src/js/chrome/action_manager.js3
-rw-r--r--addons/web/static/src/js/core/qweb.js15
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js4
-rw-r--r--addons/web/static/src/js/fields/field_utils.js2
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js53
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js31
-rw-r--r--addons/web/static/src/js/views/basic/basic_renderer.js47
-rw-r--r--addons/web/static/src/js/views/graph/graph_model.js5
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_column.js2
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_renderer.js11
-rw-r--r--addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js19
-rw-r--r--addons/web/static/src/js/views/list/list_editable_renderer.js57
-rw-r--r--addons/web/static/src/js/views/list/list_renderer.js3
-rw-r--r--addons/web/static/src/js/views/list/list_view.js2
14 files changed, 180 insertions, 74 deletions
diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js
index c9eed9b2e64..e72db2c3a0c 100644
--- a/addons/web/static/src/js/chrome/action_manager.js
+++ b/addons/web/static/src/js/chrome/action_manager.js
@@ -941,7 +941,7 @@ var ActionManager = Widget.extend({
});
});
},
- ir_actions_act_url: function (action) {
+ ir_actions_act_url: function (action, options) {
var url = action.url;
if (session.debug && url && url.length && url[0] === '/') {
url = $.param.querystring(url, {debug: session.debug});
@@ -952,6 +952,7 @@ var ActionManager = Widget.extend({
return $.Deferred(); // The action is finished only when the redirection is done
} else {
window.open(url, '_blank');
+ options.on_close();
}
return $.when();
},
diff --git a/addons/web/static/src/js/core/qweb.js b/addons/web/static/src/js/core/qweb.js
index 50742075e05..253e948d8ac 100644
--- a/addons/web/static/src/js/core/qweb.js
+++ b/addons/web/static/src/js/core/qweb.js
@@ -5,7 +5,18 @@ var translation = require('web.translation');
var _t = translation._t;
-function QWeb(debug, default_dict) {
+/**
+ * @param {boolean} debug
+ * @param {Object} default_dict
+ * @param {boolean} [enableTranslation=true] if true (this is the default),
+ * the rendering will translate all strings that are not marked with
+ * t-translation=off. This is useful for the kanban view, which uses a
+ * template which is already translated by the server
+ */
+function QWeb(debug, default_dict, enableTranslation) {
+ if (enableTranslation === undefined) {
+ enableTranslation = true;
+ }
var qweb = new QWeb2.Engine();
qweb.default_dict = _.extend({}, default_dict || {}, {
'_' : _,
@@ -16,7 +27,7 @@ function QWeb(debug, default_dict) {
'csrf_token': odoo.csrf_token,
});
qweb.debug = debug;
- qweb.preprocess_node = preprocess_node;
+ qweb.preprocess_node = enableTranslation ? preprocess_node : function () {};
return qweb;
}
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index de5f80b0e92..eb56844de14 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -576,8 +576,8 @@ var FieldMonetary = InputField.extend({
this.tagName = 'div';
this.className += ' o_input';
- // use the formatFloat function in edit
- this.formatType = 'float';
+ // do not display currency symbol in edit
+ this.formatOptions.noSymbol = true;
}
this.formatOptions.currency = this.currency;
diff --git a/addons/web/static/src/js/fields/field_utils.js b/addons/web/static/src/js/fields/field_utils.js
index bca74acc088..932ea997e6a 100644
--- a/addons/web/static/src/js/fields/field_utils.js
+++ b/addons/web/static/src/js/fields/field_utils.js
@@ -290,7 +290,7 @@ function formatMonetary(value, field, options) {
digits: (currency && currency.digits) || options.digits,
});
- if (!currency) {
+ if (!currency || options.noSymbol) {
return formatted_value;
}
if (currency.position === "after") {
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index 52654c4ce3a..677663a4967 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -95,6 +95,7 @@ var FieldMany2One = AbstractField.extend({
template: 'FieldMany2One',
custom_events: _.extend({}, AbstractField.prototype.custom_events, {
'closed_unset': '_onDialogClosedUnset',
+ 'field_changed': '_onFieldChanged',
'quick_create': '_onQuickCreate',
'search_create_popup': '_onSearchCreatePopup',
}),
@@ -124,6 +125,12 @@ var FieldMany2One = AbstractField.extend({
// 'recordParams' is a dict of params used when calling functions
// 'getDomain' and 'getContext' on this.record
this.recordParams = {fieldName: this.name, viewType: this.viewType};
+ // We need to know if the widget is dirty (i.e. if the user has changed
+ // the value, and those changes haven't been acknowledged yet by the
+ // environment), to prevent erasing that new value on a reset (e.g.
+ // coming by an onchange on another field)
+ this.isDirty = false;
+ this.lastChangeEvent = undefined;
},
start: function () {
// booleean indicating that the content of the input isn't synchronized
@@ -151,9 +158,31 @@ var FieldMany2One = AbstractField.extend({
* TODO
*/
reinitialize: function (value) {
+ this.isDirty = false;
this.floating = false;
this._setValue(value);
},
+ /**
+ * Re-renders the widget if it isn't dirty. The widget is dirty if the user
+ * changed the value, and that change hasn't been acknowledged yet by the
+ * environment. For example, another field with an onchange has been updated
+ * and this field is updated before the onchange returns. Two '_setValue'
+ * are done (this is sequential), the first one returns and this widget is
+ * reset. However, it has pending changes, so we don't re-render.
+ *
+ * @override
+ */
+ reset: function (record, event) {
+ this._reset(record, event);
+ if (!event || event === this.lastChangeEvent) {
+ this.isDirty = false;
+ }
+ if (this.isDirty) {
+ return $.when();
+ } else {
+ return this._render();
+ }
+ },
//--------------------------------------------------------------------------
// Private
@@ -238,6 +267,16 @@ var FieldMany2One = AbstractField.extend({
return value.split('\n')[0];
},
/**
+ * Listens to events 'field_changed' to keep track of the last event that
+ * has been trigerred. This allows to detect that all changes have been
+ * acknowledged by the environment.
+ *
+ * @param {OdooEvent} event 'field_changed' event
+ */
+ _onFieldChanged: function (event) {
+ this.lastChangeEvent = event;
+ },
+ /**
* @private
* @param {string} name
* @returns {Deferred} resolved after the name_create or when the slowcreate
@@ -476,6 +515,7 @@ var FieldMany2One = AbstractField.extend({
* @private
*/
_onDialogClosedUnset: function () {
+ this.isDirty = false;
this.floating = false;
this._render();
},
@@ -544,6 +584,7 @@ var FieldMany2One = AbstractField.extend({
// confirmation that the many2one is not properly set.
return;
}
+ this.isDirty = true;
if (this.$input.val() === "") {
this.reinitialize(false);
} else if (this._getDisplayName(this.m2o_value) !== this.$input.val()) {
@@ -796,7 +837,7 @@ var FieldX2Many = AbstractField.extend({
this.currentColInvisibleFields = this._evalColumnInvisibleFields();
this.renderer = new ListRenderer(this, this.value, {
arch: arch,
- mode: this.mode,
+ editable: this.mode === 'edit' && arch.attrs.editable,
addCreateLine: !this.isReadonly && this.activeActions.create,
addTrashIcon: !this.isReadonly && this.activeActions.delete,
isMany2Many: this.isMany2Many,
@@ -1337,12 +1378,12 @@ var FieldMany2ManyBinaryMultiFiles = AbstractField.extend({
throw _.str.sprintf(msg, this.field.string);
}
+ this.uploadedFiles = {};
this.uploadingFiles = [];
this.fileupload_id = _.uniqueId('oe_fileupload_temp');
$(window).on(this.fileupload_id, this._onFileLoaded.bind(this));
this.metadata = {};
- this._generatedMetadata();
},
destroy: function () {
@@ -1372,10 +1413,10 @@ var FieldMany2ManyBinaryMultiFiles = AbstractField.extend({
_generatedMetadata: function () {
var self = this;
_.each(this.value.data, function (record) {
- // attachments are tagged `allowUnlink` because only new attachments
- // will be unlinked after deletion
+ // tagging `allowUnlink` ascertains if the attachment was user
+ // uploaded or was an existing or system generated attachment
self.metadata[record.id] = {
- allowUnlink: false,
+ allowUnlink: self.uploadedFiles[record.data.id] || false,
url: self._getFileUrl(record.data),
};
});
@@ -1387,6 +1428,7 @@ var FieldMany2ManyBinaryMultiFiles = AbstractField.extend({
_render: function () {
// render the attachments ; as the attachments will changes after each
// _setValue, we put the rendering here to ensure they will be updated
+ this._generatedMetadata();
this.$('.oe_placeholder_files, .oe_attachments')
.replaceWith($(qweb.render('FieldBinaryFileUploader.files', {
widget: this,
@@ -1494,6 +1536,7 @@ var FieldMany2ManyBinaryMultiFiles = AbstractField.extend({
self.do_warn(_t('Uploading Error'), file.error);
} else {
attachment_ids.push(file.id);
+ self.uploadedFiles[file.id] = true;
}
});
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 c16f635a604..39f1be0d107 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -1495,7 +1495,8 @@ var BasicModel = AbstractModel.extend({
def = this._rpc({
model: list.model,
method: 'read',
- args: [_.pluck(data, 'id'), fieldNames]
+ args: [_.pluck(data, 'id'), fieldNames],
+ context: record.context,
}).then(function (records) {
_.each(records, function (record) {
list_records[record.id].data = record;
@@ -1743,22 +1744,21 @@ var BasicModel = AbstractModel.extend({
/**
* Fetch all name_gets for the many2ones in a group
*
- * @param {Object} group a valid resource object
+ * @param {Object[]} groups a list of object with context and record sub keys
* @returns {Deferred}
*/
- _fetchMany2OneGroup: function (group) {
- var ids = _.uniq(_.pluck(group, 'res_id'));
-
+ _fetchMany2OneGroup: function (groups) {
+ var ids = _.uniq(_.pluck(_.pluck(groups, 'record'), 'res_id'));
return this._rpc({
- model: group[0].model,
+ model: groups[0].record.model,
method: 'name_get',
args: [ids],
- context: group[0].context
+ context: groups[0].context
})
.then(function (name_gets) {
- _.each(group, function (record) {
- var nameGet = _.find(name_gets, function (n) { return n[0] === record.res_id;});
- record.data.display_name = nameGet[1];
+ _.each(groups, function (obj) {
+ var nameGet = _.find(name_gets, function (n) { return n[0] === obj.record.res_id;});
+ obj.record.data.display_name = nameGet[1];
});
});
},
@@ -2019,14 +2019,17 @@ var BasicModel = AbstractModel.extend({
if (!relatedRecord) {
return;
}
- toBeFetched.push(relatedRecord);
+ toBeFetched.push({
+ context: record.getContext({fieldName: name, viewType: record.viewType}),
+ record: relatedRecord
+ });
}
});
// group them by model and context. Using the context as key is
// necessary to make sure the correct context is used for the rpc;
- var groups = _.groupBy(toBeFetched, function (rec) {
- return [rec.model, JSON.stringify(rec.context)].join();
+ var groups = _.groupBy(toBeFetched, function (elem) {
+ return [elem.record.model, JSON.stringify(elem.context)].join();
});
return $.when.apply($, _.map(groups, this._fetchMany2OneGroup.bind(this)));
@@ -2416,7 +2419,7 @@ var BasicModel = AbstractModel.extend({
model: field.relation,
method: 'read',
args: [ids, fieldNames],
- context: {}, // FIXME
+ context: list.getContext() || {},
});
} else {
def = $.when(_.map(ids, function (id) {
diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js
index a6d5172bfe1..042d85d414a 100644
--- a/addons/web/static/src/js/views/basic/basic_renderer.js
+++ b/addons/web/static/src/js/views/basic/basic_renderer.js
@@ -251,13 +251,12 @@ var BasicRenderer = AbstractRenderer.extend({
function _apply(element) {
// If the view is in edit mode and that a widget have to switch
// its "readonly" state, we have to re-render it completely
- if ('readonly' in modifiers
- && self.mode === "edit"
- && element.widget
- && (element.widget.mode === 'readonly') !== modifiers.readonly)
- {
- self._rerenderFieldWidget(element.widget, record);
- return; // Rerendering already applied the modifiers, no need to go further
+ if ('readonly' in modifiers && element.widget) {
+ var mode = modifiers.readonly ? 'readonly' : modifiersData.baseMode;
+ if (mode !== element.widget.mode) {
+ self._rerenderFieldWidget(element.widget, record, mode);
+ return; // Rerendering already applied the modifiers, no need to go further
+ }
}
// Toggle modifiers CSS classes if necessary
@@ -429,6 +428,12 @@ var BasicRenderer = AbstractRenderer.extend({
this.allModifiersData.push(modifiersData);
}
}
+ // we register here the base mode of the node. This is a field widget
+ // specific settings which represents the generic mode for the widget,
+ // regardless of its modifiers. The interesting case is the list view:
+ // all widgets are supposed to be in the baseMode 'readonly', except the
+ // ones that are in the line that is currently being edited.
+ modifiersData.baseMode = (options && options.mode) || this.mode;
// Evaluate if necessary
if (!modifiersData.evaluatedModifiers[record.id]) {
@@ -454,7 +459,7 @@ var BasicRenderer = AbstractRenderer.extend({
}
modifiersData.elementsByRecord[record.id].push(newElement);
- this._applyModifiers(modifiersData, record, newElement);
+ this._applyModifiers(modifiersData, record, newElement, options);
}
return modifiersData.evaluatedModifiers[record.id];
@@ -493,22 +498,20 @@ var BasicRenderer = AbstractRenderer.extend({
* @param {Object} node
* @param {Object} record
* @param {Object} [options]
- * @param {Object} [modifiersOptions]
* @returns {jQueryElement}
*/
- _renderFieldWidget: function (node, record, options, modifiersOptions) {
+ _renderFieldWidget: function (node, record, options) {
var fieldName = node.attrs.name;
-
// Register the node-associated modifiers
- var modifiers = this._registerModifiers(node, record);
-
+ var mode = options && options.mode || this.mode;
+ var modifiers = this._registerModifiers(node, record, null, options);
// Initialize and register the widget
// Readonly status is known as the modifiers have just been registered
var Widget = record.fieldsInfo[this.viewType][fieldName].Widget;
- var widget = new Widget(this, fieldName, record, _.extend({
- mode: modifiers.readonly ? 'readonly' : this.mode,
+ var widget = new Widget(this, fieldName, record, {
+ mode: modifiers.readonly ? 'readonly' : mode,
viewType: this.viewType,
- }, options || {}));
+ });
// Register the widget so that it can easily be found again
if (this.allFieldWidgets[record.id] === undefined) {
@@ -534,15 +537,16 @@ var BasicRenderer = AbstractRenderer.extend({
if (async) {
$el.replaceWith(widget.$el);
}
- self._registerModifiers(node, record, widget, _.extend({
+ self._registerModifiers(node, record, widget, {
callback: function (element, modifiers, record) {
element.$el.toggleClass('o_field_empty', !!(
record.data.id
- && (modifiers.readonly || self.mode === 'readonly')
+ && (modifiers.readonly || mode === 'readonly')
&& !element.widget.isSet()
));
},
- }, modifiersOptions || {}));
+ mode: mode
+ });
self._postProcessField(widget, node);
});
@@ -610,10 +614,11 @@ var BasicRenderer = AbstractRenderer.extend({
* @private
* @param {Widget} widget
* @param {Object} record
+ * @param {string} mode either 'readonly' or 'edit'
*/
- _rerenderFieldWidget: function (widget, record) {
+ _rerenderFieldWidget: function (widget, record, mode) {
// Render the new field widget
- var $el = this._renderFieldWidget(widget.__node, record);
+ var $el = this._renderFieldWidget(widget.__node, record, {mode: mode});
widget.$el.replaceWith($el);
// Destroy the old widget and position the new one at the old one's
diff --git a/addons/web/static/src/js/views/graph/graph_model.js b/addons/web/static/src/js/views/graph/graph_model.js
index a6d2b057d0e..0518e279637 100644
--- a/addons/web/static/src/js/views/graph/graph_model.js
+++ b/addons/web/static/src/js/views/graph/graph_model.js
@@ -67,7 +67,6 @@ return AbstractModel.extend({
domain: params.domain,
context: params.context,
};
- this.defaultGroupedBy = params.groupedBy;
return this._loadGraph();
},
/**
@@ -96,7 +95,7 @@ return AbstractModel.extend({
this.chart.domain = params.domain;
}
if ('groupBy' in params) {
- this.chart.groupedBy = params.groupBy.length ? params.groupBy : this.defaultGroupedBy;
+ this.chart.groupedBy = params.groupBy.length ? params.groupBy : this.initialGroupBys;
}
if ('measure' in params) {
this.chart.measure = params.measure;
@@ -121,7 +120,7 @@ return AbstractModel.extend({
* @returns {Deferred}
*/
_loadGraph: function () {
- var groupedBy = this.chart.groupedBy.length ? this.chart.groupedBy : this.initialGroupBys;
+ var groupedBy = this.chart.groupedBy;
var fields = _.map(groupedBy, function (groupBy) {
return groupBy.split(':')[0];
});
diff --git a/addons/web/static/src/js/views/kanban/kanban_column.js b/addons/web/static/src/js/views/kanban/kanban_column.js
index d39a3b280e1..0e50e37194f 100644
--- a/addons/web/static/src/js/views/kanban/kanban_column.js
+++ b/addons/web/static/src/js/views/kanban/kanban_column.js
@@ -41,7 +41,7 @@ var KanbanColumn = Widget.extend({
this.data = data;
var value = data.value;
- this.id = data.res_id || value;
+ this.id = data.res_id;
this.folded = !data.isOpen;
this.has_active_field = 'active' in data.fields;
this.fields = data.fields;
diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js
index 029cef7cf0b..0f91fb2ae14 100644
--- a/addons/web/static/src/js/views/kanban/kanban_renderer.js
+++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js
@@ -94,7 +94,7 @@ var KanbanRenderer = BasicRenderer.extend({
this._super.apply(this, arguments);
this.widgets = [];
- this.qweb = new QWeb(session.debug, {_s: session.origin});
+ this.qweb = new QWeb(session.debug, {_s: session.origin}, false);
var templates = findInNode(this.arch, function (n) { return n.tag === 'templates';});
transformQwebTemplate(templates, state.fields);
this.qweb.add_template(utils.json_node_to_xml(templates));
@@ -235,6 +235,10 @@ var KanbanRenderer = BasicRenderer.extend({
}
});
+ // remove previous sorting
+ if(this.$el.sortable('instance') !== undefined) {
+ this.$el.sortable('destroy');
+ }
if (this.groupedByM2O) {
// Enable column sorting
this.$el.sortable({
@@ -249,7 +253,10 @@ var KanbanRenderer = BasicRenderer.extend({
stop: function () {
var ids = [];
self.$('.o_kanban_group').each(function (index, u) {
- ids.push($(u).data('id'));
+ // Ignore 'Undefined' column
+ if (_.isNumber($(u).data('id'))) {
+ ids.push($(u).data('id'));
+ }
});
self.trigger_up('resequence_columns', {ids: ids});
},
diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js b/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js
index e2b08fc0da9..7614d6bca95 100644
--- a/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js
+++ b/addons/web/static/src/js/views/kanban/kanban_renderer_mobile.js
@@ -12,6 +12,7 @@ var config = require('web.config');
var core = require('web.core');
var KanbanRenderer = require('web.KanbanRenderer');
+var _t = core._t;
var qweb = core.qweb;
if (!config.device.isMobile) {
@@ -107,8 +108,9 @@ KanbanRenderer.include({
var updateFunc = animate ? 'animate' : 'css';
self.$('.o_kanban_mobile_tab').removeClass('o_current');
_.each(self.widgets, function (column, index) {
- var $column = self.$('.o_kanban_group[data-id=' + column.id + ']');
- var $tab = self.$('.o_kanban_mobile_tab[data-id=' + column.id + ']');
+ var columnID = column.id || column.db_id;
+ var $column = self.$('.o_kanban_group[data-id="' + columnID + '"]');
+ var $tab = self.$('.o_kanban_mobile_tab[data-id="' + columnID + '"]');
if (index === moveToIndex - 1) {
$column[updateFunc]({left: '-100%'});
$tab[updateFunc]({left: '0%'});
@@ -137,8 +139,19 @@ KanbanRenderer.include({
* @private
*/
_renderGrouped: function (fragment) {
+ var data = [];
+ _.each(this.state.data, function (group) {
+ if (!group.value) {
+ group = _.extend({}, group, {value: _t('Undefined')});
+ data.unshift(group);
+ }
+ else {
+ data.push(group);
+ }
+ });
+
var $tabs = $(qweb.render('KanbanView.MobileTabs', {
- data: this.state.data,
+ data: data,
}));
$tabs.appendTo(fragment);
return this._super.apply(this, arguments);
diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js
index 964b7ba7bbe..91a914be8df 100644
--- a/addons/web/static/src/js/views/list/list_editable_renderer.js
+++ b/addons/web/static/src/js/views/list/list_editable_renderer.js
@@ -58,7 +58,7 @@ ListRenderer.include({
* @returns {Deferred}
*/
start: function () {
- if (this.mode === 'edit') {
+ if (this._isEditable()) {
this.$el.css({height: '100%'});
core.bus.on('click', this, this._onWindowClicked.bind(this));
}
@@ -299,12 +299,7 @@ ListRenderer.include({
renderInvisible: editMode,
renderWidgets: editMode,
};
- if (!editMode) {
- // Force 'readonly' mode for widgets in readonly rows as
- // otherwise they default to the view mode which is 'edit' for
- // an editable list view
- options.mode = 'readonly';
- }
+ options.mode = editMode ? 'edit' : 'readonly';
// Switch each cell to the new mode; note: the '_renderBodyCell'
// function might fill the 'this.defs' variables with multiple deferred
@@ -421,7 +416,7 @@ ListRenderer.include({
* @returns {boolean}
*/
_isEditable: function () {
- return this.mode === 'edit' && !this.state.groupedBy.length && this.arch.attrs.editable;
+ return !this.state.groupedBy.length && this.editable;
},
/**
* Move the cursor on the end of the previous line, if possible.
@@ -554,21 +549,49 @@ ListRenderer.include({
var row = _.findWhere(rows, {id: movedRecordID});
var index0 = rows.indexOf(row);
var index1 = ui.item.index();
- rows = rows.slice(Math.min(index0, index1), Math.max(index0, index1) + 1);
- rows = _.without(rows, row);
- if (index0 > index1) {
- rows.unshift(row);
+ var lower = Math.min(index0, index1);
+ var upper = Math.max(index0, index1) + 1;
+
+ var order = _.findWhere(self.state.orderedBy, {name: self.handleField});
+ var asc = !order || order.asc;
+ var reorderAll = false;
+ var sequence = (asc ? -1 : 1) * Infinity;
+
+ // determine if we need to reorder all lines
+ _.each(rows, function (row, index) {
+ if ((index < lower || index >= upper) &&
+ ((asc && sequence >= row.data[self.handleField]) ||
+ (!asc && sequence <= row.data[self.handleField]))) {
+ reorderAll = true;
+ }
+ sequence = row.data[self.handleField];
+ });
+
+ if (reorderAll) {
+ rows = _.without(rows, row);
+ rows.splice(index1, 0, row);
} else {
- rows.push(row);
+ rows = rows.slice(lower, upper);
+ rows = _.without(rows, row);
+ if (index0 > index1) {
+ rows.unshift(row);
+ } else {
+ rows.push(row);
+ }
}
var sequences = _.pluck(_.pluck(rows, 'data'), self.handleField);
var rowIDs = _.pluck(rows, 'id');
- this.trigger_up('resequence', {
- rowIDs: rowIDs,
- offset: _.min(sequences),
- handleField: this.handleField,
+ if (!asc) {
+ rowIDs.reverse();
+ }
+ this.unselectRow().then(function () {
+ self.trigger_up('resequence', {
+ rowIDs: rowIDs,
+ offset: _.min(sequences),
+ handleField: self.handleField,
+ });
});
},
/**
diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js
index b836da253c3..6b9e4aebe56 100644
--- a/addons/web/static/src/js/views/list/list_renderer.js
+++ b/addons/web/static/src/js/views/list/list_renderer.js
@@ -59,6 +59,7 @@ var ListRenderer = BasicRenderer.extend({
this.hasSelectors = params.hasSelectors;
this.selection = [];
this.pagers = []; // instantiated pagers (only for grouped lists)
+ this.editable = params.editable;
},
//--------------------------------------------------------------------------
@@ -260,7 +261,7 @@ var ListRenderer = BasicRenderer.extend({
// We register modifiers on the <td> element so that it gets the correct
// modifiers classes (for styling)
- var modifiers = this._registerModifiers(node, record, $td);
+ var modifiers = this._registerModifiers(node, record, $td, _.pick(options, 'mode'));
// If the invisible modifiers is true, the <td> element is left empty.
// Indeed, if the modifiers was to change the whole cell would be
// rerendered anyway.
diff --git a/addons/web/static/src/js/views/list/list_view.js b/addons/web/static/src/js/views/list/list_view.js
index b2295affb98..8a989dad35e 100644
--- a/addons/web/static/src/js/views/list/list_view.js
+++ b/addons/web/static/src/js/views/list/list_view.js
@@ -48,7 +48,7 @@ var ListView = BasicView.extend({
this.rendererParams.arch = arch;
this.rendererParams.hasSelectors =
'hasSelectors' in params ? params.hasSelectors : true;
- this.rendererParams.mode = mode;
+ this.rendererParams.editable = params.readonly ? false : arch.attrs.editable;
this.loadParams.limit = this.loadParams.limit || 80;
this.loadParams.type = 'list';