[FIX] arrow keys handling in editable listview field when field is not a text-based input

* Correctly handle HTMLInputElement#selectionStart throwing an error
  (checkbox and radio)

* Correctly handle elements without a selectionStart at all (select)

=> should ~always let the go-to-other-field behavior go through

bzr revid: xmo@openerp.com-20120726123819-74n1gl01p6befgkp
This commit is contained in:
Xavier Morel 2012-07-26 14:38:19 +02:00
parent 66a7dc6359
commit bf4c39cdf1
1 changed files with 58 additions and 17 deletions

View File

@ -425,26 +425,64 @@ openerp.web.list_editable = function (instance) {
keyup_ESCAPE: function () { keyup_ESCAPE: function () {
return this.cancel_edition(); return this.cancel_edition();
}, },
/**
* Gets the selection range (start, end) for the provided element,
* returns ``null`` if it can't get a range.
*
* @private
*/
_text_selection_range: function (el) { _text_selection_range: function (el) {
if (el.selectionStart !== undefined) { var selectionStart;
try {
selectionStart = el.selectionStart;
} catch (e) {
// radio or checkbox throw on selectionStart access
return null;
}
if (selectionStart !== undefined) {
return { return {
start: el.selectionStart, start: selectionStart,
end: el.selectionEnd end: el.selectionEnd
}; };
} else if(document.body.createTextRange) { } else if (document.body.createTextRange) {
throw new Error("Implement text range handling for MSIE"); throw new Error("Implement text range handling for MSIE");
var sel = document.body.createTextRange(); var sel = document.body.createTextRange();
if (sel.parentElement() === el) { if (sel.parentElement() === el) {
} }
} }
// Element without selection ranges (select, div/@contenteditable)
return null;
}, },
_text_cursor: function (el) { _text_cursor: function (el) {
var selection = this._text_selection_range(el); var selection = this._text_selection_range(el);
if (selection.start !== selection.end) { if (!selection) {
return null; return null;
} }
return selection.start; if (selection.start !== selection.end) {
return {position: null, collapsed: false};
}
return {position: selection.start, collapsed: true};
},
/**
* Checks if the cursor is at the start of the provided el
*
* @param {HTMLInputElement | HTMLTextAreaElement}
* @returns {Boolean}
* @private
*/
_at_start: function (cursor, el) {
return cursor.collapsed && (cursor.position === 0);
},
/**
* Checks if the cursor is at the end of the provided el
*
* @param {HTMLInputElement | HTMLTextAreaElement}
* @returns {Boolean}
* @private
*/
_at_end: function (cursor, el) {
return cursor.collapsed && (cursor.position === el.value.length);
}, },
/** /**
* @param DOMEvent event * @param DOMEvent event
@ -454,10 +492,11 @@ openerp.web.list_editable = function (instance) {
*/ */
_key_move_record: function (event, record_direction, is_valid_move) { _key_move_record: function (event, record_direction, is_valid_move) {
if (!this.editor.is_editing('edit')) { return $.when(); } if (!this.editor.is_editing('edit')) { return $.when(); }
// FIXME: assumes editable widgets are input-type elements var cursor = this._text_cursor(event.target);
var index = this._text_cursor(event.target); // if text-based input (has a cursor)
// If selecting or not at the start of the input // and selecting (not collapsed) or not at a field boundary
if (!is_valid_move(event.target, index)) { return $.when(); } // don't move to the next record
if (cursor && !is_valid_move(event.target, cursor)) { return $.when(); }
event.preventDefault(); event.preventDefault();
var source_field = $(event.target).closest('[data-fieldname]') var source_field = $(event.target).closest('[data-fieldname]')
@ -466,13 +505,15 @@ openerp.web.list_editable = function (instance) {
}, },
keydown_UP: function (e) { keydown_UP: function (e) {
return this._key_move_record(e, 'pred', function (el, index) { var self = this;
return index === 0; return this._key_move_record(e, 'pred', function (el, cursor) {
return self._at_start(cursor, el);
}); });
}, },
keydown_DOWN: function (e) { keydown_DOWN: function (e) {
return this._key_move_record(e, 'succ', function (el, index) { var self = this;
return index === el.value.length; return this._key_move_record(e, 'succ', function (el, cursor) {
return self._at_end(cursor, el);
}); });
}, },
@ -480,8 +521,8 @@ openerp.web.list_editable = function (instance) {
// If the cursor is at the beginning of the field // If the cursor is at the beginning of the field
var source_field = $(e.target).closest('[data-fieldname]') var source_field = $(e.target).closest('[data-fieldname]')
.attr('data-fieldname'); .attr('data-fieldname');
var index = this._text_cursor(e.target); var cursor = this._text_cursor(e.target);
if (index !== 0) { return $.when(); } if (cursor && !this._at_start(cursor, e.target)) { return $.when(); }
var fields_order = this.editor.form.fields_order; var fields_order = this.editor.form.fields_order;
var field_index = _(fields_order).indexOf(source_field); var field_index = _(fields_order).indexOf(source_field);
@ -504,8 +545,8 @@ openerp.web.list_editable = function (instance) {
// looking for new fields at the right // looking for new fields at the right
var source_field = $(e.target).closest('[data-fieldname]') var source_field = $(e.target).closest('[data-fieldname]')
.attr('data-fieldname'); .attr('data-fieldname');
var index = this._text_cursor(e.target); var cursor = this._text_cursor(e.target);
if (index !== e.target.value.length) { return $.when(); } if (cursor && !this._at_end(cursor, e.target)) { return $.when(); }
var fields_order = this.editor.form.fields_order; var fields_order = this.editor.form.fields_order;
var field_index = _(fields_order).indexOf(source_field); var field_index = _(fields_order).indexOf(source_field);