var: Position von Textcursor ermitteln und setzen

Hallo miteinander!

Folgendes Problem:

Ich habe ein div-Element mit contentEditable = true und eine onkeyup-Funktion, mit der ich bestimme Wörter wenn sie geschrieben werden automatisch farbig hervorhebe, indem ich sie in ein span-Element einwickle.

Wenn man aber das innerHTML des div-Elements verändert, springt der Textcursor immer automatisch an den Anfang, was selbstverständlich unerwünscht ist.

Was ich also bräuchte wäre eine Methode, mit der ich

a) vor der Manipulation des HTML-Inhalts meines div-Elements die Cursorposition ermitteln und    in einer Variable abspeichern kann, um dann

b) nach der Veränderung des Inhalts den Cursor wieder an die richtige, sprich ursprüngliche    Stelle zu bugsieren,

damit man ungestört weiterschreiben kann.

Habe mir zu dem Thema schon ein paar Sachen durchgelesen - Stichworte: Range, Selection - ohne aber wirklich daraus schlau zu werden.

Falls also jemand ein praktikables Beispiel posten könnte oder einen Link parat hätte, wo das Thema etwas anschaulicher und verständlicher dargestellt wird, wäre ich wirklich sehr dankbar!

Beste Grüße,

var

  1. @@var:

    nuqneH

    Ich habe ein div-Element mit contentEditable = true und eine onkeyup-Funktion, mit der ich bestimme Wörter wenn sie geschrieben werden automatisch farbig hervorhebe, indem ich sie in ein span-Element einwickle.

    Das geht doch mit Ligaturen. SCNR.

    Qapla'

    --
    „Talente finden Lösungen, Genies entdecken Probleme.“ (Hans Krailsheimer)
    1. Hallo Gunnar

      Das geht doch mit Ligaturen. SCNR.

      Nicht wirklich das, was ich gesucht habe, aber trotzdem interessant. ;-)

      Wäre wohl klüger gewesen, das im Eingangspost schon zu erwähnen, aber bei der Sache geht es mir weniger darum, ein bestimmtes Endergebnis zu erzielen, sondern vielmehr möchte ich verstehen, wie der Vorgang selbst funktioniert. - Und dazu habe ich leider bislang kein wirklich informatives Material gefunden...

      Dank und Gruß,

      var

  2. Liebe(r) var,

    Ich habe ein div-Element mit contentEditable = true

    das ist eine höchst komplexe Angelegenheit mit dermaßen vielen Fallstricken, dass ich davon die Finger lasse!

    Meiner Erfahrung nach ist es wesentlich zuverlässiger, auf fertige Editor-Komponenten zurückzugreifen.

    bestimme Wörter wenn sie geschrieben werden automatisch farbig hervorhebe,

    So irre es sich vielleicht im ersten Moment anhört, ist es aber vielleicht vom Aufwand und der Zuverlässigkeit her schlauer, einen der vorhandenen Code-Editoren mit einer "neuen Sprache" auszurüsten, damit sein Syntax-Highlighting den von Dir beabsichtigten Effekt auslöst. Die von Dir geschilderten Probleme haben diese nämlich längst gelöst (und noch andere!), so dass Du das Rad nicht neu erfinden musst.

    Liebe Grüße,

    Felix Riesterer.

    --
    "Wäre die EU ein Staat, der die Aufnahme in die EU beantragen würde, müsste der Antrag zurückgewiesen werden - aus Mangel an demokratischer Substanz." (Martin Schulz, Präsident des EU-Parlamentes)
    1. Hallo Felix

      das ist eine höchst komplexe Angelegenheit mit dermaßen vielen Fallstricken, dass ich davon die Finger lasse!

      Das es eine komplexe Angelegenheit ist, ist mir schon aufgefallen. ;-)

      Meiner Erfahrung nach ist es wesentlich zuverlässiger, auf fertige Editor-Komponenten zurückzugreifen.

      So irre es sich vielleicht im ersten Moment anhört, ist es aber vielleicht vom Aufwand und der Zuverlässigkeit her schlauer, einen der vorhandenen Code-Editoren mit einer "neuen Sprache" auszurüsten, damit sein Syntax-Highlighting den von Dir beabsichtigten Effekt auslöst. Die von Dir geschilderten Probleme haben diese nämlich längst gelöst (und noch andere!), so dass Du das Rad nicht neu erfinden musst.

      Tatsächlich ist genau die Erstellung eines solchen Editors der Sinn der Übung, wobei aber nicht so sehr das Endergebnis zählt, sondern vielmehr der Lerneffekt:

      Ich möchte verstehen, wie das funktioniert! ;-)

      Ich hatte gehofft, irgendjemand hätte vielleicht mal einen Artikel verfasst, in dem das Thema Range/Selection etwas ausführlicher erklärt ist, und der mir bei meiner Recherche schlicht entgangen ist...

      Gruß

      var

  3. Hakuna matata!

    Wenn man aber das innerHTML des div-Elements verändert, springt der Textcursor immer automatisch an den Anfang, was selbstverständlich unerwünscht ist.

    innerHTML ist dein eigentliches Problem. Mit innerHTML ersetzt du nämlich einen Teilbaum durch einen komplett neuen Teilbaum. Selbst in dem einfachen Fall, dass die beiden Bäume exakt die selbe Struktur haben, sind das zwei völlig verschiedene Bäume. DOMKnoten haben diverse Zustände, dazu zählen zum Beispiel registrierte EventHandler, MicroData und ganz besonders wichtig eine sogenannte Identität (damit meine ich nicht das id-Attribut, sondern die Objekt-Identität). Mit innerHTML gehen all diese Zustände des alten Baums verloren, ein Beispiel:

    <div id="outer"><div id="inner"></div></div>

    
    var inner1 = document.getElementById('outer');
    var outer = document.getElementById('inner');
    
    outer.innerHTML = '<div id="inner"></div>';
    // Augenscheinlich haben wir dadurch nichts verändert, aber der Schein trügt
    
    var inner2 = document.getElementById('inner');
    
    console.assert( inner1 === inner2 );
    // Error: Assertion failed
    
    

    Die Selection-API arbeitet aber mit genau diesen Objekt-Identitäten, selbst wenn du in der Lage wärst, die Cursor-Position vor der Ersetzung mit innerHTML auszulesen, dann könntest du sie nach Ersetzung niemals wieder dort einfügen, wo sie intuitiv betrachtet hingehörte, weil der neue Baum ganz neue Objekt-Identitäten hat.

    Du willst innerHTML deshalb unbedingt vermeiden und stattdessen ein inkrementelles Update auf dem DOMBaum ausführen, sodass nur die Bereiche ausgetauscht werden, die wirklich von Änderung betroffen sind. Wenn sich nur ein Attribut ändert, dann muss man nicht gleich das ganze Element austauschen, man muss nichtmal den Attribut-Knoten austauschen, es reicht dem alten Attribut einen neuen Wert zu verpassen. Wenn dir das gelingt, dann hat sich dein Problem mit dem Cursor übrigens von selbst erledigt.

    --
    “All right, then, I'll go to hell.” – Huck Finn
    1. Hakuna matata!

      Hallo 1UnitedPower!

      innerHTML ist dein eigentliches Problem. Mit innerHTML ersetzt du nämlich einen Teilbaum durch einen komplett neuen Teilbaum. Selbst in dem einfachen Fall, dass die beiden Bäume exakt die selbe Struktur haben, sind das zwei völlig verschiedene Bäume. DOMKnoten haben diverse Zustände, dazu zählen zum Beispiel registrierte EventHandler, MicroData und ganz besonders wichtig eine sogenannte Identität (damit meine ich nicht das id-Attribut, sondern die Objekt-Identität). Mit innerHTML gehen all diese Zustände des alten Baums verloren[...]

      Das ist insoweit klar: Mit innerHTML wird der gesamte vorherige Inhalt überschrieben.

      Die Selection-API arbeitet aber mit genau diesen Objekt-Identitäten, selbst wenn du in der Lage wärst, die Cursor-Position vor der Ersetzung mit innerHTML auszulesen, dann könntest du sie nach Ersetzung niemals wieder dort einfügen, wo sie intuitiv betrachtet hingehörte, weil der neue Baum ganz neue Objekt-Identitäten hat.

      Darum die Verbindung mit dem Range-Objekt. Soweit ich das verstanden habe, wird die mittels Selection ermittelte Cursorposition nicht als absolute Positionsangabe verwendet, da dies aus den von dir beschriebenen Gründen sinnlos wäre, sondern man definiert vorher mittels range quasi die Abmessungen des Elementinhalts und speichert dann die Position relativ zu diesen.

      Und das scheint auch zu funktionieren (!), aber ich habe halt leider noch nicht genau verstanden wie...

      Du willst innerHTML deshalb unbedingt vermeiden und stattdessen ein inkrementelles Update auf dem DOMBaum ausführen, sodass nur die Bereiche ausgetauscht werden, die wirklich von Änderung betroffen sind. Wenn sich nur ein Attribut ändert, dann muss man nicht gleich das ganze Element austauschen, man muss nichtmal den Attribut-Knoten austauschen, es reicht dem alten Attribut einen neuen Wert zu verpassen. Wenn dir das gelingt, dann hat sich dein Problem mit dem Cursor übrigens von selbst erledigt.

      Darüber muss ich mal nachdenken.

      Es sollte ohnehin das Ziel sein, den Text in Tokens zu zerlegen, mittels derer ich eine Repräsentation/Struktur des Textes erstelle, damit ich nicht bei jedem keyup-Event das komplette innerHTML des div-Elementes neu konstruieren muss, wenn ich an einer Stelle ein span-Element hinzufüge oder wegnehme.

      Jedenfalls glaube ich, dass mein kleines Projekt ohne grundlegendes Verständnis der Themen Range/Selection dennoch kaum zu verwirklichen sein wird...

      Hm.

      Gruß

      var

  4. @@var:

    nuqneH

    Ich habe ein div-Element mit contentEditable = true und eine onkeyup-Funktion, mit der ich bestimme Wörter wenn sie geschrieben werden automatisch farbig hervorhebe, indem ich sie in ein span-Element einwickle.

    Das mark-Element scheint mir passend.

    Qapla'

    --
    „Talente finden Lösungen, Genies entdecken Probleme.“ (Hans Krailsheimer)
    1. Lieber Gunnar Bittersmann,

      mark-Element

      gibt's auch hier im Wiki: http://wiki.selfhtml.org/wiki/HTML/Textauszeichnung/mark

      Liebe Grüße,

      Felix Riesterer.

      --
      "Wäre die EU ein Staat, der die Aufnahme in die EU beantragen würde, müsste der Antrag zurückgewiesen werden - aus Mangel an demokratischer Substanz." (Martin Schulz, Präsident des EU-Parlamentes)
  5. Hallo miteinander!

    Also, bevor der Thread Staub ansetzt, hier mal meine vorläufigen Erkenntnisse:

    Es ist zwar mittels Range und Selection tatsächlich möglich, die Cursorposition nach Änderungen am innerHTML des div-Elements wiederherzustellen, aber für den von mir verfolgten Zweck ist das, so wie ich das sehe, nicht die beste Lösung.

    Denn nehmen wir mal an, wir ersetzen während des Schreibens den Teil eines Textknotens durch einen Elementknoten wie etwa ein span- oder mark-Element, sobald der letzte Buchstabe des dadurch optisch hervorzuhebenden Wortes geschrieben wurde.

    Würden wir nun nach dem Einfügen des span- oder mark-Elements die Cursorposition wiederherstellen, dann hätte das - soweit ich das beurteilen kann - den Effekt, dass wir eben genau an der Stelle weiterschreiben wo wir aufgehört haben, sprich innerhalb des eingefügten Elements, mit der Folge, dass der weitere Text ebenfalls eingefärbt wäre.

    Es erscheint mir also wesentlich sinnvoller, beim Setzen des Cursors nicht pauschal die alte Position wiederherzustellen, sondern den Cursor statt dessen ausdrücklich nach dem neuen Elementknoten einzufügen, sobald die gesuchte Zeichenkette komplett ist.

    Optisch betrachtet ist das zwar die selbe Cursorposition, aber eben auf einer ganz anderen Grundlage, - welche zudem weitaus weniger aufwändig ist! Um den Cursor nach dem span- oder mark-Element einzufügen braucht man nämlich nur die folgende einfache Funktion aufzurufen:

    function setCursorPosition (element) {
    
      var range = document.createRange( );
      range.setStartAfter(element);
      range.collapse(true);
    
      var selection = window.getSelection( );
      selection.removeAllRanges( );
      selection.addRange(range);
    
    }
    

    Damit verlagert sich die Problematik auf die etwas leichter zu handhabende Frage, wie man das eingefügte Element identifiziert, damit es der Funktion als Parameter übergeben werden kann.

    In bisherigen Tests hat das damit jedenfalls perfekt geklappt: Der Schreibfluss wird nicht unterbrochen, der Cursor ist immer genau da wo er sein soll, und es werden nur die Ausdrücke eingefärbt, die auch tatsächlich hervorgehoben werden sollen.

    Als Arbeitsgrundlage sollte das also vorerst genügen. ;-)

    Nochmals Dank und Gruß an alle!

    var