satysfakcjonujące zdarzenia zmian

359

Chcę uruchomić funkcję, gdy użytkownik edytuje zawartość atrybutu divwith contenteditable. Jaki jest odpowiednik onchangewydarzenia?

Korzystam z jQuery, więc preferowane są wszelkie rozwiązania korzystające z jQuery. Dzięki!

Jourkey
źródło
I zazwyczaj to zrobić: document.getElementById("editor").oninput = function() { ...}.
Basj

Odpowiedzi:

311

Sugeruję dołączanie słuchaczy do kluczowych zdarzeń uruchamianych przez element edytowalny, choć musisz pamiętać, że keydowni keypresszdarzenia są uruchamiane przed zmianą samej treści. Nie obejmie to wszystkich możliwych sposobów zmiany treści: użytkownik może również używać wycinania, kopiowania i wklejania z menu Edycja lub menu przeglądarki kontekstowej, więc możesz również obsłużyć zdarzenia cut copyi paste. Ponadto użytkownik może upuścić tekst lub inną treść, więc jest tam więcej zdarzeń ( mouseupna przykład). Możesz sondować zawartość elementu jako awaryjne.

AKTUALIZACJA 29 października 2014 r

HTML5 inputzdarzenie jest odpowiedzią na dłuższą metę. W chwili pisania tego tekstu jest on obsługiwany w contenteditableelementach obecnych przeglądarek Mozilla (od Firefox 14) i WebKit / Blink, ale nie w IE.

Próbny:

document.getElementById("editor").addEventListener("input", function() {
    console.log("input event fired");
}, false);
<div contenteditable="true" id="editor">Please type something in here</div>

Demo: http://jsfiddle.net/ch6yn/2691/

Tim Down
źródło
11
Należy również dodać, że jest to możliwe, aby zmienić edycji elementu za pomocą menu Edytuj w przeglądarce lub menu kontekstowego, aby wyciąć, skopiować lub wkleić zawartość, więc musisz uchwyt cut, copya pastewydarzenia w przeglądarkach, które je obsługują (IE 5+, Firefox 3.0+, Safari 3+, Chrome)
Tim Down
4
Możesz użyć klucza i nie będziesz mieć problemów z synchronizacją.
Blowsie
2
@Blowsie: Tak, ale potencjalnie może się zdarzyć, że wiele zmian nastąpi po automatycznym powtarzaniu naciśnięcia klawisza przed uruchomieniem zdarzenia. Istnieją również inne sposoby zmiany edytowalnego tekstu, których nie uwzględnia ta odpowiedź, takie jak przeciąganie i upuszczanie oraz edycja za pomocą menu Edycja i menu kontekstowe. W dłuższej perspektywie wszystko to powinno być objęte inputzdarzeniem HTML5 .
Tim Down
1
Robię keyup z debounce.
Aen Tan
1
@AndroidDev I testet Chrome, a zdarzenie wejściowe jest również uruchamiane podczas kopiowania wklejania i wycinania zgodnie z wymaganiami specyfikacji: w3c.github.io/uievents/#events-inputevents
fishbone
188

Oto bardziej wydajna wersja ondla wszystkich edytowanych treści. Opiera się na najlepszych odpowiedziach tutaj.

$('body').on('focus', '[contenteditable]', function() {
    const $this = $(this);
    $this.data('before', $this.html());
}).on('blur keyup paste input', '[contenteditable]', function() {
    const $this = $(this);
    if ($this.data('before') !== $this.html()) {
        $this.data('before', $this.html());
        $this.trigger('change');
    }
});

Projekt jest tutaj: https://github.com/balupton/html5edit

