Bewegen des Cursors zum Ende der inhaltsbearbeitbaren Entität

83

Ich muss Caret contenteditablewie im Google Mail-Widget an das Ende des Knotens verschieben.

Ich habe Threads in StackOverflow gelesen, aber diese Lösungen basieren auf der Verwendung von Eingaben und funktionieren nicht mit contenteditableElementen.

avsej
quelle

Antworten:

27

Es gibt auch ein anderes Problem.

Die Lösung von Nico Burns funktioniert, wenn das contenteditablediv keine anderen mehrzeiligen Elemente enthält.

Wenn beispielsweise ein Div andere Divs enthält und diese anderen Divs andere Inhalte enthalten, können einige Probleme auftreten.

Um sie zu lösen, habe ich die folgende Lösung zusammengestellt, die eine Verbesserung der von Nico darstellt :

//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: /programming/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: /programming/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 || {}));

Verwendung:

var editableDiv = document.getElementById("my_contentEditableDiv");
cursorManager.setEndOfContenteditable(editableDiv);

Auf diese Weise wird der Cursor sicher am Ende des letzten Elements positioniert und schließlich verschachtelt.

EDIT # 1 : Um allgemeiner zu sein, sollte die while-Anweisung auch alle anderen Tags berücksichtigen, die keinen Text enthalten können. Diese Elemente werden als ungültige Elemente bezeichnet . In dieser Frage gibt es einige Methoden zum Testen, ob ein Element ungültig ist. Angenommen, es gibt eine Funktion namens canContainText, die zurückgibt, truewenn das Argument kein void-Element ist, die folgende Codezeile:

contentEditableElement.lastChild.tagName.toLowerCase() != 'br'

sollte ersetzt werden durch:

canContainText(getLastChildElement(contentEditableElement))

EDIT # 2 : Der obige Code wird mit allen beschriebenen und diskutierten Änderungen vollständig aktualisiert

Vito Gentile
quelle
Interessanterweise hätte ich erwartet, dass sich der Browser automatisch um diesen Fall kümmert (nicht, dass ich überrascht bin, dass dies nicht der Fall ist, Browser scheinen niemals die intuitiven Dinge mit inhaltsbearbeitbar zu machen). Haben Sie ein Beispiel für HTML, in dem Ihre Lösung funktioniert, meine jedoch nicht?
Nico Burns
In meinem Code gab es einen weiteren Fehler. Ich habe es repariert. Jetzt können Sie überprüfen, ob mein Code auf dieser Seite funktioniert , während Ihr Code nicht funktioniert
Vito Gentile
Ich Uncaught TypeError: Cannot read property 'nodeType' of nullerhalte eine Fehlermeldung bei Ihrer Funktion, sagt die Konsole, und dies kommt von der aufgerufenen Funktion getLastChildElement. Wissen Sie, was dieses Problem verursachen könnte?
Derek
@VitoGentile Es ist eine etwas alte Antwort, aber ich möchte darauf hinweisen, dass Ihre Lösung nur Blockelemente behandelt. Wenn sich Inline-Elemente darin befinden, wird der Cursor hinter diesem Inline-Element positioniert (wie span, em ...). Eine einfache Lösung besteht darin, Inline-Elemente als ungültige Tags zu betrachten und sie zu voidNodeTags hinzuzufügen, damit sie übersprungen werden.
medBouzid
238

Die Lösung von Geowa4 funktioniert für einen Textbereich, jedoch nicht für ein inhaltsbearbeitbares Element.

Diese Lösung dient dazu, das Caret an das Ende eines inhaltsbearbeitbaren Elements zu verschieben. Es sollte in allen Browsern funktionieren, die contenteditable unterstützen.

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
    }
}

Es kann von Code verwendet werden, der ähnlich ist wie:

elem = document.getElementById('txt1');//This is the element that you want to move the caret to the end of
setEndOfContenteditable(elem);
Nico Burns
quelle
1
Die Lösung von geowa4 funktioniert für Textbereiche in Chrom, nicht für inhaltsbearbeitbare Elemente in einem Browser. meins arbeitet für inhaltsbearbeitbare Elemente, aber nicht für Textbereiche.
Nico Burns
4
Dies ist die richtige Antwort auf diese Frage, perfekt, danke Nico.
Rob
7
Der selectNodeContentsTeil von Nico gab mir Fehler sowohl in Chrome als auch in FF (testete keine anderen Browser), bis ich herausfand, dass ich anscheinend .get(0)zu dem Element hinzufügen musste , dass ich die Funktion fütterte. Ich denke, das hat damit zu tun, dass ich jQuery anstelle von nacktem JS verwende. Ich habe dies von @jwarzech bei Frage 4233265 gelernt . Dank an alle!
Max Starkenburg
5
Ja, die Funktion erwartet ein DOM-Element, kein jQuery-Objekt. .get(0)Ruft das dom-Element ab, das jQuery intern speichert. Sie können auch anhängen [0], was .get(0)in diesem Zusammenhang gleichbedeutend ist .
Nico Burns
1
@Nico Burns: Ich habe deine Methode ausprobiert und sie hat unter FireFox nicht funktioniert.
Lewis
25

