Jak przenieść kursor na koniec treści podlegającej edycji

86

Muszę przenieść daszek na koniec contenteditablewęzła, jak w widżecie notatek Gmaila.

Czytam wątki na StackOverflow, ale te rozwiązania są oparte na wykorzystaniu danych wejściowych i nie działają z contenteditableelementami.

avsej
źródło

Odpowiedzi:

28

Jest też inny problem.

Rozwiązanie Nico Burnsa działa, jeśli contenteditablediv nie zawiera innych elementów wielowierszowych.

Na przykład, jeśli element DIV zawiera inne elementy DIV, a te inne elementy DIV zawierają inne elementy, mogą wystąpić pewne problemy.

Aby je rozwiązać, zaaranżowałem następujące rozwiązanie, czyli ulepszenie rozwiązania 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: /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 || {}));

Stosowanie:

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

W ten sposób kursor jest z pewnością umieszczony na końcu ostatniego elementu, ostatecznie zagnieżdżony.

EDYCJA NR 1 : Aby była bardziej ogólna, instrukcja while powinna uwzględniać również wszystkie inne znaczniki, które nie mogą zawierać tekstu. Te elementy są nazywane void elements , aw tym pytaniu istnieje kilka metod sprawdzania, czy element jest pusty. Tak więc, zakładając, że istnieje funkcja o nazwie, canContainTextktóra zwraca, truejeśli argument nie jest pustym elementem, następujący wiersz kodu:

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

należy zastąpić:

canContainText(getLastChildElement(contentEditableElement))

EDYCJA # 2 : Powyższy kod jest w pełni zaktualizowany, ze wszystkimi zmianami opisanymi i omówionymi

Vito Gentile
źródło
Co ciekawe, spodziewałbym się, że przeglądarka zajmie się tym przypadkiem automatycznie (nie dziwię się, że tak nie jest, przeglądarki nigdy nie wydają się robić intuicyjnych rzeczy z możliwością edycji treści). Czy masz przykład HTML, w którym twoje rozwiązanie działa, a moje nie?
Nico Burns
W moim kodzie był jeszcze jeden błąd. Naprawiłem to. Teraz możesz sprawdzić, czy mój kod działa na tej stronie , a twój nie
Vito Gentile
Uncaught TypeError: Cannot read property 'nodeType' of nullPodczas korzystania z funkcji pojawia się błąd, mówi konsola, a to z wywoływanej funkcji getLastChildElement. Czy wiesz, co może być przyczyną tego problemu?
Derek
@VitoGentile to trochę stara odpowiedź, ale chcę zauważyć, że twoje rozwiązanie zajmuje się tylko elementami blokowymi, jeśli wewnątrz znajdują się elementy liniowe, kursor zostanie umieszczony za tym elementem wbudowanym (jak span, em ...) , prostym rozwiązaniem jest potraktowanie elementów wbudowanych jako znaczników void i dodanie ich do voidNodeTags, aby były pomijane.
medBouzid
242

Rozwiązanie Geowa4 będzie działać dla obszaru tekstowego, ale nie dla elementu, który można edytować.

To rozwiązanie służy do przenoszenia karetki na koniec elementu, który można edytować. Powinien działać we wszystkich przeglądarkach obsługujących edytowalne treści.

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

Może być używany przez kod podobny do:

