inhaltsbearbeitbare Änderungsereignisse

359

Ich möchte eine Funktion ausführen, wenn ein Benutzer den Inhalt eines divwith- contenteditableAttributs bearbeitet . Was entspricht einer onchangeVeranstaltung?

Ich verwende jQuery, daher werden alle Lösungen bevorzugt, die jQuery verwenden. Vielen Dank!

Jourkey
quelle
Normalerweise mache ich das : document.getElementById("editor").oninput = function() { ...}.
Basj

Antworten:

311

Ich würde Anbringen Hörer vorschlagen zu den wichtigsten Veranstaltungen des editierbaren Element gefeuert, obwohl Sie müssen sich bewusst sein , dass keydownund keypressEreignisse ausgelöst werden , bevor der Inhalt selbst geändert wird. Dies deckt nicht alle Möglichkeiten zum Ändern des Inhalts ab: Der Benutzer kann auch das Ausschneiden, Kopieren und Einfügen aus den Menüs Bearbeiten oder Kontextbrowser verwenden, sodass Sie möglicherweise auch die Ereignisse cut copyund behandeln möchten paste. Außerdem kann der Benutzer Text oder anderen Inhalt ablegen, sodass dort ( mouseupzum Beispiel) mehr Ereignisse vorhanden sind . Möglicherweise möchten Sie den Inhalt des Elements als Fallback abfragen.

UPDATE 29. Oktober 2014

Das HTML5- inputEreignis ist langfristig die Antwort. Zum Zeitpunkt des Schreibens wird es für contenteditableElemente in aktuellen Mozilla- (ab Firefox 14) und WebKit / Blink-Browsern unterstützt, jedoch nicht für IE.

Demo:

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
quelle
11
Ich möchte auch hinzufügen , dass es möglich ist , durch ein editierbares Element zu ändern den Browser des Edit - Menü oder im Kontextmenü zu schneiden, kopieren oder Inhalte einfügen, so dass Sie Griff benötigen cut, copyund pasteEreignisse in Browsern , die sie unterstützen (IE 5+, Firefox 3.0+, Safari 3+, Chrome)
Tim Down
4
Sie könnten Keyup verwenden und haben kein Timing-Problem.
Blowsie
2
@Blowsie: Ja, aber Sie könnten möglicherweise viele Änderungen durch einen automatisch wiederholten Tastendruck erhalten, bevor das Ereignis ausgelöst wird. Es gibt auch andere Möglichkeiten, bearbeitbaren Text zu ändern, der in dieser Antwort nicht berücksichtigt wird, z. B. Drag & Drop und Bearbeiten über die Menüs Bearbeiten und Kontext. Langfristig sollte dies alles durch das HTML5- inputEreignis abgedeckt werden .
Tim Down
1
Ich mache Keyup mit Debounce.
Aen Tan
1
@AndroidDev I Getestet Chrome und das Eingabeereignis wird auch auf Kopie gebrannt einfügen und geschnitten , wie durch die Spezifikationen erforderlich: w3c.github.io/uievents/#events-inputevents
Fischgräte
188

Hier ist eine effizientere Version, die onfür alle inhaltsbearbeitbaren Dateien verwendet wird. Es basiert auf den Top-Antworten hier.

$('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');
    }
});

Das Projekt ist hier: https://github.com/balupton/html5edit

Balupton
quelle
Hat perfekt für mich
funktioniert
7
Es gibt einige Änderungen, die von diesem Code erst erfasst werden, wenn die inhaltsbearbeitbare Datei unscharf ist. Zum Beispiel: Ziehen und Ablegen, Ausschneiden aus dem Kontextmenü und Einfügen aus dem Browserfenster. Ich habe eine Beispielseite eingerichtet, die diesen Code verwendet, mit dem Sie hier interagieren können .
Jrullmann
@jrullmann Ja, ich hatte Probleme mit diesem Code beim Ziehen und Ablegen von Bildern, da er das Laden von Bildern nicht abhört (in meinem Fall war es ein Problem, eine Größenänderungsoperation durchzuführen)
Sebastien Lorber
@jrullmann Sehr schön, das funktioniert auch dann, wenn der Wert mit Javascript geändert wird. Versuchen Sie es in der Konsole: document.getElementById('editor_input').innerHTML = 'ssss' . Nachteil ist, dass es IE11 erfordert
1
@NadavB sollte es nicht, da dies "if ($ this.data ('before')! == $ this.html ()) {" für
balupton ist
52

Erwägen Sie die Verwendung von MutationObserver . Diese Beobachter sind entworfen , um Veränderungen in dem DOM und als performant Ersatz zu reagieren Mutationsereignissen .

