summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static')
-rw-r--r--addons/web/static/src/js/chrome/control_panel.js46
-rw-r--r--addons/web/static/src/js/chrome/search_menus.js2
-rw-r--r--addons/web/static/src/js/core/dom.js6
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js10
-rw-r--r--addons/web/static/src/js/services/data_manager.js3
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js17
-rw-r--r--addons/web/static/src/js/views/list/list_editable_renderer.js54
-rw-r--r--addons/web/static/src/js/views/list/list_renderer.js11
-rw-r--r--addons/web/static/src/js/widgets/debug_manager.js4
-rw-r--r--addons/web/static/src/js/widgets/domain_selector.js2
-rw-r--r--addons/web/static/src/less/kanban_dashboard.less5
-rw-r--r--addons/web/static/src/less/kanban_view.less5
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js6
-rw-r--r--addons/web/static/tests/views/list_tests.js4
-rw-r--r--addons/web/static/tests/widgets/domain_selector_tests.js20
15 files changed, 117 insertions, 78 deletions
diff --git a/addons/web/static/src/js/chrome/control_panel.js b/addons/web/static/src/js/chrome/control_panel.js
index 8be394205d5..98ce7814b7a 100644
--- a/addons/web/static/src/js/chrome/control_panel.js
+++ b/addons/web/static/src/js/chrome/control_panel.js
@@ -10,32 +10,32 @@ odoo.define('web.ControlPanelMixin', function (require) {
* we get the time to refactor the control panel.
*
* For example, here is what a typical client action would need to do to add
- * support for a control panel with some buttons:
+ * support for a control panel with some buttons::
*
- * var ControlPanelMixin = require('web.ControlPanelMixin');
+ * var ControlPanelMixin = require('web.ControlPanelMixin');
*
- * var SomeClientAction = Widget.extend(ControlPanelMixin, {
- * ...
- * start: function () {
- * this._renderButtons();
- * this._updateControlPanel();
+ * var SomeClientAction = Widget.extend(ControlPanelMixin, {
* ...
- * },
- * do_show: function () {
- * ...
- * this._updateControlPanel();
- * },
- * _renderButtons: function () {
- * this.$buttons = $(QWeb.render('SomeTemplate.Buttons'));
- * this.$buttons.on('click', ...);
- * },
- * _updateControlPanel: function () {
- * this.update_control_panel({
- * breadcrumbs: this.action_manager.get_breadcrumbs(),
- * cp_content: {
- * $buttons: this.$buttons,
- * },
- * });
+ * start: function () {
+ * this._renderButtons();
+ * this._updateControlPanel();
+ * ...
+ * },
+ * do_show: function () {
+ * ...
+ * this._updateControlPanel();
+ * },
+ * _renderButtons: function () {
+ * this.$buttons = $(QWeb.render('SomeTemplate.Buttons'));
+ * this.$buttons.on('click', ...);
+ * },
+ * _updateControlPanel: function () {
+ * this.update_control_panel({
+ * breadcrumbs: this.action_manager.get_breadcrumbs(),
+ * cp_content: {
+ * $buttons: this.$buttons,
+ * },
+ * });
*/
var ControlPanelMixin = {
need_control_panel: true,
diff --git a/addons/web/static/src/js/chrome/search_menus.js b/addons/web/static/src/js/chrome/search_menus.js
index eee7303ffbd..fe2ce5f3c32 100644
--- a/addons/web/static/src/js/chrome/search_menus.js
+++ b/addons/web/static/src/js/chrome/search_menus.js
@@ -415,7 +415,7 @@ return Widget.extend({
this.groupableFields = [];
var groupable_types = ['many2one', 'char', 'boolean', 'selection', 'date', 'datetime'];
_.each(fields, function (field, name) {
- if (field.store && _.contains(groupable_types, field.type)) {
+ if (field.sortable && _.contains(groupable_types, field.type)) {
self.groupableFields.push(_.extend({}, field, {name: name}));
}
});
diff --git a/addons/web/static/src/js/core/dom.js b/addons/web/static/src/js/core/dom.js
index b39f39cab9c..274f82bcc53 100644
--- a/addons/web/static/src/js/core/dom.js
+++ b/addons/web/static/src/js/core/dom.js
@@ -159,10 +159,8 @@ return {
/**
* Returns the selection range of an input or textarea
*
- * @param {Object} DOM item input or texteara
+ * @param {Object} node DOM item input or texteara
* @returns {Object} range
- * @returns {integer} range.start
- * @returns {integer} range.end
*/
getSelectionRange: function (node) {
return {
@@ -174,7 +172,7 @@ return {
* Returns the distance between a DOM element and the top-left corner of the
* window
*
- * @param {Object} node DOM element (input or texteara)
+ * @param {Object} e DOM element (input or texteara)
* @return {Object} the left and top distances in pixels
*/
getPosition: function (e) {
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index 0bbd1a50c1a..ed5f03b4f9b 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -245,7 +245,8 @@ var FieldMany2One = AbstractField.extend({
self.reinitialize({id: result[0], display_name: result[1]});
}
def.resolve();
- }).fail(function () {
+ }).fail(function (error, event) {
+ event.preventDefault();
slowCreate();
});
},
@@ -371,9 +372,14 @@ var FieldMany2One = AbstractField.extend({
}
// create and edit ...
if (create_enabled && !self.nodeOptions.no_create_edit) {
+ var createAndEditAction = function () {
+ // Clear the value in case the user clicks on discard
+ self.$('input').val('');
+ return self._searchCreatePopup("form", false, self._createContext(search_val));
+ };
values.push({
label: _t("Create and Edit..."),
- action: self._searchCreatePopup.bind(self, "form", false, self._createContext(search_val)),
+ action: createAndEditAction,
classname: 'o_m2o_dropdown_option',
});
} else if (values.length === 0) {
diff --git a/addons/web/static/src/js/services/data_manager.js b/addons/web/static/src/js/services/data_manager.js
index 48a0e2676ae..911e1c5582f 100644
--- a/addons/web/static/src/js/services/data_manager.js
+++ b/addons/web/static/src/js/services/data_manager.js
@@ -408,6 +408,9 @@ return core.Class.extend({
if (!_.isObject(node.attrs.modifiers)) {
node.attrs.modifiers = node.attrs.modifiers ? JSON.parse(node.attrs.modifiers) : {};
}
+ if (!_.isObject(node.attrs.options) && node.tag === 'button') {
+ node.attrs.options = node.attrs.options ? JSON.parse(node.attrs.options) : {};
+ }
if (node.tag === 'field') {
fieldsInfo[node.attrs.name] = self._processField(viewType,
fields[node.attrs.name], node.attrs ? _.clone(node.attrs) : {});
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 57c4a1f3743..5c6f5d2d851 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -198,7 +198,7 @@ var BasicModel = AbstractModel.extend({
* behind must be applied. This function applies changes stored in
* '_rawChanges' for a given viewType.
*
- * @param {string} id local resource id of a record
+ * @param {string} recordID local resource id of a record
* @param {string} viewType the current viewType
* @returns {Deferred<string>} resolves to the id of the record
*/
@@ -3666,10 +3666,16 @@ var BasicModel = AbstractModel.extend({
var self = this;
// sort records according to ordered_by[0]
- var order = list.orderedBy[0];
var data = list.data;
var res_ids = list.res_ids;
- data.sort(function (record1ID, record2ID) {
+ var compareRecords = function (record1ID, record2ID, level) {
+ if(!level) {
+ level = 0;
+ }
+ if(list.orderedBy.length < level + 1) {
+ return 0;
+ }
+ var order = list.orderedBy[level];
var r1 = self.localData[record1ID];
var r2 = self.localData[record2ID];
var data1 = _.extend({}, r1.data, r1._changes);
@@ -3680,8 +3686,9 @@ var BasicModel = AbstractModel.extend({
if (data1[order.name] > data2[order.name]) {
return order.asc ? 1 : -1;
}
- return 0;
- });
+ 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)
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 8f0e73cb466..9027fc3c984 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
@@ -47,7 +47,6 @@ ListRenderer.include({
this.addTrashIcon = params.addTrashIcon;
this.currentRow = null;
- this.currentCol = null;
this.currentFieldIndex = null;
},
/**
@@ -175,7 +174,7 @@ ListRenderer.include({
});
if (self.currentRow !== null) {
self.currentRow = newRowIndex;
- return self._selectCell(newRowIndex, self.currentCol, {force: true}).then(function () {
+ return self._selectCell(newRowIndex, self.currentFieldIndex, {force: true}).then(function () {
// restore the cursor position
currentRowID = self.state.data[newRowIndex].id;
currentWidget = self.allFieldWidgets[currentRowID][self.currentFieldIndex];
@@ -458,7 +457,6 @@ ListRenderer.include({
*/
_render: function () {
this.currentRow = null;
- this.currentCol = null;
this.currentFieldIndex = null;
return this._super.apply(this, arguments);
},
@@ -560,22 +558,22 @@ ListRenderer.include({
* is, if necessary.
*
* @param {integer} rowIndex
- * @param {integer} colIndex
+ * @param {integer} fieldIndex
* @param {Object} [options]
* @param {Event} [options.event] original target of the event which
* @param {boolean} [options.wrap=true] if true and no widget could be
* triggered the cell selection
- * selected from the colIndex to the last column, then we wrap around and
+ * selected from the fieldIndex to the last column, then we wrap around and
* try to select a widget starting from the beginning
* @param {boolean} [options.force=false] if true, force selecting the cell
* even if seems to be already the selected one (useful after a re-
* rendering, to reset the focus on the correct field)
* @return {Deferred} fails if no cell could be selected
*/
- _selectCell: function (rowIndex, colIndex, options) {
+ _selectCell: function (rowIndex, fieldIndex, options) {
options = options || {};
// Do nothing if the user tries to select current cell
- if (!options.force && rowIndex === this.currentRow && colIndex === this.currentCol) {
+ if (!options.force && rowIndex === this.currentRow && fieldIndex === this.currentFieldIndex) {
return $.when();
}
var wrap = options.wrap === undefined ? true : options.wrap;
@@ -584,32 +582,18 @@ ListRenderer.include({
var self = this;
return this._selectRow(rowIndex).then(function () {
var record = self.state.data[rowIndex];
- var correctedIndex = colIndex - getNbButtonBefore(colIndex);
- if (correctedIndex >= (self.allFieldWidgets[record.id] || []).length) {
+ if (fieldIndex >= (self.allFieldWidgets[record.id] || []).length) {
return $.Deferred().reject();
}
- var fieldIndex = self._activateFieldWidget(record, correctedIndex, {
+ fieldIndex = self._activateFieldWidget(record, fieldIndex, {
inc: 1,
wrap: wrap,
event: options && options.event,
});
-
if (fieldIndex < 0) {
return $.Deferred().reject();
}
-
- self.currentCol = fieldIndex + getNbButtonBefore(fieldIndex);
self.currentFieldIndex = fieldIndex;
-
- function getNbButtonBefore(index) {
- var nbButtons = 0;
- for (var i = 0 ; i < index ; i++) {
- if (self.columns[i].tag === 'button') {
- nbButtons++;
- }
- }
- return nbButtons;
- }
});
},
/**
@@ -682,8 +666,8 @@ ListRenderer.include({
var $td = $(event.currentTarget);
var $tr = $td.parent();
var rowIndex = this.$('.o_data_row').index($tr);
- var colIndex = $tr.find('.o_data_cell').index($td);
- this._selectCell(rowIndex, colIndex, {event: event});
+ var fieldIndex = Math.max($tr.find('.o_data_cell').not('.o_list_button').index($td), 0);
+ this._selectCell(rowIndex, fieldIndex, {event: event});
},
/**
* We need to manually unselect row, because noone else would do it
@@ -725,35 +709,35 @@ ListRenderer.include({
switch (ev.data.direction) {
case 'up':
if (this.currentRow > 0) {
- this._selectCell(this.currentRow - 1, this.currentCol);
+ this._selectCell(this.currentRow - 1, this.currentFieldIndex);
}
break;
case 'right':
- if (this.currentCol + 1 < this.columns.length) {
- this._selectCell(this.currentRow, this.currentCol + 1);
+ if (this.currentFieldIndex + 1 < this.columns.length) {
+ this._selectCell(this.currentRow, this.currentFieldIndex + 1);
}
break;
case 'down':
if (this.currentRow < this.state.data.length - 1) {
- this._selectCell(this.currentRow + 1, this.currentCol);
+ this._selectCell(this.currentRow + 1, this.currentFieldIndex);
}
break;
case 'left':
- if (this.currentCol > 0) {
- this._selectCell(this.currentRow, this.currentCol - 1);
+ if (this.currentFieldIndex > 0) {
+ this._selectCell(this.currentRow, this.currentFieldIndex - 1);
}
break;
case 'previous':
- if (this.currentCol > 0) {
- this._selectCell(this.currentRow, this.currentCol - 1, {wrap: false})
+ if (this.currentFieldIndex > 0) {
+ this._selectCell(this.currentRow, this.currentFieldIndex - 1, {wrap: false})
.fail(this._moveToPreviousLine.bind(this));
} else {
this._moveToPreviousLine();
}
break;
case 'next':
- if (this.currentCol + 1 < this.columns.length) {
- this._selectCell(this.currentRow, this.currentCol + 1, {wrap: false})
+ if (this.currentFieldIndex + 1 < this.columns.length) {
+ this._selectCell(this.currentRow, this.currentFieldIndex + 1, {wrap: false})
.fail(this._moveToNextLine.bind(this));
} else {
this._moveToNextLine();
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 3ba80210714..d37120339d6 100644
--- a/addons/web/static/src/js/views/list/list_renderer.js
+++ b/addons/web/static/src/js/views/list/list_renderer.js
@@ -4,6 +4,7 @@ odoo.define('web.ListRenderer', function (require) {
var BasicRenderer = require('web.BasicRenderer');
var config = require('web.config');
var core = require('web.core');
+var Dialog = require('web.Dialog');
var dom = require('web.dom');
var field_utils = require('web.field_utils');
var Pager = require('web.Pager');
@@ -323,7 +324,15 @@ var ListRenderer = BasicRenderer.extend({
});
});
} else {
- $button.prop('disabled', true);
+ if (node.attrs.options.warn) {
+ var self = this;
+ $button.on("click", function (e) {
+ e.stopPropagation();
+ self.do_warn(_t("Warning"), _t('Please click on the "save" button first'));
+ });
+ } else {
+ $button.prop('disabled', true);
+ }
}
return $button;
diff --git a/addons/web/static/src/js/widgets/debug_manager.js b/addons/web/static/src/js/widgets/debug_manager.js
index ed0248f6337..a214d8b55a4 100644
--- a/addons/web/static/src/js/widgets/debug_manager.js
+++ b/addons/web/static/src/js/widgets/debug_manager.js
@@ -439,8 +439,8 @@ DebugManager.include({
return field.name === fieldToSet;
}).value;
self._rpc({
- model: 'ir.values',
- method: 'set_default',
+ model: 'ir.default',
+ method: 'set',
args: [
self._active_view.fields_view.model,
fieldToSet,
diff --git a/addons/web/static/src/js/widgets/domain_selector.js b/addons/web/static/src/js/widgets/domain_selector.js
index 82cca87f4df..782128994db 100644
--- a/addons/web/static/src/js/widgets/domain_selector.js
+++ b/addons/web/static/src/js/widgets/domain_selector.js
@@ -805,7 +805,7 @@ var DomainLeaf = DomainNode.extend({
if (_.isBoolean(this.value)) {
this.value = "";
} else if (_.isObject(this.value) && !_.isArray(this.value)) { // Can be object if parsed to x2x representation
- this.value = this.value.id || "";
+ this.value = this.value.id || value || "";
}
}
diff --git a/addons/web/static/src/less/kanban_dashboard.less b/addons/web/static/src/less/kanban_dashboard.less
index cf3f632f26c..6fb2999a765 100644
--- a/addons/web/static/src/less/kanban_dashboard.less
+++ b/addons/web/static/src/less/kanban_dashboard.less
@@ -226,4 +226,9 @@
left: 0;
right: auto;
}
+
+ // Emphasize records' colors when necessary
+ &.o_emphasize_colors .o_kanban_record::after {
+ width: @o-kanban-color-border-width * 2;
+ }
}
diff --git a/addons/web/static/src/less/kanban_view.less b/addons/web/static/src/less/kanban_view.less
index 6f001929521..d95a518fdda 100644
--- a/addons/web/static/src/less/kanban_view.less
+++ b/addons/web/static/src/less/kanban_view.less
@@ -24,8 +24,9 @@
cursor: pointer;
.o-hover-opacity(@base-opacity);
}
-.o-kanban-tag-color(@size: 1) when (@size < @odoo-nb-ui-color) {
- // Note: the 0 color is not defined as it is the 'no color' for kanban
+.o-kanban-tag-color(@size: 0) when (@size < @odoo-nb-ui-color) {
+ // Note: the 0 color is supposed to be invisible if there is a color field
+ // but it is used as a default color when there is no color field
&.o_tag_color_@{size} span {
background-color: ~"@{odoo-color-@{size}}";
}
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 0c910e5b4de..7a0937084c3 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -1491,7 +1491,7 @@ QUnit.module('relational_fields', {
QUnit.test('pressing ENTER on a \'no_quick_create\' many2one should not trigger M2ODialog', function (assert) {
var done = assert.async();
- assert.expect(1);
+ assert.expect(2);
var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY;
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0;
@@ -1529,6 +1529,10 @@ QUnit.module('relational_fields', {
$input.blur();
assert.strictEqual($('.modal').length, 1,
"should have one modal in body");
+ // Check that discarding clears $input
+ $('.modal .o_form_button_cancel').click();
+ assert.strictEqual($input.val(), '',
+ "the field should be empty");
form.destroy();
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY;
done();
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index 52149f008eb..4a89a4a70e7 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -2390,6 +2390,8 @@ QUnit.module('Views', {
model: 'foo',
data: this.data,
arch: '<tree editable="bottom">' +
+ // Adding a button column makes conversions between column and field position trickier
+ '<button name="kikou" string="Kikou" type="object"/>' +
'<field name="foo"/>' +
'<button name="kikou" string="Kikou" type="object"/>' +
'<field name="int_field"/>' +
@@ -2397,7 +2399,7 @@ QUnit.module('Views', {
res_id: 1,
});
- list.$('tbody tr:eq(2) td:eq(1)').click();
+ list.$('tbody tr:eq(2) td:eq(2)').click();
assert.strictEqual(list.$('tbody tr:eq(2) input[name="foo"]')[0], document.activeElement,
"foo should be focused");
list.$('tbody tr:eq(2) input[name="foo"]').trigger($.Event('keydown', {which: $.ui.keyCode.TAB}));
diff --git a/addons/web/static/tests/widgets/domain_selector_tests.js b/addons/web/static/tests/widgets/domain_selector_tests.js
index 77d8eeb24da..55ddc971642 100644
--- a/addons/web/static/tests/widgets/domain_selector_tests.js
+++ b/addons/web/static/tests/widgets/domain_selector_tests.js
@@ -174,6 +174,26 @@ QUnit.module('DomainSelector', {
domainSelector.destroy();
});
+
+ QUnit.test("building a domain with a m2o without following the relation", function (assert) {
+ assert.expect(1);
+
+ var $target = $("#qunit-fixture");
+
+ // Create the domain selector and its mock environment
+ var domainSelector = new DomainSelector(null, "partner", [["product_id", "ilike", 1]], {
+ debugMode: true,
+ readonly: false,
+ });
+ testUtils.addMockEnvironment(domainSelector, {data: this.data});
+ domainSelector.appendTo($target);
+
+ domainSelector.$('.o_domain_leaf_value_input').val('pad').trigger('input').trigger('change');
+ assert.strictEqual(domainSelector.$('.o_domain_debug_input').val(), '[["product_id","ilike","pad"]]',
+ "string should have been allowed as m2o value");
+
+ domainSelector.destroy();
+ });
});
});
});