balupton
źródło
Działa idealnie dla mnie, dzięki za CoffeeScript i JavaScript
jwilcox09
7
Istnieją pewne zmiany, które nie zostaną przechwycone przez ten kod, dopóki zawartość nie zostanie zamazana. Na przykład: przeciąganie i upuszczanie, wycinanie z menu kontekstowego i wklejanie z okna przeglądarki. Już tylko konfiguracja przykładowa strona, która używa tego kodu, które można komunikować się z tutaj .
jrullmann
@jrullmann Tak Miałem problemy z tym kodem podczas przeciągania i upuszczania obrazów, ponieważ nie nasłuchuje on ładowania obrazu (w moim przypadku był to problem z operacją zmiany rozmiaru)
Sebastien Lorber
@jrullmann Bardzo ładny, to działa nawet wtedy zmiana wartości z javascript, spróbuj w konsoli: document.getElementById('editor_input').innerHTML = 'ssss'. Wadą jest to, że wymaga IE11
1
@NadavB nie powinno, bo to jest to, co „jeśli ($ this.data ('before')! == $ this.html ()) {” jest dla
balupton
52

Rozważ użycie MutationObserver . Ci obserwatorzy mają za zadanie reagować na zmiany w DOM i zastępować zdarzenia mutacji .

Plusy:

  • Uaktywnia się, gdy nastąpi jakakolwiek zmiana, co jest trudne do osiągnięcia przez słuchanie kluczowych wydarzeń, jak sugerują inne odpowiedzi. Na przykład wszystkie z nich działają dobrze: przeciągnij i upuść, kursywa, kopiuj / wytnij / wklej przez menu kontekstowe.
  • Zaprojektowany z myślą o wydajności.
  • Prosty, prosty kod. Znacznie łatwiej jest zrozumieć i debugować kod, który nasłuchuje jednego zdarzenia, niż kod, który nasłuchuje 10 zdarzeń.
  • Google ma doskonałą bibliotekę podsumowań mutacji, która bardzo ułatwia korzystanie z MutationObservers.

Cons:

  • Wymaga najnowszej wersji przeglądarki Firefox (14.0+), Chrome (18+) lub IE (11+).
  • Nowy interfejs API do zrozumienia
  • Niewiele jeszcze informacji na temat najlepszych praktyk lub studiów przypadków

Ucz się więcej:

  • Napisałem mały fragment do porównania przy użyciu MutationObserers do obsługi różnych zdarzeń. Użyłem kodu Baluptona, ponieważ jego odpowiedź ma najwięcej pozytywnych opinii.
  • Mozilla ma doskonałą stronę API
  • Spójrz na bibliotekę MutationSummary
jrullmann
źródło
Nie jest to dokładnie to samo, co inputzdarzenie HTML5 (które jest obsługiwane contenteditablewe wszystkich przeglądarkach WebKit i Mozilla, które obsługują obserwacje mutacji), ponieważ mutacja DOM może wystąpić zarówno za pomocą skryptu, jak i danych wejściowych użytkownika, ale jest realnym rozwiązaniem dla tych przeglądarek. Wyobrażam sobie, że może to bardziej zaszkodzić wydajności niż samo inputwydarzenie, ale nie mam na to twardych dowodów.
Tim Down
2
+1, ale czy zdajesz sobie sprawę, że zdarzenia mutacji nie zgłaszają efektów linijki liniowej w zadowalającym? Naciśnij enter we fragmencie.
citykid
4
@citykid: To dlatego, że fragment obserwuje tylko zmiany danych postaci. Można również obserwować zmiany strukturalne DOM. Zobacz na przykład jsfiddle.net/4n2Gz/1 .
Tim Down
Internet Explorer 11+ obsługuje obserwatory mutacji .
Sampson,
Byłem w stanie zweryfikować (przynajmniej w Chrome 43.0.2357.130), że inputzdarzenie HTML5 jest uruchamiane w każdej z tych sytuacji (w szczególności przeciąganie i upuszczanie, kursywa, kopiowanie / wycinanie / wklejanie za pomocą menu kontekstowego). Przetestowałem również pogrubienie w / cmd / ctrl + b i uzyskałem oczekiwane wyniki. Sprawdziłem również i byłem w stanie sprawdzić, czy inputzdarzenie nie uruchamia się przy zmianach programowych (co wydaje się oczywiste, ale prawdopodobnie jest sprzeczne z jakimś mylącym językiem na odpowiedniej stronie MDN; zobacz dół strony tutaj, aby zobaczyć, co mam na myśli: programista .mozilla.org / en-US / docs / Web / Events / input )
mikermcneil
22

Zmodyfikowałem odpowiedź prawnika w ten sposób i to działa dla mnie. Używam zdarzenia keyup zamiast naciśnięcia klawisza, co działa świetnie.

$('#editor').on('focus', function() {
  before = $(this).html();
}).on('blur keyup paste', function() { 
  if (before != $(this).html()) { $(this).trigger('change'); }
});

$('#editor').on('change', function() {alert('changed')});
Dennkster
źródło
22

szybka i brudna odpowiedź non jQuery :

function setChangeListener (div, listener) {

    div.addEventListener("blur", listener);
    div.addEventListener("keyup", listener);
    div.addEventListener("paste", listener);
    div.addEventListener("copy", listener);
    div.addEventListener("cut", listener);
    div.addEventListener("delete", listener);
    div.addEventListener("mouseup", listener);

}

var div = document.querySelector("someDiv");

setChangeListener(div, function(event){
    console.log(event);
});
Developia
źródło
3
co to za zdarzenie usuwania?
James Cat
7

Dwie opcje:

1) W przypadku nowoczesnych (wiecznie zielonych) przeglądarek: zdarzenie „wejściowe” działałoby jako alternatywne zdarzenie „zmiany”.