elem = document.getElementById('txt1');//This is the element that you want to move the caret to the end of
setEndOfContenteditable(elem);
Nico Burns
źródło
1
Rozwiązanie geowa4 będzie działać na obszarach tekstowych w chrome, nie będzie działać dla elementów edytowalnych w żadnej przeglądarce. mój działa dla elementów, które można edytować, ale nie dla obszarów tekstowych.
Nico Burns
4
To poprawna odpowiedź na to pytanie, doskonale, dzięki Nico.
Rob
7
selectNodeContentsCzęść Nico był dając mi błędy zarówno w Chrome i FF (nie testowałem innych przeglądarek), dopóki nie okazało się, że widocznie potrzebna, aby dodać .get(0)do elementu że karmiła funkcję. Wydaje mi się, że ma to związek z używaniem jQuery zamiast czystego JS? Dowiedziałem się tego od @jwarzech w pytaniu 4233265 . Dziękuje za wszystko!
Max Starkenburg
5
Tak, funkcja oczekuje elementu DOM, a nie obiektu jQuery. .get(0)pobiera element dom, który jQuery przechowuje wewnętrznie. Możesz również dołączyć [0], co jest równoważne .get(0)w tym kontekście.
Nico Burns
1
@Nico Burns: Wypróbowałem twoją metodę i nie zadziałała w FireFox.
Lewis
26

Jeśli nie przejmujesz się starszymi przeglądarkami, ta mi załatwiła sprawę.

// [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
źródło
to jedyna rzecz, która działała dla mnie w skrypcie działającym w tle dla rozszerzenia do Chrome
Rob
1
To działa dobrze. Testowano na Chrome 71.0.3578.98 i WebView na Androidzie 5.1.
maswerdna
3
document.execCommandjest teraz przestarzały developer.mozilla.org/en-US/docs/Web/API/Document/execCommand .
programista internetowy
2020 i nadal działa w chrome wersja 83.0.4103.116 (oficjalna kompilacja) (64-bitowa)
user2677034
to jest zwycięzca!
MNN TNK
8

Możliwe jest ustawienie kursora na koniec zakresu:

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
źródło
Użycie powyższego kodu załatwia sprawę - ale chcę mieć możliwość przesunięcia kursora w dowolne miejsce w edytowalnym div z treścią i kontynuowania pisania od tego miejsca - np. Użytkownik rozpoznał literówkę ... Jak Poprawiam twój kod powyżej do tego?
Zabs
1
@Zabs to całkiem proste: nie wywołuj za setCaretToEnd()każdym razem - wywołuj go tylko wtedy, gdy tego potrzebujesz: np. Po kopiowaniu-wklejaniu lub po ograniczeniu długości wiadomości.
am0wa,
To zadziałało dla mnie. po wybraniu przez użytkownika znacznika przesuwam kursor w edytowalnym elemencie div na koniec.
Ayudh
0

Miałem podobny problem, próbując udostępnić element do edycji. Było to możliwe w Chrome i FireFox, ale w FireFox karetka albo poszła na początek wejścia, albo przesunęła się o jedną spację po końcu wejścia. Myślę, że to bardzo zagmatwane dla użytkownika końcowego, próbującego edytować zawartość.

Nie znalazłem rozwiązania próbując kilku rzeczy. Jedyną rzeczą, która zadziałała w moim przypadku, było „obejście problemu” poprzez umieszczenie zwykłego starego pola tekstowego WEWNĄTRZ pliku. Teraz działa. Wygląda na to, że „edytowalna treść” to wciąż najnowocześniejsza technologia, która może, ale nie musi, działać tak, jak chcesz, w zależności od kontekstu.

Panu Logic
źródło
0

Przesunięcie kursora na koniec edytowalnego zakresu w odpowiedzi na zdarzenie fokusa:

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

I wywołując to w module obsługi zdarzeń (zareaguj tutaj):

onFocus={(e) => this.moveCursorToEnd(e.target)}} 
Maxim Saplin
źródło
0

Problem z contenteditable <div>i <span>zostanie rozwiązany, gdy zaczniesz pisać na początku. Jednym z obejść tego problemu może być wywołanie zdarzenia focus na elemencie div i tej funkcji, wyczyszczenie i uzupełnienie tego, co już było w elemencie div. W ten sposób problem został rozwiązany i na koniec możesz umieścić kursor na końcu, używając zakresu i wyboru. Pracował dla mnie.

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

W kodzie HTML:

<div contentEditable="true" (focus)="moveCursorToEnd($event)"></div>
Nida
źródło