2015-04-22 5 views
6

Przeczytałem to excellent answer prawie na to samo pytanie. Jednak wypróbowałem każdą technikę zalecaną przez użytkownika i żaden z nich nie działa.CKEDITOR - nie można zmienić położenia kursora po modyfikacji DOM

Sytuacja polega na tym, że zabieram obecny kod HTML z edytora i zawijam pewne elementy w tagach z rozpiętością. Następnie ustawiłem zmodyfikowany HTML i spróbuję przywrócić położenie kursora użytkownika. Żadna technika nie działa.

Tutaj jest bardzo prosty przykład, aby odtworzyć problem:

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
    <title></title> 
    <script src="//cdn.ckeditor.com/4.4.7/standard/ckeditor.js"></script> 

</head> 
<body> 
    <textarea id="cktest"><p>Sometimes Lorem. Sometime Ipsum. Always dolor.</p></textarea> 

    <script type="text/javascript"> 

     (function() { 
      var checkTimeout; 
      var bookmark; 

      var storeCursorLocation = function(editor) { 
       bookmark = editor.getSelection().createBookmarks(); 
      }; 

      var restoreCursorLocation = function(editor) { 
       editor.getSelection().selectBookmarks(bookmark); 
      }; 

      var validateText = function(editor) { 
       storeCursorLocation(editor); 
       var data = editor.document.getBody().getHtml(); 
       data = data.replace("Lorem", "<span class='err-item'>Lorem</span>"); 
       editor.document.getBody().setHtml(data); 
       restoreCursorLocation(editor); 
      }; 


      CKEDITOR.replace('cktest', { 
       on: { 
        'instanceReady': function(evt) { 

        }, 
        'key' : function(evt) { 
         clearTimeout(checkTimeout); 
         checkTimeout = setTimeout(function() { 
          validateText(evt.editor); 
         }, 1000); 
        } 
       } 
      }); 
     })(); 

    </script> 
</body> 
</html> 

Kod ten uruchamia licznik czasu, gdy użytkownik naciśnie klawisz, a następnie czeka na 1 sekundę po zaprzestaniu naciśnięcie klawiszy zrobić czek.

Skopiuj ten plik do nowego pliku .html i uruchom go w ulubionej przeglądarce (używam przeglądarki Chrome).

Kiedy ładuje się CKEditor, użyj myszki, aby umieścić kursor w środku tekstu. Następnie naciśnij klawisz CTRL i odczekaj 1 sekundę. Zobaczysz, że kursor przeskoczył z powrotem na początek tekstu.

Ten przykładowy kod wykorzystuje

editor.getSelection().createBookmarks(); 

aby utworzyć zakładkę. Ale próbowałem też:

editor.getSelection().createBookmarks(true); 

i

editor.getSelection().createBookmarks2(); 

Próbowałem również tylko oszczędność zakres używając

var ranges = editor.getSelection().getRanges(); 

i

editor.getSelection().selectRanges(ranges); 

w funkcji restoreCursorLocation.

Odpowiedz

5
 (function() { 
      var checkTimeout; 
      var bookmark; 

      var storeCursorLocation = function(editor) { 
       bookmark = editor.getSelection().createBookmarks(true); 
      }; 

      var restoreCursorLocation = function(editor) { 
       //editor.focus(); 
       editor.getSelection().selectBookmarks(bookmark); 
      }; 

      var validateText = function(editor) { 
       storeCursorLocation(editor); 

       var data = editor.document.getBody().getHtml(); 
       data = data.replace("spaceflight", "<span class='err-item'>spaceflight</span>"); 
       editor.document.getBody().setHtml(data); 
       restoreCursorLocation(editor); 
       //fire this event after DOM changes if working with widgets 
       //editor.fire('contentDomInvalidated'); 
      }; 


      var editor = CKEDITOR.replace('editor1', { 
       extraAllowedContent : 'span(err-item)',    
       on: { 
        "pluginsLoaded" : function(event){ 
         editor.on('contentDom', function() { 
          var editable = editor.editable();     
          editable.attachListener(editable, 'keyup', function(e) { 
           clearTimeout(checkTimeout); 
           checkTimeout = setTimeout(function() { 
            validateText(editor); 
           }, 100); 
          }); 
         }); 
        } 
       } 
      }); 
     })(); 

Sprawdziłem twój kod, wprowadziłem pewne poprawki i powyższe wydaje się działać dobrze. Wiem, że powiedziałeś, że próbowałeś, ale dla mnie createBookmarks(true) zrobił lewy.

Wyjaśnienia i uwagi:

  1. Trzeba było użyć createBookmarks(true) który wstawia unikalne rozpiętości w HTML. Na taką zakładkę nie mają wpływu zmiany, które wprowadzasz w DOM (są oczywiście ograniczenia, np. Twoje zmiany niestandardowe usuwają zakładkę).
  2. Było sprytnie używać getBody().getHtml() i getBody().setHTML(). Jeśli używałeś editor.getData(), to usunąłoby to puste przęsła reprezentujące zakładki. Należy jednak pamiętać, że takie podejście może uszkodzić widżety, dlatego po takich zmianach wymagane jest uruchomienie zdarzenia contentDomInvalidated.
  3. Ja również skupiałem się na edytorze przed przywróceniem wyboru, ale jest to rozwiązanie "na wszelki wypadek", ponieważ zauważyłem, że edytor wybiera zakładkę bez niego. Jeśli jednak z jakiegoś powodu przegrywasz selekcję, jest to kolejna rzecz do wykorzystania.