Vorteile:

  • Wird ausgelöst , wenn irgendeine Änderung auftritt, die nur schwer durch das Hören auf wichtige Ereignisse zu erreichen , wie von anderen Antworten vorgeschlagen. All dies funktioniert beispielsweise gut: Drag & Drop, Kursivschrift, Kopieren / Ausschneiden / Einfügen über das Kontextmenü.
  • Entwickelt mit Blick auf Leistung.
  • Einfacher, unkomplizierter Code. Es ist viel einfacher, Code zu verstehen und zu debuggen, der ein Ereignis abhört, als Code, der 10 Ereignisse abhört.
  • Google verfügt über eine hervorragende Bibliothek mit Mutationszusammenfassungen, die die Verwendung von MutationObservern sehr einfach macht.

Nachteile:

  • Erfordert eine sehr aktuelle Version von Firefox (14.0+), Chrome (18+) oder IE (11+).
  • Neue API zu verstehen
  • Es sind noch nicht viele Informationen zu Best Practices oder Fallstudien verfügbar

Erfahren Sie mehr:

  • Ich habe einen kleinen Ausschnitt geschrieben , um die Verwendung von MutationObserers mit der Behandlung einer Vielzahl von Ereignissen zu vergleichen. Ich habe seit seiner Antwort Baluptons Code verwendet die meisten positiven Stimmen hat.
  • Mozilla hat eine ausgezeichnete Seite auf der API
  • Schauen Sie sich die MutationSummary- Bibliothek an
jrullmann
quelle
Es ist nicht ganz dasselbe wie das HTML5- inputEreignis (das contenteditablein allen WebKit- und Mozilla-Browsern unterstützt wird, die Mutationsbeobachter unterstützen), da die DOM-Mutation sowohl über Skripte als auch über Benutzereingaben erfolgen kann, aber es ist eine praktikable Lösung für diese Browser. Ich kann mir vorstellen, dass dies die Leistung mehr beeinträchtigen könnte als das inputEreignis, aber ich habe keine eindeutigen Beweise dafür.
Tim Down
2
+1, aber haben Sie festgestellt, dass Mutationsereignisse die Auswirkungen von Zeilenvorschub in einer inhaltsbearbeitbaren Datei nicht melden? Drücken Sie die Eingabetaste in Ihrem Snippet.
Citykid
4
@citykid: Das liegt daran, dass das Snippet nur nach Änderungen an den Zeichendaten sucht. Es ist auch möglich, DOM-Strukturänderungen zu beobachten. Siehe zum Beispiel jsfiddle.net/4n2Gz/1 .
Tim Down
Internet Explorer 11+ unterstützt Mutationsbeobachter .
Sampson
Ich konnte überprüfen (zumindest in Chrome 43.0.2357.130), dass das HTML5- inputEreignis in jeder dieser Situationen ausgelöst wird (insbesondere Drag & Drop, Kursivschrift, Kopieren / Ausschneiden / Einfügen über das Kontextmenü). Ich habe auch Fettdruck mit cmd / Strg + B getestet und die erwarteten Ergebnisse erzielt. Ich habe auch überprüft und konnte überprüfen, ob das inputEreignis nicht bei programmatischen Änderungen ausgelöst wird (was offensichtlich erscheint, aber möglicherweise einer verwirrenden Sprache auf der entsprechenden MDN-Seite widerspricht; siehe unten auf der Seite, um zu sehen, was ich meine: Entwickler .mozilla.org / en-US / docs / Web / Events / Eingabe )
mikermcneil
22

Ich habe die Antwort von lawwantsin so geändert und das funktioniert für mich. Ich benutze das Keyup-Ereignis anstelle des Tastendrucks, was großartig funktioniert.

$('#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
quelle
22

non jQuery schnelle und schmutzige Antwort:

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);
});
Entwicklung
quelle
3
Was ist dieses Löschereignis?
James Cat
7

Zwei Optionen:

1) Für moderne (immergrüne) Browser: Das "Eingabe" -Ereignis würde als alternatives "Änderungs" -Ereignis fungieren.

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

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

oder

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

oder (mit jQuery)

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

2) Um IE11 und moderne (immergrüne) Browser zu berücksichtigen: Dies überwacht Elementänderungen und deren Inhalt innerhalb der 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 });
ein bisschen weniger
quelle
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
quelle
2
Das sieht interessant aus. Kann jemand eine Erklärung geben? 😇
WoIIe
3

Folgendes hat bei mir funktioniert:

   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
quelle
3

Dieser Thread war sehr hilfreich, als ich das Thema untersuchte.

Ich habe einen Teil des hier verfügbaren Codes in ein jQuery-Plugin geändert, sodass er in einer wiederverwendbaren Form vorliegt, hauptsächlich um meine Anforderungen zu erfüllen. Andere schätzen jedoch möglicherweise eine einfachere Oberfläche für den Start mit inhaltsbearbeitbaren Tags.

https://gist.github.com/3410122

Aktualisieren:

Aufgrund seiner zunehmenden Beliebtheit wurde das Plugin von Makesites.org übernommen

Die Entwicklung wird von hier aus fortgesetzt:

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