https://developer.mozilla.org/en-US/docs/Web/Events/input

document.querySelector('div').addEventListener('input', (e) => {
    // Do something with the "change"-like event
});

lub

<div oninput="someFunc(event)"></div>

lub (z jQuery)

$('div').on('click', function(e) {
    // Do something with the "change"-like event
});

2) Aby uwzględnić IE11 i nowoczesne (wiecznie zielone) przeglądarki: Obserwuje zmiany elementów i ich zawartość w div.

https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

var div = document.querySelector('div');
var divMO = new window.MutationObserver(function(e) {
    // Do something on change
});
divMO.observe(div, { childList: true, subtree: true, characterData: true });
nieco mniej
źródło
7

const p = document.querySelector('p')
const result = document.querySelector('div')
const observer = new MutationObserver((mutationRecords) => {
  result.textContent = mutationRecords[0].target.data
  // result.textContent = p.textContent
})
observer.observe(p, {
  characterData: true,
  subtree: true,
})
<p contenteditable>abc</p>
<div />

superwf
źródło
2
To wygląda interesująco. Czy ktoś może podać jakieś wyjaśnienie? 😇
WoIIe
3

Oto, co zadziałało dla mnie:

   var clicked = {} 
   $("[contenteditable='true']").each(function(){       
        var id = $(this).attr("id");
        $(this).bind('focus', function() {
            // store the original value of element first time it gets focus
            if(!(id in clicked)){
                clicked[id] = $(this).html()
            }
        });
   });

   // then once the user clicks on save
   $("#save").click(function(){
            for(var id in clicked){
                var original = clicked[id];
                var current = $("#"+id).html();
                // check if value changed
                if(original != current) save(id,current);
            }
   });
Gregory Whiteside
źródło
3

Ten wątek był bardzo pomocny podczas badania tematu.

Zmodyfikowałem część kodu dostępnego tutaj w wtyczce jQuery, więc jest w formie wielokrotnego użytku, przede wszystkim w celu zaspokojenia moich potrzeb, ale inni mogą docenić prostszy interfejs do uruchamiania za pomocą tagów, które można zadowolić.

https://gist.github.com/3410122

Aktualizacja:

Ze względu na rosnącą popularność wtyczka została przyjęta przez Makesites.org

Rozwój będzie kontynuowany stąd:

https://github.com/makesites/jquery-contentiable

tracend
źródło
2

Oto rozwiązanie, z którego korzystałem i działa bajecznie. Zamiast tego używam $ (this) .text (), ponieważ po prostu używam div jednowierszowego, który można edytować treści. Ale możesz także użyć .html () w ten sposób, że nie musisz się martwić o zakres globalnej / nieglobalnej zmiennej, a wcześniejsza wersja faktycznie jest dołączona do edytora div.

$('body').delegate('#editor', 'focus', function(){
    $(this).data('before', $(this).html());
});
$('#client_tasks').delegate('.task_text', 'blur', function(){
    if($(this).data('before') != $(this).html()){
        /* do your stuff here - like ajax save */
        alert('I promise, I have changed!');
    }
});
użytkownik740433
źródło
1

Aby uniknąć liczników czasu i przycisków „zapisz”, możesz użyć zdarzenia rozmycia, które uruchamia się, gdy element traci fokus. ale aby mieć pewność, że element został faktycznie zmieniony (nie tylko skupiony i nieostry), jego zawartość należy porównać z jego ostatnią wersją. lub użyj zdarzenia keydown, aby ustawić flagę „brudną” dla tego elementu.

joox
źródło
1

Prosta odpowiedź w JQuery, właśnie stworzyłem ten kod i pomyślałem, że będzie on pomocny także dla innych

    var cont;

    $("div [contenteditable=true]").focus(function() {
        cont=$(this).html();
    });

    $("div [contenteditable=true]").blur(function() {
        if ($(this).html()!=cont) {
           //Here you can write the code to run when the content change
        }           
    });
sarath
źródło
Pamiętaj, że $("div [contenteditable=true]")wybierze wszystkie dzieci div, bezpośrednio lub pośrednio, które są zadowolone.
Bruno Ferreira,
1

Odpowiedź nie JQuery ...

function makeEditable(elem){
    elem.setAttribute('contenteditable', 'true');
    elem.addEventListener('blur', function (evt) {
        elem.removeAttribute('contenteditable');
        elem.removeEventListener('blur', evt.target);
    });
    elem.focus();
}