przykład Tu masz pracy: http://jsfiddle.net/j_swiderski/nwbsywnn/1/

+0

Wystarczy dodać do tej odpowiedzi - o wiele bezpieczniejszym rozwiązaniem byłoby użycie ['CKEDITOR.style'] (http://docs.ckeditor.com/#!/api/CKEDITOR.style), ponieważ dotknie tylko tych części drzewa, które muszą zostać zmienione. Jednak aby go użyć, musisz zaimplementować funkcję szukającą tekstu w DOM, co jest dość skomplikowaną rzeczą. – Reinmar

+0

Dzięki za doskonałą odpowiedź, Reinmar. W prawdziwym kodzie używam CKEDITOR.htmlParser, aby przejść przez dokument i dokonać modyfikacji. Nie tylko proste .replace(). Przyjrzę się używaniu stylów i zobaczę, czy to działa lepiej. Nie wiem, dlaczego createBookmark (true) nie działał wcześniej. Ale teraz jest. –

1

Sprawdź domyślne zachowanie podczas ustawiania InnerHTML w https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML

Usuwa wszystkie dzieci elementu, przetwarza ciąg treści i przypisuje wynikające węzły jako dzieci elementu

zakładek w CKEditor są ukryte elementy span i ustawienie innerHtml spowoduje usunięcie wszystkich tych elementów.

W każdym razie rozwiązanie jest bardzo proste.

Zmień swoją funkcję storeCursorLocation do tego

var storeCursorLocation = function(editor) { 
    bookmark = editor.getSelection().createBookmarks(true); 
}; 

Po przejechaniu prawda jako parametry użyje identyfikatory jako odniesienie zamiast przechowywania elementów DOM tak można przywrócić następnie po zmianie InnerHTML.

{Zmień}

Reading Rozwiązanie 2 z @Reinmar mówi

Jeśli można uniknąć niekontrolowanych zmian InnerHTML i zamiast dodać/usunąć/przenieść niektóre węzły, a potem po prostu pamiętać, że muszą zachować te elementy, a ta metoda zadziała doskonale. Możesz także przenosić elementy zakładek, jeśli twoje modyfikacje powinny również zmienić wybór.

Tak to zrobisz, jeśli nie możesz zastąpić zawartości innerHtml elementu.

To rozwiązanie jest mniej skuteczne, ale może działać w niektórych scenariuszach

zmienić funkcję validateText do tego.

var validateText = function(editor) { 
    storeCursorLocation(editor); 
    var parent = editor.document.getBody().$.firstChild, 
     nodes = parent.childNodes, 
     nodeText, 
     words, 
     index = 0, 
     current, 
     newElement; 
    while (index < nodes.length) { 
     current = nodes[index]; 
     nodeText = current.nodeValue; 
     if (current.nodeType === Node.TEXT_NODE && nodeText.indexOf('Lorem') !== -1) { 
      words = nodeText.split('Lorem'); 
      newElement = document.createTextNode(words[0]); 
      parent.insertBefore(newElement, current); 
      newElement = document.createTextNode(words[1]); 
      parent.insertBefore(newElement, current.nextSibling); 
      newElement = document.createElement('span') 
      newElement.className = 'err-item'; 
      newElement.innerHTML = 'Lorem'; 
      parent.replaceChild(newElement, current); 
      break; 
     } 
     index++; 

    } 
    restoreCursorLocation(editor); 
}; 

Zasadniczo m transversing węzłów pierwszego p w organizmie chkeditor i wymianę tylko węzeł tekstu typu, który zawiera lorem o rozpiętości i dodać pozostałą tekstu przed i po jako elementy tekstowe. Jeśli zastąpisz cały tekst tak, jak robiłeś, usuniesz z DOM zakładki, więc gdy spróbujesz je przywrócić, nie istnieją.

+0

w rzeczywistym kodzie używam CKEDITOR.htmlParser zrobić coś podobnego do drugiego rozwiązania. W rzeczy samej, jest to lepsze niż użycie .replace(). Ale .replace() był o wiele mniej skomplikowany na przykład, a sposób zastąpienia nie był problemem. –

+1

Dzięki pierwszemu rozwiązaniu możesz użyć zamiany. Zakładki są zapisywane za pomocą identyfikatorów i przywracane. Dodałem drugie rozwiązanie, ponieważ istnieją pewne scenariusze, w których użycie zamiany jest trudniejsze niż zastąpienie pojedynczego elementu tekstowego. Jest to przykład, jak powiedziałeś, a ja osobiście preferuję przekazywanie większej ilości danych, które mogą być bardziej pomocne w przypadku rzeczywistego problemu. – devconcept