diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js
index 5e669e0aa57..5f43b04f7cd 100644
--- a/addons/web/static/src/js/views/form/form_controller.js
+++ b/addons/web/static/src/js/views/form/form_controller.js
@@ -21,6 +21,8 @@ var FormController = BasicController.extend({
toggle_column_order: '_onToggleColumnOrder',
focus_control_button: '_onFocusControlButton',
form_dialog_discarded: '_onFormDialogDiscarded',
+ swipe_left: '_onSwipeLeft',
+ swipe_right: '_onSwipeRight',
}),
/**
* @override
@@ -644,6 +646,30 @@ var FormController = BasicController.extend({
self._enableButtons();
});
},
+ /**
+ * Called when user swipes left. Move to next record.
+ *
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onSwipeLeft: function (ev) {
+ ev.stopPropagation();
+ if (this.pager) {
+ this.pager.next();
+ }
+ },
+ /**
+ * Called when user swipes right. Move to previous record.
+ *
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onSwipeRight: function (ev) {
+ ev.stopPropagation();
+ if (this.pager) {
+ this.pager.previous();
+ }
+ },
/**
* This method is called when someone tries to sort a column, most likely
* in a x2many list view
diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js
index 6e9952c81d7..ac8ff541544 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -86,7 +86,7 @@ var FormRenderer = BasicRenderer.extend({
* @param {string} recordID
* @returns {string[]}
*/
- canBeSaved: function (recordID) {
+ canBeSaved: function () {
var self = this;
var fieldNames = this._super.apply(this, arguments);
@@ -130,7 +130,7 @@ var FormRenderer = BasicRenderer.extend({
*
* @override
*/
- confirmChange: function (state, id, fields, e) {
+ confirmChange: function () {
var self = this;
return this._super.apply(this, arguments).then(function (resetWidgets) {
_.each(resetWidgets, function (widget) {
@@ -288,6 +288,30 @@ var FormRenderer = BasicRenderer.extend({
});
});
},
+ /**
+ * Enable swipe event to allow navigating through records
+ *
+ * @private
+ */
+ _enableSwipe: function () {
+ var self = this;
+ this.$('.o_form_sheet').swipe({
+ swipeLeft: function () {
+ this.css({
+ transform: 'translateX(-100%)',
+ transition: '350ms'
+ });
+ self.trigger_up('swipe_left');
+ },
+ swipeRight: function () {
+ this.css({
+ transform: 'translateX(100%)',
+ transition: '350ms'
+ });
+ self.trigger_up('swipe_right');
+ },
+ });
+ },
/**
* @private
* @param {string} name
@@ -895,6 +919,11 @@ var FormRenderer = BasicRenderer.extend({
this.$el.toggleClass('o_form_editable', this.mode === 'edit');
this.$el.toggleClass('o_form_readonly', this.mode === 'readonly');
+ // Enable swipe for mobile when formview is in readonly mode and there are multiple records
+ if (config.device.isMobile && this.mode === 'readonly' && this.state.count > 1) {
+ this._enableSwipe();
+ }
+
// Attach the tooltips on the fields' label
_.each(this.allFieldWidgets[this.state.id], function (widget) {
var idForLabel = self.idsForLabels[widget.name];
diff --git a/addons/web/static/src/js/views/form/form_view.js b/addons/web/static/src/js/views/form/form_view.js
index c688a997581..01474a01180 100644
--- a/addons/web/static/src/js/views/form/form_view.js
+++ b/addons/web/static/src/js/views/form/form_view.js
@@ -2,6 +2,7 @@ odoo.define('web.FormView', function (require) {
"use strict";
var BasicView = require('web.BasicView');
+var config = require('web.config');
var Context = require('web.Context');
var core = require('web.core');
var FormController = require('web.FormController');
@@ -18,6 +19,7 @@ var FormView = BasicView.extend({
icon: 'fa-edit',
multi_record: false,
searchable: false,
+ jsLibs: [],
viewType: 'form',
/**
* @override
@@ -41,6 +43,9 @@ var FormView = BasicView.extend({
this.controllerParams.mode = mode;
this.rendererParams.mode = mode;
+ if (config.device.isMobile) {
+ this.jsLibs.push('/web/static/lib/jquery.touchSwipe/jquery.touchSwipe.js');
+ }
},
//--------------------------------------------------------------------------
diff --git a/addons/web/static/tests/fields/basic_fields_mobile_tests.js b/addons/web/static/tests/fields/basic_fields_mobile_tests.js
new file mode 100644
index 00000000000..76482a71e83
--- /dev/null
+++ b/addons/web/static/tests/fields/basic_fields_mobile_tests.js
@@ -0,0 +1,178 @@
+odoo.define('web.basic_fields_tests', function (require) {
+"use strict";
+
+var FormView = require('web.FormView');
+var ListView = require('web.ListView');
+var testUtils = require('web.test_utils');
+
+var createView = testUtils.createView;
+
+QUnit.module('fields', {}, function () {
+
+QUnit.module('basic_fields', {
+ beforeEach: function () {
+ this.data = {
+ partner: {
+ fields: {
+ date: {string: "A date", type: "date", searchable: true},
+ datetime: {string: "A datetime", type: "datetime", searchable: true},
+ display_name: {string: "Displayed name", type: "char", searchable: true},
+ foo: {string: "Foo", type: "char", default: "My little Foo Value", searchable: true, trim: true},
+ bar: {string: "Bar", type: "boolean", default: true, searchable: true},
+ int_field: {string: "int_field", type: "integer", sortable: true, searchable: true},
+ qux: {string: "Qux", type: "float", digits: [16,1], searchable: true},
+ },
+ records: [{
+ id: 1,
+ date: "2017-02-03",
+ datetime: "2017-02-08 10:00:00",
+ display_name: "first record",
+ bar: true,
+ foo: "yop",
+ int_field: 10,
+ qux: 0.44444,
+ }, {
+ id: 2,
+ display_name: "second record",
+ bar: true,
+ foo: "blip",
+ int_field: 0,
+ qux: 0,
+ }, {
+ id: 4,
+ display_name: "aaa",
+ foo: "abc",
+ int_field: false,
+ qux: false,
+ }],
+ onchanges: {},
+ },
+ };
+ }
+}, function () {
+
+ QUnit.module('PhoneWidget');
+
+ QUnit.test('phone field in form view on extra small screens', function (assert) {
+ assert.expect(7);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'
',
+ res_id: 1,
+ });
+
+ var $phoneLink = form.$('a.o_form_uri.o_field_widget');
+ assert.strictEqual($phoneLink.length, 1,
+ "should have a anchor with correct classes");
+ assert.strictEqual($phoneLink.text(), 'yop',
+ "value should be displayed properly");
+ assert.strictEqual($phoneLink.attr('href'), 'tel:yop',
+ "should have proper tel prefix");
+
+ // switch to edit mode and check the result
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('input[type="text"].o_field_widget').length, 1,
+ "should have an int for the phone field");
+ assert.strictEqual(form.$('input[type="text"].o_field_widget').val(), 'yop',
+ "input should contain field value in edit mode");
+
+ // change value in edit mode
+ form.$('input[type="text"].o_field_widget').val('new').trigger('input');
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ $phoneLink = form.$('a.o_form_uri.o_field_widget');
+ assert.strictEqual($phoneLink.text(), 'new',
+ "new value should be displayed properly");
+ assert.strictEqual($phoneLink.attr('href'), 'tel:new',
+ "should still have proper tel prefix");
+
+ form.destroy();
+ });
+
+ QUnit.test('phone field in editable list view on extra small screens', function (assert) {
+ assert.expect(10);
+
+ var list = createView({
+ View: ListView,
+ model: 'partner',
+ data: this.data,
+ arch: '',
+ });
+
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').length, 5,
+ "should have 5 cells");
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'yop',
+ "value should be displayed properly");
+
+ var $phoneLink = list.$('a.o_form_uri.o_field_widget');
+ assert.strictEqual($phoneLink.length, 5,
+ "should have anchors with correct classes");
+ assert.strictEqual($phoneLink.first().attr('href'), 'tel:yop',
+ "should have proper tel prefix");
+
+ // Edit a line and check the result
+ var $cell = list.$('tbody td:not(.o_list_record_selector)').first();
+ $cell.click();
+ assert.ok($cell.parent().hasClass('o_selected_row'), 'should be set as edit mode');
+ assert.strictEqual($cell.find('input').val(), 'yop',
+ 'should have the corect value in internal input');
+ $cell.find('input').val('new').trigger('input');
+
+ // save
+ list.$buttons.find('.o_list_button_save').click();
+ $cell = list.$('tbody td:not(.o_list_record_selector)').first();
+ assert.ok(!$cell.parent().hasClass('o_selected_row'), 'should not be in edit mode anymore');
+ assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'new',
+ "value should be properly updated");
+ $phoneLink = list.$('a.o_form_uri.o_field_widget');
+ assert.strictEqual($phoneLink.length, 5,
+ "should still have anchors with correct classes");
+ assert.strictEqual($phoneLink.first().attr('href'), 'tel:new',
+ "should still have proper tel prefix");
+
+ list.destroy();
+ });
+
+ QUnit.test('phone field does not allow html injections', function (assert) {
+ assert.expect(1);
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch:'',
+ res_id: 1,
+ viewOptions: {
+ mode: 'edit',
+ },
+ });
+
+ var val = '';
+ form.$('input').val(val).trigger('input');
+
+ // save
+ form.$buttons.find('.o_form_button_save').click();
+ assert.strictEqual(form.$('.o_field_widget').text(), val,
+ "value should have been correctly escaped");
+
+ form.destroy();
+ });
+});
+});
+});
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index 68643f7b48d..4036594609f 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -3269,7 +3269,6 @@ QUnit.module('basic_fields', {
list.destroy();
});
-
QUnit.module('FieldFloatTime');
QUnit.test('float_time field in form view', function (assert) {
@@ -3400,106 +3399,6 @@ QUnit.module('basic_fields', {
QUnit.module('PhoneWidget');
- QUnit.test('phone field in form view on extra small screens', function (assert) {
- assert.expect(7);
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:'',
- res_id: 1,
- config: {
- device: {
- size_class: config.device.SIZES.VSM,
- },
- },
- });
-
- var $phoneLink = form.$('a.o_form_uri.o_field_widget');
- assert.strictEqual($phoneLink.length, 1,
- "should have a anchor with correct classes");
- assert.strictEqual($phoneLink.text(), 'yop',
- "value should be displayed properly");
- assert.strictEqual($phoneLink.attr('href'), 'tel:yop',
- "should have proper tel prefix");
-
- // switch to edit mode and check the result
- form.$buttons.find('.o_form_button_edit').click();
- assert.strictEqual(form.$('input[type="text"].o_field_widget').length, 1,
- "should have an input for the phone field");
- assert.strictEqual(form.$('input[type="text"].o_field_widget').val(), 'yop',
- "input should contain field value in edit mode");
-
- // change value in edit mode
- form.$('input[type="text"].o_field_widget').val('new').trigger('input');
-
- // save
- form.$buttons.find('.o_form_button_save').click();
- $phoneLink = form.$('a.o_form_uri.o_field_widget');
- assert.strictEqual($phoneLink.text(), 'new',
- "new value should be displayed properly");
- assert.strictEqual($phoneLink.attr('href'), 'tel:new',
- "should still have proper tel prefix");
-
- form.destroy();
- });
-
- QUnit.test('phone field in editable list view on extra small screens', function (assert) {
- assert.expect(10);
-
- var list = createView({
- View: ListView,
- model: 'partner',
- data: this.data,
- arch: '',
- config: {
- device: {
- size_class: config.device.SIZES.VSM,
- },
- },
- });
-
- assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').length, 5,
- "should have 5 cells");
- assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'yop',
- "value should be displayed properly");
-
- var $phoneLink = list.$('a.o_form_uri.o_field_widget');
- assert.strictEqual($phoneLink.length, 5,
- "should have anchors with correct classes");
- assert.strictEqual($phoneLink.first().attr('href'), 'tel:yop',
- "should have proper tel prefix");
-
- // Edit a line and check the result
- var $cell = list.$('tbody td:not(.o_list_record_selector)').first();
- $cell.click();
- assert.ok($cell.parent().hasClass('o_selected_row'), 'should be set as edit mode');
- assert.strictEqual($cell.find('input').val(), 'yop',
- 'should have the corect value in internal input');
- $cell.find('input').val('new').trigger('input');
-
- // save
- list.$buttons.find('.o_list_button_save').click();
- $cell = list.$('tbody td:not(.o_list_record_selector)').first();
- assert.ok(!$cell.parent().hasClass('o_selected_row'), 'should not be in edit mode anymore');
- assert.strictEqual(list.$('tbody td:not(.o_list_record_selector)').first().text(), 'new',
- "value should be properly updated");
- $phoneLink = list.$('a.o_form_uri.o_field_widget');
- assert.strictEqual($phoneLink.length, 5,
- "should still have anchors with correct classes");
- assert.strictEqual($phoneLink.first().attr('href'), 'tel:new',
- "should still have proper tel prefix");
-
- list.destroy();
- });
-
QUnit.test('phone field in form view on normal screens', function (assert) {
assert.expect(5);
@@ -3517,7 +3416,7 @@ QUnit.module('basic_fields', {
res_id: 1,
config: {
device: {
- size_class: config.device.SIZES.MD,
+ size_class: config.device.SIZES.LG,
},
},
});
@@ -3556,7 +3455,7 @@ QUnit.module('basic_fields', {
arch: '',
config: {
device: {
- size_class: config.device.SIZES.MD,
+ size_class: config.device.SIZES.LG,
},
},
});
@@ -3589,42 +3488,6 @@ QUnit.module('basic_fields', {
list.destroy();
});
- QUnit.test('phone field does not allow html injections', function (assert) {
- assert.expect(1);
-
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch:'',
- res_id: 1,
- viewOptions: {
- mode: 'edit',
- },
- config: {
- device: {
- size_class: config.device.SIZES.VSM,
- },
- },
- });
-
- var val = '';
- form.$('input').val(val).trigger('input');
-
- // save
- form.$buttons.find('.o_form_button_save').click();
- assert.strictEqual(form.$('.o_field_widget').text(), val,
- "value should have been correctly escaped");
-
- form.destroy();
- });
-
QUnit.test('use TAB to navigate to a phone field', function (assert) {
assert.expect(2);
diff --git a/addons/web/static/tests/fields/relational_fields_mobile_tests.js b/addons/web/static/tests/fields/relational_fields_mobile_tests.js
new file mode 100644
index 00000000000..8a1ab7ce1a5
--- /dev/null
+++ b/addons/web/static/tests/fields/relational_fields_mobile_tests.js
@@ -0,0 +1,119 @@
+odoo.define('web.relational_fields_tests', function (require) {
+"use strict";
+
+var FormView = require('web.FormView');
+var testUtils = require('web.test_utils');
+
+var createView = testUtils.createView;
+
+QUnit.module('fields', {}, function () {
+
+QUnit.module('relational_fields', {
+ beforeEach: function () {
+ this.data = {
+ partner: {
+ fields: {
+ display_name: { string: "Displayed name", type: "char" },
+ foo: {string: "Foo", type: "char", default: "My little Foo Value"},
+ bar: {string: "Bar", type: "boolean", default: true},
+ int_field: {string: "int_field", type: "integer", sortable: true},
+ qux: {string: "Qux", type: "float", digits: [16,1] },
+ p: {string: "one2many field", type: "one2many", relation: 'partner', relation_field: 'trululu'},
+ trululu: {string: "Trululu", type: "many2one", relation: 'partner'},
+ },
+ records: [{
+ id: 1,
+ display_name: "first record",
+ bar: true,
+ foo: "yop",
+ int_field: 10,
+ qux: 0.44,
+ p: [],
+ trululu: 4,
+ }, {
+ id: 2,
+ display_name: "second record",
+ bar: true,
+ foo: "blip",
+ int_field: 9,
+ qux: 13,
+ p: [],
+ trululu: 1,
+ }, {
+ id: 4,
+ display_name: "aaa",
+ bar: false,
+ }],
+ onchanges: {},
+ },
+ };
+ }
+}, function () {
+
+ QUnit.module('FieldOne2Many');
+
+ QUnit.test('one2many kanban: deletion in mobile', function (assert) {
+ assert.expect(9);
+
+ this.data.partner.records[0].p = [1, 2];
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/call_kw/partner/write') {
+ var commands = args.args[1].p;
+ assert.strictEqual(commands.length, 2,
+ 'should have generated two commands');
+ assert.ok(commands[0][0] === 4 && commands[0][1] === 2,
+ 'should have generated the command 2 (DELETE) with id 1');
+ assert.ok(commands[1][0] === 2 && commands[1][1] === 1,
+ 'should have generated the command 2 (DELETE) with id 2');
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.strictEqual(form.$('.o_field_one2many .o-kanban-button-new').length, 0,
+ '"Create" button should not be visible in readonly');
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('.o_field_one2many .o-kanban-button-new').length, 1,
+ '"Create" button should be visible in edit');
+ assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 2,
+ "should have 2 records");
+
+ // open and delete record
+ form.$('.oe_kanban_global_click').first().click();
+ assert.strictEqual($('.modal .modal-footer .o_btn_remove').length, 1,
+ 'there should be a Remove button in the modal footer');
+ $('.modal .modal-footer .o_btn_remove').click();
+ assert.strictEqual($('.o_modal').length, 0, "there shoul be no more modal");
+ assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 1,
+ 'should contain 1 records');
+
+ // save and check that the correct command has been generated
+ form.$buttons.find('.o_form_button_save').click();
+ form.destroy();
+ });
+});
+});
+});
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index bb7b4a446e6..245ff8f7a07 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -4909,74 +4909,6 @@ QUnit.module('relational_fields', {
form.destroy();
});
- QUnit.test('one2many kanban: deletion in mobile', function (assert) {
- assert.expect(9);
-
- this.data.partner.records[0].p = [1, 2];
- var form = createView({
- View: FormView,
- model: 'partner',
- data: this.data,
- arch: '',
- res_id: 1,
- mockRPC: function (route, args) {
- if (route === '/web/dataset/call_kw/partner/write') {
- var commands = args.args[1].p;
- assert.strictEqual(commands.length, 2,
- 'should have generated two commands');
- assert.ok(commands[0][0] === 4 && commands[0][1] === 2,
- 'should have generated the command 2 (DELETE) with id 1');
- assert.ok(commands[1][0] === 2 && commands[1][1] === 1,
- 'should have generated the command 2 (DELETE) with id 2');
- }
- return this._super.apply(this, arguments);
- },
- config: {
- device: {
- size_class: config.device.SIZES.XS,
- },
- },
- });
-
- assert.strictEqual(form.$('.o_field_one2many .o-kanban-button-new').length, 0,
- '"Create" button should not be visible in readonly');
-
- form.$buttons.find('.o_form_button_edit').click();
-
- assert.strictEqual(form.$('.o_field_one2many .o-kanban-button-new').length, 1,
- '"Create" button should be visible in edit');
- assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 2,
- "should have 2 records");
-
- // open and delete record
- form.$('.oe_kanban_global_click').first().click();
- assert.strictEqual($('.modal .modal-footer .o_btn_remove').length, 1,
- 'there should be a Remove button in the modal footer');
- $('.modal .modal-footer .o_btn_remove').click();
- assert.strictEqual($('.o_modal').length, 0, "there shoul be no more modal");
- assert.strictEqual(form.$('.o_kanban_record:not(.o_kanban_ghost)').length, 1,
- 'should contain 1 records');
-
- // save and check that the correct command has been generated
- form.$buttons.find('.o_form_button_save').click();
- form.destroy();
- });
-
QUnit.test('one2many kanban: create action disabled', function (assert) {
assert.expect(3);
diff --git a/addons/web/static/tests/views/form_mobile_tests.js b/addons/web/static/tests/views/form_mobile_tests.js
new file mode 100644
index 00000000000..05a19a27668
--- /dev/null
+++ b/addons/web/static/tests/views/form_mobile_tests.js
@@ -0,0 +1,92 @@
+odoo.define('web.form_mobile_tests', function (require) {
+"use strict";
+
+var FormView = require('web.FormView');
+var testUtils = require('web.test_utils');
+
+var createView = testUtils.createView;
+
+QUnit.module('Views', {
+ beforeEach: function () {
+ this.data = {
+ partner: {
+ fields: {
+ display_name: { string: "Displayed name", type: "char" },
+ foo: {string: "Foo", type: "char", default: "My little Foo Value"},
+ bar: {string: "Bar", type: "boolean"},
+ },
+ records: [{
+ id: 1,
+ display_name: "first record",
+ bar: true,
+ foo: "yop",
+ }, {
+ id: 2,
+ display_name: "second record",
+ bar: true,
+ foo: "blip",
+ }, {
+ id: 4,
+ display_name: "aaa",
+ state: "ef",
+ }, {
+ id: 5,
+ display_name: "aaa",
+ foo:'',
+ bar: false,
+ }],
+ onchanges: {},
+ },
+ };
+ }
+}, function () {
+
+ QUnit.module('FormView');
+
+ QUnit.test('switching to next/previous record on swipe in readonly mode', function (assert) {
+ assert.expect(6);
+
+ // mimic touchSwipe library's swipe method
+ var oldSwipe = $.fn.swipe;
+ var swipeLeft, swipeRight;
+ $.fn.swipe = function (params) {
+ swipeLeft = params.swipeLeft.bind(this);
+ swipeRight = params.swipeRight.bind(this);
+ };
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '' +
+ '' +
+ '' +
+ '' +
+ '',
+ viewOptions: {
+ ids: [1, 2],
+ index: 0,
+ },
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (route === '/web/dataset/call_kw/partner/read') {
+ assert.step(args.args[0][0]);
+ }
+ return this._super(route, args);
+ },
+ });
+
+ swipeLeft();
+ assert.strictEqual(form.pager.$('.o_pager_value').text(), '2', 'pager value should be 2');
+
+ swipeRight();
+ assert.strictEqual(form.pager.$('.o_pager_value').text(), '1', 'pager value should be 1');
+
+ assert.verifySteps([1, 2, 1]);
+
+ $.fn.swipe = oldSwipe;
+ form.destroy();
+ });
+});
+
+});
diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js
index 219972f46c9..222317ae3e4 100644
--- a/addons/web/static/tests/views/form_tests.js
+++ b/addons/web/static/tests/views/form_tests.js
@@ -7036,7 +7036,6 @@ QUnit.module('Views', {
form.destroy();
});
-
QUnit.module('FormViewTABMainButtons');
QUnit.test('using tab in an empty required string field should not move to the next field',function(assert) {
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml
index a33592a602a..975d5fc63ec 100644
--- a/addons/web/views/webclient_templates.xml
+++ b/addons/web/views/webclient_templates.xml
@@ -625,6 +625,9 @@
+
+
+