Wenn Sie sich nicht für ältere Browser interessieren, hat dieser den Trick für mich getan.

// [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();
Juank
quelle
Dies ist das einzige, was für mich in einem Hintergrundskript für eine Chrome-Erweiterung funktioniert hat
Rob
1
Das funktioniert gut. Getestet auf Chrome 71.0.3578.98 und WebView auf Android 5.1.
Maswerdna
3
document.execCommandist jetzt veraltet developer.mozilla.org/en-US/docs/Web/API/Document/execCommand .
Webprogrammierer
2020 und dies funktioniert immer noch in Chrome Version 83.0.4103.116 (Official Build) (64-Bit)
user2677034
das ist der Gewinner!
MNN TNK
7

Es ist möglich, den Cursor durch den Bereich auf das Ende zu setzen:

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; 
}
am0wa
quelle
Mit dem obigen Code macht es den Trick - aber ich möchte in der Lage sein, den Cursor an eine beliebige Stelle innerhalb des inhaltsbearbeitbaren Div zu bewegen und von diesem Punkt aus weiter zu tippen - z. B. hat der Benutzer beispielsweise einen Tippfehler erkannt ... Wie würde Ich ändere Ihren Code oben dazu?
Zabs
1
@Zabs ist ziemlich einfach: Nicht setCaretToEnd()jedes Mal aufrufen - nur dann aufrufen, wenn Sie es benötigen: z. B. nach dem Kopieren und Einfügen oder nach dem Einschränken der Nachrichtenlänge.
am0wa
Das hat bei mir funktioniert. Nachdem der Benutzer ein Tag ausgewählt hat, bewege ich den Cursor im contenteditable div zum Ende.
Ayudh
0

Ich hatte ein ähnliches Problem beim Versuch, ein Element bearbeitbar zu machen. In Chrome und FireFox war dies möglich, aber in FireFox ging das Caret entweder an den Anfang der Eingabe oder ein Leerzeichen nach dem Ende der Eingabe. Sehr verwirrend für den Endbenutzer, der versucht, den Inhalt zu bearbeiten.

Ich habe keine Lösung gefunden, indem ich verschiedene Dinge ausprobiert habe. Das einzige, was für mich funktionierte, war, "das Problem zu umgehen", indem ich eine einfache alte Texteingabe in meine einfügte. Jetzt funktioniert es. Es scheint, als ob "inhaltsbearbeitbar" immer noch auf dem neuesten Stand der Technik ist, die je nach Kontext möglicherweise so funktioniert, wie Sie es möchten.

Panu Logic
quelle
0

Bewegen des Cursors zum Ende der bearbeitbaren Spanne als Reaktion auf das Fokusereignis:

  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);
    }
  }

Und im Event-Handler aufrufen (Hier reagieren):

onFocus={(e) => this.moveCursorToEnd(e.target)}} 
Maxim Saplin
quelle
0

Das Problem mit contenteditable <div>und <span>ist behoben, wenn Sie es zunächst eingeben. Eine Problemumgehung hierfür könnte darin bestehen, ein Fokusereignis für Ihr div-Element und für diese Funktion auszulösen, das zu löschen und wieder aufzufüllen, was bereits im div-Element enthalten war. Auf diese Weise wird das Problem behoben und schließlich können Sie den Cursor mithilfe von Bereich und Auswahl am Ende platzieren. Hat für mich gearbeitet.

  moveCursorToEnd(e : any) {
    let placeholderText = e.target.innerText;
    e.target.innerText = '';
    e.target.innerText = placeholderText;

    if(e.target.innerText && document.createRange)
    {
      let range = document.createRange();
      let selection = window.getSelection();
      range.selectNodeContents(e.target);
      range.setStart(e.target.firstChild,e.target.innerText.length);
      range.setEnd(e.target.firstChild,e.target.innerText.length);
      selection.removeAllRanges();
      selection.addRange(range);
    }
  }

Im HTML-Code:

<div contentEditable="true" (focus)="moveCursorToEnd($event)"></div>
Nida
quelle