diff options
| author | Christophe Simonis <chs@odoo.com> | 2018-08-27 11:16:44 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2018-08-27 11:16:44 +0200 |
| commit | a7a30791def5a033f5e77d42f155bafae0c430cb (patch) | |
| tree | ba668f1a961ccd7455be33dfa50e0b763f7c360b /addons/web | |
| parent | 0da9da4c447eed7dafc10a17d59000a6cfa5f55f (diff) | |
| parent | a5187cef1078b01a27e05e761d7592b6857f41f9 (diff) | |
[MERGE] forward port branch saas-11.4 up to a5187cef107
Diffstat (limited to 'addons/web')
| -rw-r--r-- | addons/web/models/ir_qweb.py | 1 | ||||
| -rw-r--r-- | addons/web/static/src/js/chrome/keyboard_navigation_mixin.js | 8 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/misc.js | 25 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/basic/basic_model.js | 8 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/graph/graph_renderer.js | 2 | ||||
| -rw-r--r-- | addons/web/static/src/scss/fields.scss | 8 | ||||
| -rw-r--r-- | addons/web/static/tests/helpers/mock_server.js | 19 | ||||
| -rw-r--r-- | addons/web/static/tests/views/graph_tests.js | 23 | ||||
| -rw-r--r-- | addons/web/static/tests/views/list_tests.js | 110 |
9 files changed, 198 insertions, 6 deletions
diff --git a/addons/web/models/ir_qweb.py b/addons/web/models/ir_qweb.py index 34d2ecb0d5d..b734c4f193c 100644 --- a/addons/web/models/ir_qweb.py +++ b/addons/web/models/ir_qweb.py @@ -65,6 +65,7 @@ class Image(models.AbstractModel): atts["alt"] = alt atts["data-zoom"] = src_zoom and u'1' or None atts["data-zoom-image"] = src_zoom + atts["data-no-post-process"] = options.get('data-no-post-process') atts = self.env['ir.qweb']._post_processing_att('img', atts, options.get('template_options')) 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"); |