Aby go użyć, wywołaj (powiedzmy) element nagłówka o id = "myHeader"

makeEditable(document.getElementById('myHeader'))

Ten element będzie teraz edytowalny przez użytkownika, dopóki nie straci ostrości.

DrMcCleod
źródło
5
Ugh, dlaczego głosowanie bez wyjaśnienia? Jest to niekorzystne zarówno dla osoby, która napisała odpowiedź, jak i dla wszystkich, którzy czytają odpowiedź później.
Mike Willis
0

Zdarzenie onchange nie jest uruchamiane po zmianie elementu z atrybutem contentEditable. Sugerowanym podejściem może być dodanie przycisku w celu „zapisania” edycji.

Sprawdź tę wtyczkę, która rozwiązuje problem w ten sposób:

CMS
źródło
dla potomności, dziesięć lat naprzód i nie ma potrzeby przycisku „zapisz”, sprawdź jsfiddle przyjętej odpowiedzi
odrzekł
0

Użycie DOMCharacterDataModified pod MutationEvents doprowadzi do tego samego. Limit czasu jest skonfigurowany, aby zapobiec wysyłaniu niepoprawnych wartości (np. W Chrome miałem problemy z klawiszem spacji)

var timeoutID;
$('[contenteditable]').bind('DOMCharacterDataModified', function() {
    clearTimeout(timeoutID);
    $that = $(this);
    timeoutID = setTimeout(function() {
        $that.trigger('change')
    }, 50)
});
$('[contentEditable]').bind('change', function() {
    console.log($(this).text());
})

Przykład JSFIDDLE

janfabian
źródło
1
+1 - DOMCharacterDataModifiednie będzie uruchamiany, gdy użytkownik zmodyfikuje istniejący tekst, na przykład stosując pogrubienie lub kursywę. DOMSubtreeModifiedjest bardziej odpowiednie w tym przypadku. Ludzie powinni także pamiętać, że starsze przeglądarki nie obsługują tych zdarzeń.
Andy E
3
Tylko uwaga, że ​​zdarzenia mutacji są amortyzowane przez w3c z powodu problemów z wydajnością. Więcej można znaleźć w tym pytaniu dotyczącym przepełnienia stosu .
jrullmann
0

W tym celu zbudowałem wtyczkę jQuery.

(function ($) {
    $.fn.wysiwygEvt = function () {
        return this.each(function () {
            var $this = $(this);
            var htmlold = $this.html();
            $this.bind('blur keyup paste copy cut mouseup', function () {
                var htmlnew = $this.html();
                if (htmlold !== htmlnew) {
                    $this.trigger('change')
                }
            })
        })
    }
})(jQuery);

Możesz po prostu zadzwonić $('.wysiwyg').wysiwygEvt();

Możesz także usunąć / dodać wydarzenia, jeśli chcesz

Blowsie
źródło
2
Stanie się to powolne i opóźnione, gdy zawartość edytowalna wydłuży się ( innerHTMLjest droga). Poleciłbym wykorzystać inputwydarzenie tam, gdzie ono istnieje i powrócić do czegoś takiego, ale z pewnym zapowiedziami.
Tim Down
0

W Angular 2+

<div contentEditable (input)="type($event)">
   Value
</div>

@Component({
  ...
})
export class ContentEditableComponent {

 ...

 type(event) {
   console.log(event.data) // <-- The pressed key
   console.log(event.path[0].innerHTML) // <-- The content of the div 
 }
}

Dávid Konkoly
źródło
0

Postępuj zgodnie z następującym prostym rozwiązaniem

W .ts:

getContent(innerText){
  this.content = innerText;
}

w .html:

<div (blur)="getContent($event.target.innerHTML)" contenteditable [innerHTML]="content"></div>
Sahil Ralkar
źródło
-1

Sprawdź ten pomysł. http://pastie.org/1096892

Myślę, że jest blisko. HTML 5 naprawdę musi dodać zdarzenie zmiany do specyfikacji. Jedynym problemem jest to, że funkcja zwrotna ocenia, czy (przed == $ (this) .html ()) przed faktyczną aktualizacją treści w $ (this) .html (). setTimeout nie działa i to smutne. Powiedz mi co myślisz.

Lawrence Whiteside
źródło
Spróbuj keyup zamiast keypress.
Aen Tan
-2

Na podstawie odpowiedzi @ balupton:

$(document).on('focus', '[contenteditable]', e => {
	const self = $(e.target)
	self.data('before', self.html())
})
$(document).on('blur', '[contenteditable]', e => {
	const self = $(e.target)
	if (self.data('before') !== self.html()) {
	  self.trigger('change')
	}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Phillip Senn
źródło