diff options
| author | Aaron Bohy <aab@odoo.com> | 2017-09-29 08:50:28 +0200 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2017-09-29 16:02:36 +0200 |
| commit | 36b4d78a2678806b25aaa5650b64496f9da9d205 (patch) | |
| tree | e5325edb2d1a26530f587325e249d2b76136951a /addons/web/static/src | |
| parent | f9dc8cf76dc8cd9bd091c7e0c4bdf368a9e47b23 (diff) | |
[FIX] web,mail: reset focus after onchange on x2many
Rev. 2728fe1 introduced an attempt of restoring the focus on the
currently focused field in an x2many list after an onchange
returned, and the list has been updated. However, it was often
wrong (e.g. leaving a field by pressing tab/shift+tab, clicking on
another field...) and reset the focus on an unexpected field.
In addition to fixing this issue, this rev. also unifies the helper
functions to get and set the cursor positions in an input or
textarea, as they were duplicated between mail and web.
Diffstat (limited to 'addons/web/static/src')
4 files changed, 54 insertions, 56 deletions
diff --git a/addons/web/static/src/js/core/dom.js b/addons/web/static/src/js/core/dom.js index 6ca13f18e4e..b39f39cab9c 100644 --- a/addons/web/static/src/js/core/dom.js +++ b/addons/web/static/src/js/core/dom.js @@ -157,26 +157,18 @@ return { return $to_detach.detach(); }, /** - * Returns the input or textarea cursor offset + * Returns the selection range of an input or textarea * * @param {Object} DOM item input or texteara - * @returns {Object} + * @returns {Object} range + * @returns {integer} range.start + * @returns {integer} range.end */ - getCursor: function (node) { - node = $(node)[0]; - var offset = 0; - var length = 0; - if ('selectionStart' in node) { - offset = node.selectionStart; - length = node.selectionEnd - offset; - } else if ('selection' in document) { - node.focus(); - var sel = document.selection.createRange(); - length = document.selection.createRange().text.length; - sel.moveStart('character', -node.value.length); - offset = sel.text.length - length; - } - return { offset: offset, length: length }; + getSelectionRange: function (node) { + return { + start: node.selectionStart, + end: node.selectionEnd, + }; }, /** * Returns the distance between a DOM element and the top-left corner of the @@ -281,21 +273,21 @@ return { return $container; }, /** - * Moves the cursor position in a DOM element (input or texteara) + * Sets the selection range of a given input or textarea * * @param {Object} node DOM element (input or textarea) - * @param {Int} pos cursor position + * @param {integer} range.start + * @param {integer} range.end */ - setCursor: function (node, pos) { - node = $(node).first().focus()[0]; - if ('createTextRange' in node) { - var textRange = node.createTextRange(); - textRange.collapse(true); - textRange.moveEnd(pos); - textRange.moveStart(pos); - textRange.select(); - } else if ('setSelectionRange' in node) { - node.setSelectionRange(pos, pos); + setSelectionRange: function (node, range) { + if (node.setSelectionRange){ + node.setSelectionRange(range.start, range.end); + } else if (node.createTextRange){ + node.createTextRange() + .collapse(true) + .moveEnd('character', range.start) + .moveStart('character', range.end) + .select(); } }, }; diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index f518c3df354..2851f31c673 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -20,7 +20,6 @@ var ControlPanel = require('web.ControlPanel'); var dialogs = require('web.view_dialogs'); var core = require('web.core'); var data = require('web.data'); -var dom = require('web.dom'); var Dialog = require('web.Dialog'); var KanbanRenderer = require('web.KanbanRenderer'); var ListRenderer = require('web.ListRenderer'); @@ -905,33 +904,11 @@ var FieldX2Many = AbstractField.extend({ this.lastInitialEvent = undefined; if (Object.keys(changes).length) { this.lastInitialEvent = ev; - // store the cursor position to restore it once potential onchanges have been applied - var self = this; - var editableID = this.renderer.getEditableRecordID(); - var datapoint = _.find(this.value.data, {id: editableID}); - - var ref = datapoint && datapoint.ref; - var cursor = ref && dom.getCursor(ev.target.getFocusableElement()); - var fieldName = ev.target.name; - - var def = this._setValue({ + this._setValue({ operation: 'UPDATE', id: ev.data.dataPointID, data: changes, }); - - if (ref) { - def.then(function () { - var nextEditableRecordID = self.renderer.getEditableRecordID(); - if (nextEditableRecordID && editableID !== nextEditableRecordID) { - return; - } - var datapoint = _.find(self.value.data, {ref: ref}); - if (datapoint) { - self.renderer.focusField(datapoint.id, fieldName, cursor && cursor.offset); - } - }); - } } }, /** 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 24922622a42..93dd1f4fb73 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -127,7 +127,7 @@ var BasicRenderer = AbstractRenderer.extend({ this.editRecord(id); if (typeof offset === "number") { var field = _.findWhere(this.allFieldWidgets[id], {name: fieldName}); - dom.setCursor(field.getFocusableElement(), offset); + dom.setSelectionRange(field.getFocusableElement().get(0), {start: offset, end: offset}); } }, 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 5bd24044c39..7f5eba36418 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 @@ -12,6 +12,7 @@ odoo.define('web.EditableListRenderer', function (require) { * view. It uses the same widgets, but the code is totally stand alone. */ var core = require('web.core'); +var dom = require('web.dom'); var ListRenderer = require('web.ListRenderer'); var utils = require('web.utils'); @@ -127,6 +128,17 @@ ListRenderer.include({ */ confirmUpdate: function (state, id, fields, ev) { var self = this; + + // store the cursor position to restore it once potential onchanges have + // been applied + var currentRowID, currentWidget, focusedElement, selectionRange; + if (self.currentRow !== null) { + currentRowID = this.state.data[this.currentRow].id; + currentWidget = this.allFieldWidgets[currentRowID][this.currentCol]; + focusedElement = currentWidget.getFocusableElement().get(0); + selectionRange = dom.getSelectionRange(focusedElement); + } + var oldData = this.state.data; this.state = state; return this.confirmChange(state, id, fields, ev).then(function () { @@ -160,6 +172,13 @@ ListRenderer.include({ }); if (self.currentRow !== null) { self.currentRow = newRowIndex; + return self._selectCell(newRowIndex, self.currentCol, {force: true}).then(function () { + // restore the cursor position + currentRowID = self.state.data[newRowIndex].id; + currentWidget = self.allFieldWidgets[currentRowID][self.currentCol]; + focusedElement = currentWidget.getFocusableElement().get(0); + dom.setSelectionRange(focusedElement, selectionRange); + }); } }); }, @@ -544,14 +563,18 @@ ListRenderer.include({ * triggered the cell selection * selected from the colIndex 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) { + options = options || {}; // Do nothing if the user tries to select current cell - if (rowIndex === this.currentRow && colIndex === this.currentCol) { + if (!options.force && rowIndex === this.currentRow && colIndex === this.currentCol) { return $.when(); } - var wrap = (!options || options.wrap === undefined) ? true : options.wrap; + var wrap = options.wrap === undefined ? true : options.wrap; // Select the row then activate the widget in the correct cell var self = this; @@ -599,6 +622,12 @@ ListRenderer.include({ // To select a row, the currently selected one must be unselected first var self = this; return this.unselectRow().then(function () { + if (self.state.data.length <= rowIndex) { + // The row to selected doesn't exist anymore (probably because + // an onchange triggered when unselecting the previous one + // removes rows) + return $.Deferred().reject(); + } // Notify the controller we want to make a record editable var def = $.Deferred(); self.trigger_up('edit_line', { |
