Comment déplacer le curseur à la fin de l'entité contenteditable
-
13-09-2019 - |
Question
Je dois passer caret à la fin du nœud contenteditable
comme sur Gmail widget Notes.
Je lis les discussions sur StackOverflow, mais ces solutions sont basées sur l'utilisation des intrants et ils ne fonctionne pas avec des éléments contenteditable
.
La solution
Il y a aussi un autre problème.
La solution de Nico Burns, fonctionne si le div contenteditable
ne contient pas d'autres éléments multilined.
Par exemple, si un div contient d'autres divs, et ces autres divs contiennent d'autres choses à l'intérieur, pourrait se produire quelques problèmes.
Pour les résoudre, je me suis arrangé la solution suivante, qui est une amélioration de la Nico l » un:
//Namespace management idea from http://enterprisejquery.com/2010/10/how-good-c-habits-can-encourage-bad-javascript-habits-part-1/
(function( cursorManager ) {
//From: http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
var voidNodeTags = ['AREA', 'BASE', 'BR', 'COL', 'EMBED', 'HR', 'IMG', 'INPUT', 'KEYGEN', 'LINK', 'MENUITEM', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR', 'BASEFONT', 'BGSOUND', 'FRAME', 'ISINDEX'];
//From: https://stackoverflow.com/questions/237104/array-containsobj-in-javascript
Array.prototype.contains = function(obj) {
var i = this.length;
while (i--) {
if (this[i] === obj) {
return true;
}
}
return false;
}
//Basic idea from: https://stackoverflow.com/questions/19790442/test-if-an-element-can-contain-text
function canContainText(node) {
if(node.nodeType == 1) { //is an element node
return !voidNodeTags.contains(node.nodeName);
} else { //is not an element node
return false;
}
};
function getLastChildElement(el){
var lc = el.lastChild;
while(lc && lc.nodeType != 1) {
if(lc.previousSibling)
lc = lc.previousSibling;
else
break;
}
return lc;
}
//Based on Nico Burns's answer
cursorManager.setEndOfContenteditable = function(contentEditableElement)
{
while(getLastChildElement(contentEditableElement) &&
canContainText(getLastChildElement(contentEditableElement))) {
contentEditableElement = getLastChildElement(contentEditableElement);
}
var range,selection;
if(document.createRange)//Firefox, Chrome, Opera, Safari, IE 9+
{
range = document.createRange();//Create a range (a range is a like the selection but invisible)
range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
selection = window.getSelection();//get the selection object (allows you to change selection)
selection.removeAllRanges();//remove any selections already made
selection.addRange(range);//make the range you have just created the visible selection
}
else if(document.selection)//IE 8 and lower
{
range = document.body.createTextRange();//Create a range (a range is a like the selection but invisible)
range.moveToElementText(contentEditableElement);//Select the entire contents of the element with the range
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
range.select();//Select the range (make it the visible selection
}
}
}( window.cursorManager = window.cursorManager || {}));
Utilisation:
var editableDiv = document.getElementById("my_contentEditableDiv");
cursorManager.setEndOfContenteditable(editableDiv);
De cette façon, le curseur est positionné sûrement à la fin du dernier élément, éventuellement imbriqués.
EDIT # 1 : Pour être plus générique, l'instruction while doit également tenir compte de toutes les autres balises qui ne peuvent pas contenir du texte. Ces éléments sont appelés éléments vides , et cette question il existe quelques méthodes sur la façon de tester si un élément est vide. Donc, en supposant qu'il existe une fonction appelée canContainText
qui retourne true
si l'argument n'est pas un élément vide, la ligne de code suivante:
contentEditableElement.lastChild.tagName.toLowerCase() != 'br'
devrait être remplacé par:
canContainText(getLastChildElement(contentEditableElement))
EDIT # 2 : Le code ci-dessus est entièrement mis à jour, avec tous les changements décrits et discutés
Autres conseils
La solution de Geowa4 fonctionnera pour un textarea, mais pas pour un élément contenteditable.
Cette solution est destiné à déplacer le curseur à l'extrémité d'un élément contenteditable. Il devrait fonctionner dans tous les navigateurs qui prennent en charge contenteditable.
function setEndOfContenteditable(contentEditableElement)
{
var range,selection;
if(document.createRange)//Firefox, Chrome, Opera, Safari, IE 9+
{
range = document.createRange();//Create a range (a range is a like the selection but invisible)
range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
selection = window.getSelection();//get the selection object (allows you to change selection)
selection.removeAllRanges();//remove any selections already made
selection.addRange(range);//make the range you have just created the visible selection
}
else if(document.selection)//IE 8 and lower
{
range = document.body.createTextRange();//Create a range (a range is a like the selection but invisible)
range.moveToElementText(contentEditableElement);//Select the entire contents of the element with the range
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
range.select();//Select the range (make it the visible selection
}
}
Il peut être utilisé par un code similaire à:
elem = document.getElementById('txt1');//This is the element that you want to move the caret to the end of
setEndOfContenteditable(elem);
Si vous ne se soucient pas de navigateurs plus anciens, celui-ci a fait l'affaire pour moi.
// [optional] make sure focus is on the element
yourContentEditableElement.focus();
// select all the content in the element
document.execCommand('selectAll', false, null);
// collapse selection to the end
document.getSelection().collapseToEnd();
Il est possible de faire ensemble curseur jusqu'à la fin à travers la gamme:
setCaretToEnd(target/*: HTMLDivElement*/) {
const range = document.createRange();
const sel = window.getSelection();
range.selectNodeContents(target);
range.collapse(false);
sel.removeAllRanges();
sel.addRange(range);
target.focus();
range.detach(); // optimization
// set scroll to the end if multiline
target.scrollTop = target.scrollHeight;
}
J'ai eu un problème similaire en essayant de faire un élément modifiable. Il a été possible dans Chrome et FireFox, mais dans FireFox le caret soit allé au début de l'entrée ou il est allé un espace après la fin de l'entrée. Très déroutant pour l'utilisateur final, je pense, en essayant de modifier le contenu.
J'ai trouvé aucune solution à essayer plusieurs choses. La seule chose qui a fonctionné pour moi était de « faire le tour du problème » en mettant un dans mon ancien texte d'entrée ordinaire. Maintenant ça marche. On dirait saigne encore tech bord « contenu modifiable », qui peut travailler ou non que vous le souhaitez à travailler, en fonction du contexte.
En déplaçant le curseur à la fin de l'échelle modifiable en réponse à concentrer l'action:
moveCursorToEnd(el){
if(el.innerText && document.createRange)
{
window.setTimeout(() =>
{
let selection = document.getSelection();
let range = document.createRange();
range.setStart(el.childNodes[0],el.innerText.length);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
}
,1);
}
}
Et l'appeler dans le gestionnaire d'événements (React ici):
onFocus={(e) => this.moveCursorToEnd(e.target)}}