tracend
quelle
2

Hier ist die Lösung, die ich letztendlich verwendet habe und die hervorragend funktioniert. Ich verwende stattdessen $ (this) .text (), weil ich nur ein einzeiliges div verwende, das inhaltlich bearbeitet werden kann. Sie können aber auch .html () verwenden, damit Sie sich keine Gedanken über den Umfang einer globalen / nicht globalen Variablen machen müssen und das Vorherige tatsächlich an den Editor div angehängt ist.

$('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!');
    }
});
user740433
quelle
1

Um Timer und Schaltflächen zum Speichern zu vermeiden, können Sie ein Unschärfeereignis verwenden, das ausgelöst wird, wenn das Element den Fokus verliert. Um jedoch sicherzugehen, dass das Element tatsächlich geändert wurde (nicht nur fokussiert und defokussiert), sollte sein Inhalt mit seiner letzten Version verglichen werden. Oder verwenden Sie das Keydown-Ereignis, um ein "schmutziges" Flag für dieses Element zu setzen.

Joox
quelle
1

Eine einfache Antwort in JQuery: Ich habe diesen Code gerade erstellt und dachte, dass er auch für andere hilfreich sein wird

    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
quelle
Beachten Sie, dass $("div [contenteditable=true]")alle Kinder eines Div direkt oder indirekt ausgewählt werden, die inhaltsbearbeitbar sind.
Bruno Ferreira
1

Nicht JQuery Antwort ...

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

Rufen Sie dazu ein Header-Element mit der ID = "myHeader" auf.

makeEditable(document.getElementById('myHeader'))

Dieses Element kann jetzt vom Benutzer bearbeitet werden, bis es den Fokus verliert.

DrMcCleod
quelle
5
Ugh, warum das Downvote ohne Erklärung? Dies ist sowohl für die Person, die die Antwort geschrieben hat, als auch für alle, die die Antwort später lesen, ein schlechter Dienst.
Mike Willis
0

Das Ereignis onchange wird nicht ausgelöst, wenn ein Element mit dem Attribut contentEditable geändert wird. Ein empfohlener Ansatz könnte darin bestehen, eine Schaltfläche hinzuzufügen, um die Edition zu "speichern" .

Überprüfen Sie dieses Plugin, das das Problem auf diese Weise behandelt:

CMS
quelle
Für die Nachwelt, zehn Jahre später und ohne "Speichern" -Button, überprüfen Sie die jsfiddle der akzeptierten Antwort
reveltieren Sie den
0

Die Verwendung von DOMCharacterDataModified unter MutationEvents führt zu demselben Ergebnis . Das Zeitlimit wurde eingerichtet, um das Senden falscher Werte zu verhindern (z. B. hatte ich in Chrome einige Probleme mit der Leertaste).

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

JSFIDDLE-Beispiel

Janfabian
quelle
1
+1 - DOMCharacterDataModifiedwird nicht ausgelöst, wenn der Benutzer vorhandenen Text ändert, z. B. fett oder kursiv. DOMSubtreeModifiedist in diesem Fall angemessener. Außerdem sollten sich Benutzer daran erinnern, dass ältere Browser diese Ereignisse nicht unterstützen.
Andy E
3
Nur ein Hinweis, dass Mutationsereignisse vom w3c aufgrund von Leistungsproblemen abgeschrieben werden. Weitere Informationen finden Sie in dieser Frage zum Stapelüberlauf .
Jrullmann
0

Ich habe dazu ein jQuery-Plugin erstellt.

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

Sie können einfach anrufen $('.wysiwyg').wysiwygEvt();

Sie können Ereignisse auch entfernen / hinzufügen, wenn Sie dies wünschen

Blowsie
quelle
2
Das wird langsam und verzögert, wenn der bearbeitbare Inhalt länger wird ( innerHTMList teuer). Ich würde empfehlen, das inputEreignis dort zu verwenden, wo es existiert, und auf so etwas zurückzugreifen, aber mit einer Art Entprellung.
Tim Down
0

In 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
quelle
0

Bitte folgen Sie der folgenden einfachen Lösung

In .ts:

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

in .html:

<div (blur)="getContent($event.target.innerHTML)" contenteditable [innerHTML]="content"></div>
Sahil Ralkar
quelle
-1

Schauen Sie sich diese Idee an. http://pastie.org/1096892

Ich denke es ist nah. HTML 5 muss das Änderungsereignis wirklich zur Spezifikation hinzufügen. Das einzige Problem ist, dass die Rückruffunktion auswertet, ob (vor == $ (this) .html ()), bevor der Inhalt tatsächlich in $ (this) .html () aktualisiert wird. setTimeout funktioniert nicht und es ist traurig. Lass mich wissen was du denkst.

Lawrence Whiteside
quelle
Versuchen Sie Keyup anstelle von Tastendruck.
Aen Tan
-2

Basierend auf der Antwort von @ 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
quelle