summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-04-12 13:37:44 +0200
committerChristophe Simonis <chs@odoo.com>2019-04-12 13:37:44 +0200
commit0a09a4d8a819ef06db47e798f0465e500004e4c1 (patch)
treeb9d66d60a1e9b5ff6030be62fff89df410b744ee /addons/web/static
parentdb97bb7f1136dc3ebde9924b79364a698a63765d (diff)
parent120359bf0c98191051ac19ae2e5fde53ca7d2ae5 (diff)
[MERGE] forward port branch saas-12.3 up to 120359bf0c9
Diffstat (limited to 'addons/web/static')
-rw-r--r--addons/web/static/src/js/chrome/systray_menu.js41
-rw-r--r--addons/web/static/src/js/core/domain.js4
-rw-r--r--addons/web/static/src/js/views/basic/basic_renderer.js8
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_controller.js1
-rw-r--r--addons/web/static/src/js/views/list/list_editable_renderer.js20
-rw-r--r--addons/web/static/src/scss/layout_background.scss7
-rw-r--r--addons/web/static/src/scss/list_view.scss2
-rw-r--r--addons/web/static/tests/chrome/systray_tests.js43
-rw-r--r--addons/web/static/tests/helpers/test_utils_create.js1
-rw-r--r--addons/web/static/tests/views/calendar_tests.js36
-rw-r--r--addons/web/static/tests/views/list_tests.js156
11 files changed, 290 insertions, 29 deletions
diff --git a/addons/web/static/src/js/chrome/systray_menu.js b/addons/web/static/src/js/chrome/systray_menu.js
index fd214c0b650..cb292a9b90f 100644
--- a/addons/web/static/src/js/chrome/systray_menu.js
+++ b/addons/web/static/src/js/chrome/systray_menu.js
@@ -1,6 +1,7 @@
odoo.define('web.SystrayMenu', function (require) {
"use strict";
+var dom = require('web.dom');
var Widget = require('web.Widget');
/**
@@ -18,35 +19,35 @@ var SystrayMenu = Widget.extend({
this.widgets = [];
},
/**
+ * Instanciate the items and add them into a temporary fragmenet
* @override
- * @returns {Promise}
*/
- start: function () {
+ willStart: function () {
var self = this;
- self._super.apply(this, arguments);
- self._loadItems();
-
- return new Promise(function (resolve, reject) {
- Promise.all(self.items).then(resolve).guardedCatch(resolve);
+ var proms = [];
+ SystrayMenu.Items = _.sortBy(SystrayMenu.Items, function (item) {
+ return !_.isUndefined(item.prototype.sequence) ? item.prototype.sequence : 50;
});
- },
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
+ SystrayMenu.Items.forEach(function (WidgetClass) {
+ var cur_systray_item = new WidgetClass(self);
+ self.widgets.push(cur_systray_item);
+ proms.push(cur_systray_item.appendTo($('<div>')));
+ });
+ return this._super.apply(this, arguments).then(function () {
+ return Promise.all(proms);
+ });
+ },
/**
- * Instantiate items, using the classes located in SystrayMenu.items.
+ * Add the instanciated items, using the object located in this.wisgets
*/
- _loadItems: function () {
+ start: function () {
var self = this;
- SystrayMenu.Items = _.sortBy(SystrayMenu.Items, function (item) {
- return !_.isUndefined(item.prototype.sequence) ? item.prototype.sequence : 50;
- });
- _.each(SystrayMenu.Items, function (WidgetClass) {
- var cur_systray_item = new WidgetClass(self);
- self.widgets.push(cur_systray_item);
- self.items.push(cur_systray_item.prependTo(self.$el));
+ return this._super.apply(this, arguments).then(function () {
+ self.widgets.forEach(function (widget) {
+ dom.prepend(self.$el, widget.$el);
+ });
});
},
});
diff --git a/addons/web/static/src/js/core/domain.js b/addons/web/static/src/js/core/domain.js
index c7533aabae0..9c74dcb953e 100644
--- a/addons/web/static/src/js/core/domain.js
+++ b/addons/web/static/src/js/core/domain.js
@@ -263,8 +263,8 @@ var Domain = collections.Tree.extend({
});
break;
case 'previous_year':
- leftBoundaryParams.years = leftBoundaryParams.years ? leftBoundaryParams.years-- : -1;
- rightBoundaryParams.years = rightBoundaryParams.years ? rightBoundaryParams.years-- : -1;
+ leftBoundaryParams.years = leftBoundaryParams.years ? leftBoundaryParams.years - 1 : -1;
+ rightBoundaryParams.years = rightBoundaryParams.years ? rightBoundaryParams.years - 1 : -1;
break;
}
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 f741e11b789..4c03d9b77a1 100644
--- a/addons/web/static/src/js/views/basic/basic_renderer.js
+++ b/addons/web/static/src/js/views/basic/basic_renderer.js
@@ -731,8 +731,12 @@ var BasicRenderer = AbstractRenderer.extend({
var defs = [];
this.defs = defs; // Potentially filled by widget rerendering
_.each(this.allModifiersData, function (modifiersData) {
- modifiersData.evaluatedModifiers[record.id] = record.evalModifiers(modifiersData.modifiers);
- self._applyModifiers(modifiersData, record);
+ // `allModifiersData` might contain modifiers registered for other
+ // records than the given record (e.g. <groupby> in list)
+ if (record.id in modifiersData.evaluatedModifiers) {
+ modifiersData.evaluatedModifiers[record.id] = record.evalModifiers(modifiersData.modifiers);
+ self._applyModifiers(modifiersData, record);
+ }
});
delete this.defs;
diff --git a/addons/web/static/src/js/views/calendar/calendar_controller.js b/addons/web/static/src/js/views/calendar/calendar_controller.js
index ec85b567cf7..dda3978635b 100644
--- a/addons/web/static/src/js/views/calendar/calendar_controller.js
+++ b/addons/web/static/src/js/views/calendar/calendar_controller.js
@@ -272,6 +272,7 @@ var CalendarController = AbstractController.extend({
res_model: this.modelName,
context: context,
title: title,
+ view_id: this.formViewId || false,
disable_multiple_selection: true,
on_saved: function () {
if (event.data.on_save) {
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 9f37fcecbea..818739c6f48 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
@@ -435,6 +435,18 @@ ListRenderer.include({
});
}
},
+ /**
+ * @override
+ */
+ updateState: function (state, params) {
+ if (params.noRender) {
+ // the state changed, but we won't do a re-rendering right now, so
+ // remove computed modifiers data (as they are obsolete) to force
+ // them to be recomputed at next (sub-)rendering
+ this.allModifiersData = [];
+ }
+ return this._super.apply(this, arguments);
+ },
//--------------------------------------------------------------------------
// Private
@@ -586,7 +598,7 @@ ListRenderer.include({
this.commitChanges(curRecordId).then(function () {
var $prevRow = self._getRow(self.allRecordIds[prevRecordIndex]);
var prevRowIndex = $prevRow.prop('rowIndex') - 1;
- self._selectCell(prevRowIndex, self.columns.length - 1);
+ self._selectCell(prevRowIndex, self.columns.length - 1, {inc: -1});
});
},
/**
@@ -955,6 +967,8 @@ ListRenderer.include({
* @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)
+ * @param {integer} [options.inc=1] the increment to use when searching for
+ * the "next" possible cell (if the cell to select can't be selected)
* @return {Promise} fails if no cell could be selected
*/
_selectCell: function (rowIndex, fieldIndex, options) {
@@ -979,7 +993,7 @@ ListRenderer.include({
var oldFieldIndex = self.currentFieldIndex;
self.currentFieldIndex = fieldIndex;
fieldIndex = self._activateFieldWidget(record, fieldIndex, {
- inc: 1,
+ inc: options.inc || 1,
wrap: wrap,
event: options && options.event,
});
@@ -1183,7 +1197,7 @@ ListRenderer.include({
switch (ev.data.direction) {
case 'previous':
if (this.currentFieldIndex > 0) {
- this._selectCell(this.currentRow, this.currentFieldIndex - 1, {wrap: false})
+ this._selectCell(this.currentRow, this.currentFieldIndex - 1, {inc: -1, wrap: false})
.guardedCatch(this._moveToPreviousLine.bind(this));
} else {
this._moveToPreviousLine();
diff --git a/addons/web/static/src/scss/layout_background.scss b/addons/web/static/src/scss/layout_background.scss
index 9b137fde8c1..caeb1f0c2bf 100644
--- a/addons/web/static/src/scss/layout_background.scss
+++ b/addons/web/static/src/scss/layout_background.scss
@@ -82,3 +82,10 @@
}
}
}
+
+/* special case for displaying report in iframe */
+.o_in_iframe {
+ .o_background_header {
+ min-width: 0;
+ }
+}
diff --git a/addons/web/static/src/scss/list_view.scss b/addons/web/static/src/scss/list_view.scss
index 07da78731f3..86b989dbd10 100644
--- a/addons/web/static/src/scss/list_view.scss
+++ b/addons/web/static/src/scss/list_view.scss
@@ -76,7 +76,7 @@
}
}
- .o_list_record_selector {
+ .o_list_record_selector, .o_list_record_remove, .o_handle_cell {
width: 1px; // to prevent the column to expand
}
diff --git a/addons/web/static/tests/chrome/systray_tests.js b/addons/web/static/tests/chrome/systray_tests.js
new file mode 100644
index 00000000000..c5238ceff04
--- /dev/null
+++ b/addons/web/static/tests/chrome/systray_tests.js
@@ -0,0 +1,43 @@
+odoo.define('web.systray_tests', function (require) {
+ "use strict";
+
+ var testUtils = require('web.test_utils');
+ var SystrayMenu = require('web.SystrayMenu');
+ var Widget = require('web.Widget');
+
+ QUnit.test('Adding async components to the registry respects the sequence', async function (assert) {
+ assert.expect(2);
+ var parent = testUtils.createParent({
+ });
+ var prom = testUtils.makeTestPromise();
+
+ var synchronousFirstWidget = Widget.extend({
+ sequence: 3, // bigger sequence means more to the left
+ start: function () {
+ this.$el.addClass('first');
+ }
+ });
+ var asynchronousSecondWidget = Widget.extend({
+ sequence: 1, // smaller sequence means more to the right
+ willStart: function () {
+ return prom;
+ },
+ start: function () {
+ this.$el.addClass('second');
+ }
+ });
+
+ SystrayMenu.Items = [synchronousFirstWidget, asynchronousSecondWidget];
+ var menu = new SystrayMenu(parent);
+
+ menu.appendTo($('#qunit-fixture'));
+ await testUtils.nextTick();
+ prom.resolve();
+ await testUtils.nextTick();
+
+ assert.hasClass(menu.$('div:eq(0)'), 'first');
+ assert.hasClass(menu.$('div:eq(1)'), 'second');
+
+ parent.destroy();
+ })
+});
diff --git a/addons/web/static/tests/helpers/test_utils_create.js b/addons/web/static/tests/helpers/test_utils_create.js
index 097d46e93f7..f1c67284707 100644
--- a/addons/web/static/tests/helpers/test_utils_create.js
+++ b/addons/web/static/tests/helpers/test_utils_create.js
@@ -392,7 +392,6 @@ function createParent(params) {
return {
createActionManager: createActionManager,
- createView: createView,
createCalendarView: createCalendarView,
createControlPanel: createControlPanel,
createDebugManager: createDebugManager,
diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js
index 9f637c4fdc5..01f431589fc 100644
--- a/addons/web/static/tests/views/calendar_tests.js
+++ b/addons/web/static/tests/views/calendar_tests.js
@@ -2371,6 +2371,42 @@ QUnit.module('Views', {
calendar.destroy();
});
+ QUnit.test('form_view_id attribute works with popup (for creating events)', async function (assert) {
+ assert.expect(1);
+
+ var calendar = await createCalendarView({
+ View: CalendarView,
+ model: 'event',
+ data: this.data,
+ arch: '<calendar class="o_calendar_test" '+
+ 'date_start="start" '+
+ 'date_stop="stop" '+
+ 'mode="month" '+
+ 'event_open_popup="true" ' +
+ 'quick_add="false" ' +
+ 'form_view_id="1">'+
+ '<field name="name"/>'+
+ '</calendar>',
+ archs: archs,
+ viewOptions: {
+ initialDate: initialDate,
+ },
+ mockRPC: function (route, args) {
+ if (args.method === "load_views") {
+ assert.strictEqual(args.kwargs.views[0][0], 1,
+ "should load view with id 1");
+ }
+ return this._super(route, args);
+ },
+ });
+
+ var $cell = calendar.$('.fc-day-grid .fc-row:eq(2) .fc-day:eq(2)');
+ testUtils.dom.triggerMouseEvent($cell, "mousedown");
+ testUtils.dom.triggerMouseEvent($cell, "mouseup");
+ await testUtils.nextTick();
+ calendar.destroy();
+ });
+
QUnit.test('calendar fallback to form view id in action if necessary', async function (assert) {
assert.expect(1);
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index 5e5a0c7b7a1..7aff294fb97 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -1957,6 +1957,37 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('editable list view with groupby node and modifiers', async function (assert) {
+ assert.expect(3);
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree expand="1" editable="bottom">' +
+ '<field name="foo"/>' +
+ '<groupby name="currency_id">' +
+ '<field name="position"/>' +
+ '<button string="Button 1" type="object" name="button_method" attrs=\'{"invisible": [("position", "=", "after")]}\'/>' +
+ '</groupby>' +
+ '</tree>',
+ groupBy: ['currency_id'],
+ });
+
+ assert.doesNotHaveClass(list.$('.o_data_row:first'), 'o_selected_row',
+ "first row should be in readonly mode");
+
+ await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell'));
+ assert.hasClass(list.$('.o_data_row:first'), 'o_selected_row',
+ "the row should be in edit mode");
+
+ await testUtils.fields.triggerKeydown($(document.activeElement), 'escape');
+ assert.doesNotHaveClass(list.$('.o_data_row:first'), 'o_selected_row',
+ "the row should be back in readonly mode");
+
+ list.destroy();
+ });
+
QUnit.test('groupby node with edit button', async function (assert) {
assert.expect(1);
@@ -3212,6 +3243,90 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('pressing SHIFT-TAB in editable list with a readonly field [REQUIRE FOCUS]', async function (assert) {
+ assert.expect(4);
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="foo"/>' +
+ '<field name="int_field" readonly="1"/>' +
+ '<field name="qux"/>' +
+ '</tree>',
+ });
+
+ // start on 'qux', line 3
+ await testUtils.dom.click(list.$('.o_data_row:nth(2) .o_data_cell:nth(2)'));
+ assert.hasClass(list.$('.o_data_row:nth(2)'), 'o_selected_row');
+ assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(2) .o_data_cell input[name=qux]')[0]);
+
+ // Press 'shift-Tab' -> should go to first cell (same line)
+ $(document.activeElement).trigger({type: 'keydown', which: $.ui.keyCode.TAB, shiftKey: true});
+ await testUtils.nextTick();
+ assert.hasClass(list.$('.o_data_row:nth(2)'), 'o_selected_row');
+ assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(2) .o_data_cell input[name=foo]')[0]);
+
+ list.destroy();
+ });
+
+ QUnit.test('pressing SHIFT-TAB in editable list with a readonly field in first column [REQUIRE FOCUS]', async function (assert) {
+ assert.expect(4);
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="int_field" readonly="1"/>' +
+ '<field name="foo"/>' +
+ '<field name="qux"/>' +
+ '</tree>',
+ });
+
+ // start on 'foo', line 3
+ await testUtils.dom.click(list.$('.o_data_row:nth(2) .o_data_cell:nth(1)'));
+ assert.hasClass(list.$('.o_data_row:nth(2)'), 'o_selected_row');
+ assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(2) .o_data_cell input[name=foo]')[0]);
+
+ // Press 'shift-Tab' -> should go to previous line (last cell)
+ $(document.activeElement).trigger({type: 'keydown', which: $.ui.keyCode.TAB, shiftKey: true});
+ await testUtils.nextTick();
+ assert.hasClass(list.$('.o_data_row:nth(1)'), 'o_selected_row');
+ assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(1) .o_data_cell input[name=qux]')[0]);
+
+ list.destroy();
+ });
+
+ QUnit.test('pressing SHIFT-TAB in editable list with a readonly field in last column [REQUIRE FOCUS]', async function (assert) {
+ assert.expect(4);
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree editable="bottom">' +
+ '<field name="int_field"/>' +
+ '<field name="foo"/>' +
+ '<field name="qux" readonly="1"/>' +
+ '</tree>',
+ });
+
+ // start on 'int_field', line 3
+ await testUtils.dom.click(list.$('.o_data_row:nth(2) .o_data_cell:first'));
+ assert.hasClass(list.$('.o_data_row:nth(2)'), 'o_selected_row');
+ assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(2) .o_data_cell input[name=int_field]')[0]);
+
+ // Press 'shift-Tab' -> should go to previous line ('foo' field)
+ $(document.activeElement).trigger({type: 'keydown', which: $.ui.keyCode.TAB, shiftKey: true});
+ await testUtils.nextTick();
+ assert.hasClass(list.$('.o_data_row:nth(1)'), 'o_selected_row');
+ assert.strictEqual(document.activeElement, list.$('.o_data_row:nth(1) .o_data_cell input[name=foo]')[0]);
+
+ list.destroy();
+ });
+
QUnit.test('skip invisible fields when navigating list view with TAB', async function (assert) {
assert.expect(2);
@@ -4460,6 +4575,47 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('create record on list with modifiers depending on id', async function (assert) {
+ assert.expect(8);
+
+ var list = await createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree editable="top">' +
+ '<field name="id" invisible="1"/>' +
+ '<field name="foo" attrs="{\'readonly\': [[\'id\',\'!=\',False]]}"/>' +
+ '<field name="int_field" attrs="{\'invisible\': [[\'id\',\'!=\',False]]}"/>' +
+ '</tree>',
+ });
+
+ // add a new record
+ await testUtils.dom.click(list.$buttons.find('.o_list_button_add'));
+
+ // modifiers should be evaluted to false
+ assert.containsOnce(list, '.o_selected_row');
+ assert.doesNotHaveClass(list.$('.o_selected_row .o_data_cell:first'), 'o_readonly_modifier');
+ assert.doesNotHaveClass(list.$('.o_selected_row .o_data_cell:nth(1)'), 'o_invisible_modifier');
+
+ // set a value and save
+ await testUtils.fields.editInput(list.$('.o_selected_row input[name=foo]'), 'some value');
+ await testUtils.dom.click(list.$buttons.find('.o_list_button_save'));
+
+ // modifiers should be evaluted to true
+ assert.hasClass(list.$('.o_data_row:first .o_data_cell:first'), 'o_readonly_modifier');
+ assert.hasClass(list.$('.o_data_row:first .o_data_cell:nth(1)'), 'o_invisible_modifier');
+
+ // edit again the just created record
+ await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell:first'));
+
+ // modifiers should be evaluted to true
+ assert.containsOnce(list, '.o_selected_row');
+ assert.hasClass(list.$('.o_selected_row .o_data_cell:first'), 'o_readonly_modifier');
+ assert.hasClass(list.$('.o_selected_row .o_data_cell:nth(1)'), 'o_invisible_modifier');
+
+ list.destroy();
+ });
+
QUnit.test('grouped list with async widget', async function (assert) {
assert.expect(4);