summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-08-27 11:16:44 +0200
committerChristophe Simonis <chs@odoo.com>2018-08-27 11:16:44 +0200
commita7a30791def5a033f5e77d42f155bafae0c430cb (patch)
treeba668f1a961ccd7455be33dfa50e0b763f7c360b /addons/web/static
parent0da9da4c447eed7dafc10a17d59000a6cfa5f55f (diff)
parenta5187cef1078b01a27e05e761d7592b6857f41f9 (diff)
[MERGE] forward port branch saas-11.4 up to a5187cef107
Diffstat (limited to 'addons/web/static')
-rw-r--r--addons/web/static/src/js/chrome/keyboard_navigation_mixin.js8
-rw-r--r--addons/web/static/src/js/core/misc.js25
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js8
-rw-r--r--addons/web/static/src/js/views/graph/graph_renderer.js2
-rw-r--r--addons/web/static/src/scss/fields.scss8
-rw-r--r--addons/web/static/tests/helpers/mock_server.js19
-rw-r--r--addons/web/static/tests/views/graph_tests.js23
-rw-r--r--addons/web/static/tests/views/list_tests.js110
8 files changed, 197 insertions, 6 deletions
diff --git a/addons/web/static/src/js/chrome/keyboard_navigation_mixin.js b/addons/web/static/src/js/chrome/keyboard_navigation_mixin.js
index d0eed176d9c..6fec5795bee 100644
--- a/addons/web/static/src/js/chrome/keyboard_navigation_mixin.js
+++ b/addons/web/static/src/js/chrome/keyboard_navigation_mixin.js
@@ -103,6 +103,14 @@ odoo.define('web.KeyboardNavigationMixin', function (require) {
* return {undefined|false}
*/
_onKeyDown: function (keyDownEvent) {
+ if ($('body.o_ui_blocked').length &&
+ (keyDownEvent.altKey || keyDownEvent.key === 'Alt') &&
+ !keyDownEvent.ctrlKey) {
+ if (keyDownEvent.preventDefault) keyDownEvent.preventDefault(); else keyDownEvent.returnValue = false;
+ if (keyDownEvent.stopPropagation) keyDownEvent.stopPropagation();
+ if (keyDownEvent.cancelBubble) keyDownEvent.cancelBubble = true;
+ return false;
+ }
if (!this._areAccessKeyVisible &&
(keyDownEvent.altKey || keyDownEvent.key === 'Alt') &&
!keyDownEvent.ctrlKey) {
diff --git a/addons/web/static/src/js/core/misc.js b/addons/web/static/src/js/core/misc.js
index be0f012752c..cd9a73ef22b 100644
--- a/addons/web/static/src/js/core/misc.js
+++ b/addons/web/static/src/js/core/misc.js
@@ -52,6 +52,29 @@ if ($.blockUI) {
}
+/**
+ * Remove the "accesskey" attributes to avoid the use of the access keys
+ * while the blockUI is enable.
+ */
+
+function blockAccessKeys() {
+ var elementWithAccessKey = [];
+ elementWithAccessKey = document.querySelectorAll('[accesskey]');
+ _.each(elementWithAccessKey, function (elem) {
+ elem.setAttribute("data-accesskey",elem.getAttribute('accesskey'));
+ elem.removeAttribute('accesskey');
+ });
+}
+
+function unblockAccessKeys() {
+ var elementWithDataAccessKey = [];
+ elementWithDataAccessKey = document.querySelectorAll('[data-accesskey]');
+ _.each(elementWithDataAccessKey, function (elem) {
+ elem.setAttribute('accesskey', elem.getAttribute('data-accesskey'));
+ elem.removeAttribute('data-accesskey');
+ });
+}
+
var throbbers = [];
function blockUI() {
@@ -60,6 +83,7 @@ function blockUI() {
throbbers.push(throbber);
throbber.appendTo($(".oe_blockui_spin_container"));
$(document.body).addClass('o_ui_blocked');
+ blockAccessKeys();
return tmp;
}
@@ -67,6 +91,7 @@ function unblockUI() {
_.invoke(throbbers, 'destroy');
throbbers = [];
$(document.body).removeClass('o_ui_blocked');
+ unblockAccessKeys();
return $.unblockUI.apply($, arguments);
}
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 186d0798e8f..c095871b75d 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -1006,8 +1006,14 @@ var BasicModel = AbstractModel.extend({
params: params,
})
.then(function () {
+ var offset = options.offset ? options.offset : 0;
+ var old_data = data.data.slice();
data.data = _.sortBy(data.data, function (d) {
- return _.indexOf(resIDs, self.localData[d].res_id);
+ if (_.contains(resIDs, self.localData[d].res_id)) {
+ return _.indexOf(resIDs, self.localData[d].res_id) + offset;
+ } else {
+ return _.indexOf(old_data, d);
+ }
});
data.res_ids = [];
_.each(data.data, function (d) {
diff --git a/addons/web/static/src/js/views/graph/graph_renderer.js b/addons/web/static/src/js/views/graph/graph_renderer.js
index e801fcc5323..80edb8047ba 100644
--- a/addons/web/static/src/js/views/graph/graph_renderer.js
+++ b/addons/web/static/src/js/views/graph/graph_renderer.js
@@ -389,7 +389,7 @@ return AbstractRenderer.extend({
};
}
data_dict[serie].values.push({
- x: tick, y: graphData[i].value,
+ x: tick - 1, y: graphData[i].value,
});
data = _.map(data_dict, identity);
}
diff --git a/addons/web/static/src/scss/fields.scss b/addons/web/static/src/scss/fields.scss
index 1c6fdf3abb1..3681c38eb47 100644
--- a/addons/web/static/src/scss/fields.scss
+++ b/addons/web/static/src/scss/fields.scss
@@ -403,6 +403,14 @@
}
}
+ // PDF Viewer
+ &.o_field_pdfviewer, .o_pdfview_iframe {
+ width: 100%;
+ height: 450px;
+ border: 0;
+ margin-top: 10px;
+ }
+
// Copy to clipboard
&.o_field_copy {
position: relative;
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index 6839223a903..f05c89c1a7d 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -882,6 +882,23 @@ var MockServer = Class.extend({
return data;
},
/**
+ * Simulates a 'resequence' operation
+ *
+ * @private
+ * @param {string} model
+ * @param {string} field
+ * @param {Array} ids
+ */
+ _mockResequence: function (args) {
+ var offset = args.offset ? Number(args.offset) : 0;
+ var field = args.field ? args.field : 'sequence';
+ var records = this.data[args.model].records;
+ for (var i in args.ids) {
+ var record = _.findWhere(this.data[args.model].records, {id: args.ids[i]});
+ record[field] = Number(i) + offset;
+ }
+ },
+ /**
* Simulate a 'search_count' operation
*
* @private
@@ -1040,7 +1057,7 @@ var MockServer = Class.extend({
return $.when(this._mockSearchReadController(args));
case '/web/dataset/resequence':
- return $.when();
+ return $.when(this._mockResequence(args));
}
if (route.indexOf('/web/image') >= 0 || _.contains(['.png', '.jpg'], route.substr(route.length - 4))) {
return $.when();
diff --git a/addons/web/static/tests/views/graph_tests.js b/addons/web/static/tests/views/graph_tests.js
index 0c3b664afc5..2e74e36e5e1 100644
--- a/addons/web/static/tests/views/graph_tests.js
+++ b/addons/web/static/tests/views/graph_tests.js
@@ -152,6 +152,29 @@ QUnit.module('Views', {
});
});
+ QUnit.test('displaying line chart data with multiple groupbys', function (assert) {
+ // this test makes sure the line chart shows all data labels (X axis) when
+ // it is grouped by several fields
+ assert.expect(3);
+
+ var graph = createView({
+ View: GraphView,
+ model: 'foo',
+ data: this.data,
+ arch: '<graph type="line"><field name="foo" /></graph>',
+ groupBy: ['product_id', 'bar'],
+ });
+
+ assert.strictEqual(graph.$('.nv-x text:contains(xphone)').length, 1,
+ "should contain a text element with product xphone on X axis");
+ assert.strictEqual(graph.$('.nv-x text:contains(xpad)').length, 1,
+ "should contain a text element with product xpad on X axis");
+ assert.strictEqual(graph.$('text:contains(true)').length, 1,
+ "should have an entry for each value of field 'bar' in the legend");
+
+ graph.destroy();
+ });
+
QUnit.test('switching measures', function (assert) {
var done = assert.async();
assert.expect(4);
diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js
index d389e4ef34c..3fb1ca99ded 100644
--- a/addons/web/static/tests/views/list_tests.js
+++ b/addons/web/static/tests/views/list_tests.js
@@ -3156,20 +3156,118 @@ QUnit.module('Views', {
list.destroy();
});
+ QUnit.test('result of consecutive resequences is correctly sorted', function (assert) {
+ assert.expect(9);
+ this.data = { // we want the data to be minimal to have a minimal test
+ foo: {
+ fields: {int_field: {string: "int_field", type: "integer", sortable: true}},
+ records: [
+ {id: 1, int_field: 0},
+ {id: 2, int_field: 1},
+ {id: 3, int_field: 2},
+ {id: 4, int_field: 3},
+ ]
+ }
+ };
+ var moves = 0;
+ var list = createView({
+ View: ListView,
+ model: 'foo',
+ data: this.data,
+ arch: '<tree>' +
+ '<field name="int_field" widget="handle"/>' +
+ '<field name="id"/>' +
+ '</tree>',
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/resequence') {
+ if (moves === 0) {
+ assert.deepEqual(args, {
+ model: "foo",
+ ids: [4, 3],
+ offset: 2,
+ field: "int_field",
+ });
+ }
+ if (moves === 1) {
+ assert.deepEqual(args, {
+ model: "foo",
+ ids: [1, 4, 2, 3],
+ field: "int_field",
+ });
+ }
+ if (moves === 2) {
+ assert.deepEqual(args, {
+ model: "foo",
+ ids: [2, 4],
+ offset: 1,
+ field: "int_field",
+ });
+ }
+ if (moves === 3) {
+ assert.deepEqual(args, {
+ model: "foo",
+ ids: [1, 4, 2, 3],
+ field: "int_field",
+ });
+ }
+ moves += 1;
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+ assert.strictEqual(list.$('tbody tr td').text(), '1234',
+ "default should be sorted by id");
+ testUtils.dragAndDrop(
+ list.$('.ui-sortable-handle').eq(3),
+ list.$('tbody tr').eq(2),
+ {position: 'top'}
+ );
+ assert.strictEqual(list.$('tbody tr td').text(), '1243',
+ "the int_field (sequence) should have been correctly updated");
+ testUtils.dragAndDrop(
+ list.$('.ui-sortable-handle').eq(2),
+ list.$('tbody tr').eq(1),
+ {position: 'top'}
+ );
+ assert.deepEqual(list.$('tbody tr td').text(), '1423',
+ "the int_field (sequence) should have been correctly updated");
+ testUtils.dragAndDrop(
+ list.$('.ui-sortable-handle').eq(1),
+ list.$('tbody tr').eq(3),
+ {position: 'top'}
+ );
+ assert.deepEqual(list.$('tbody tr td').text(), '1243',
+ "the int_field (sequence) should have been correctly updated");
+ testUtils.dragAndDrop(
+ list.$('.ui-sortable-handle').eq(2),
+ list.$('tbody tr').eq(1),
+ {position: 'top'}
+ );
+ assert.deepEqual(list.$('tbody tr td').text(), '1423',
+ "the int_field (sequence) should have been correctly updated");
+ list.destroy();
+ });
+
QUnit.test('editable list with handle widget', function (assert) {
assert.expect(12);
+ // resequence makes sense on a sequence field, not on arbitrary fields
+ this.data.foo.records[0].int_field = 0;
+ this.data.foo.records[1].int_field = 1;
+ this.data.foo.records[2].int_field = 2;
+ this.data.foo.records[3].int_field = 3;
+
var list = createView({
View: ListView,
model: 'foo',
data: this.data,
- arch: '<tree editable="top">' +
+ arch: '<tree editable="top" default_order="int_field">' +
'<field name="int_field" widget="handle"/>' +
'<field name="amount" widget="float" digits="[5,0]"/>' +
'</tree>',
mockRPC: function (route, args) {
if (route === '/web/dataset/resequence') {
- assert.strictEqual(args.offset, -4,
+ assert.strictEqual(args.offset, 1,
"should write the sequence starting from the lowest current one");
assert.strictEqual(args.field, 'int_field',
"should write the right field as sequence");
@@ -3217,6 +3315,12 @@ QUnit.module('Views', {
QUnit.test('editable list with handle widget with slow network', function (assert) {
assert.expect(15);
+ // resequence makes sense on a sequence field, not on arbitrary fields
+ this.data.foo.records[0].int_field = 0;
+ this.data.foo.records[1].int_field = 1;
+ this.data.foo.records[2].int_field = 2;
+ this.data.foo.records[3].int_field = 3;
+
var def = $.Deferred();
var list = createView({
@@ -3229,7 +3333,7 @@ QUnit.module('Views', {
'</tree>',
mockRPC: function (route, args) {
if (route === '/web/dataset/resequence') {
- assert.strictEqual(args.offset, -4,
+ assert.strictEqual(args.offset, 1,
"should write the sequence starting from the lowest current one");
assert.strictEqual(args.field, 'int_field',
"should write the right field as sequence");