Je dois déplacer le curseur à la fin du noeud contenteditable
, comme sur le widget de notes Gmail.
J'ai lu les discussions sur StackOverflow, mais ces solutions sont basées sur l'utilisation d'entrées et ne fonctionnent pas avec les éléments contenteditable
.
Il y a aussi un autre problème.
La solution de Nico Burns fonctionne si le div de contenteditable
ne contient pas d’autres éléments multilignes.
Par exemple, si un div contient d'autres divs, et que ces autres divs contiennent d'autres éléments à l'intérieur, des problèmes peuvent survenir.
Afin de les résoudre, j'ai arrangé la solution suivante, qui est une amélioration de celle de Nico :
//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 || {}));
Usage:
var editableDiv = document.getElementById("my_contentEditableDiv");
cursorManager.setEndOfContenteditable(editableDiv);
De cette façon, le curseur est sûrement positionné à la fin du dernier élément, éventuellement imbriqué.
EDIT # 1: Pour être plus générique, l'instruction while devrait également prendre en compte toutes les autres balises qui ne peuvent pas contenir de texte. Ces éléments sont nommés éléments void, et dans cette question , il existe des méthodes permettant de vérifier 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 void, la ligne de code suivante:
contentEditableElement.lastChild.tagName.toLowerCase() != 'br'
doit être remplacé par:
canContainText(getLastChildElement(contentEditableElement))
EDIT # 2: le code ci-dessus est complètement mis à jour, avec tous les changements décrits et discutés
La solution de Geowa4 fonctionnera pour une zone de texte, mais pas pour un élément contenteditable.
Cette solution permet de déplacer le curseur à la fin d'un élément contenteditable. Cela devrait fonctionner dans tous les navigateurs supportant 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é avec un code similaire à:
elem = document.getElementById('txt1');//This is the element that you want to move the caret to the end of
setEndOfContenteditable(elem);
Il est possible de placer le curseur à la fin de la plage:
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;
}
Si vous ne vous souciez pas des anciens navigateurs, 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();
Déplacement du curseur à la fin de la plage modifiable en réponse à l'événement focus:
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 en l'appelant dans le gestionnaire d'événements (React here):
onFocus={(e) => this.moveCursorToEnd(e.target)}}
J'ai eu un problème similaire en essayant de rendre un élément modifiable. C'était possible dans Chrome et FireFox, mais dans FireFox, le curseur se trouvait au début de l'entrée ou bien un espace après la fin de l'entrée. Je trouve très déroutant pour l'utilisateur final d'essayer de modifier le contenu.
Je n'ai trouvé aucune solution en essayant plusieurs choses. La seule chose qui a fonctionné pour moi a été de "contourner le problème" en plaçant une vieille vieille entrée de texte PLACE DANS mon écran. Maintenant ça marche. On dirait que "contenu éditable" saigne toujours de la technologie Edge, qui peut ou non fonctionner comme vous le souhaitez, en fonction du contexte.