web-dev-qa-db-fra.com

Contenter the curseur to the final of l'entité

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.

66
avsej

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

22
Vito Gentile

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);
202
Nico Burns

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; 
}
2
Andrew

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();
1
Juank

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)}} 
0
Maxim Saplin

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. 

0
Panu Logic