I want to run a function when a user edits the content of a div
with contenteditable
attribute. What's the equivalent of an onchange
event?
I'm using jQuery so any solutions that uses jQuery is preferred. Thanks!
I want to run a function when a user edits the content of a div
with contenteditable
attribute. What's the equivalent of an onchange
event?
I'm using jQuery so any solutions that uses jQuery is preferred. Thanks!
As pointed out in the comments, this doesn't answer the question asked, which wanted the equivalent of the change
event rather than the input
event. However, I'll leave it here as is.
I'd suggest attaching listeners to key events fired by the editable element, though you need to be aware that keydown
and keypress
events are fired before the content itself is changed. This won't cover every possible means of changing the content: the user can also use cut, copy and paste from the Edit or context browser menus, so you may want to handle the cut
copy
and paste
events too. Also, the user can drop text or other content, so there are more events there (mouseup
, for example). You may want to poll the element's contents as a fallback.
UPDATE 29 October 2014
The HTML5 input
event is the answer in the long term. At the time of writing, it is supported for contenteditable
elements in current Mozilla (from Firefox 14) and WebKit/Blink browsers, but not 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>
cut
, copy
and paste
events in browsers that support them (IE 5+, Firefox 3.0+, Safari 3+, Chrome) –
Afflux input
event. –
Afflux input
event. The HTML Editing spec seems to have stalled but the input
event is part of it (w3.org/Bugs/Public/show_bug.cgi?id=13118) and most browsers implement it for contenteditable
. –
Afflux input
is fired on every keypress, I want an event that fires at the end, I'm not sure if the change event works for contenteditable
–
Drumhead Here is a more efficient version which uses on
for all contenteditables. It's based off the top answers here.
$('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');
}
});
The project is here: https://github.com/balupton/html5edit
document.getElementById('editor_input').innerHTML = 'ssss'
. Drawback is that it requires IE11 –
Yonne blur paste input
or blur paste keyup
in order to prevent the event from triggering twice. –
Herve Consider using MutationObserver. These observers are designed to react to changes in the DOM, and as a performant replacement to Mutation Events.
Pros:
Cons:
Learn more:
input
event (which is supported for contenteditable
in all the WebKit and Mozilla browsers that support mutation observers), since DOM mutation could occur via script as well as user input, but it's a viable solution for those browsers. I imagine it could harm performance more than the input
event too, but I have no hard evidence for this. –
Afflux input
event fires in each of these situations (specifically drag & drop, italicizing, copy/cut/paste through context menu). I also tested bolding w/ cmd/ctrl+b and got the expected results. I also checked and was able to verify that the input
event does not fire on programmatic changes (which seems obvious, but is arguably contrary to some confusing language on the relevant MDN page; see the bottom of the page here to see what I mean: developer.mozilla.org/en-US/docs/Web/Events/input) –
Slider non jQuery quick and dirty answer:
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);
});
I have modified lawwantsin 's answer like so and this works for me. I use the keyup event instead of keypress which works great.
$('#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')});
Two options:
1) For modern (evergreen) browsers: The "input" event would act as an alternative "change" event.
https://developer.mozilla.org/en-US/docs/Web/Events/input
document.querySelector('div').addEventListener('input', (e) => {
// Do something with the "change"-like event
});
or
<div oninput="someFunc(event)"></div>
or (with jQuery)
$('div').on('click', function(e) {
// Do something with the "change"-like event
});
2) To account for IE11 and modern (evergreen) browsers: This watches for element changes and their contents inside the 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 });
input
event! If you need to support IE11, use a mutation observer. Love it. Short and to the point. –
Beefburger change
event would do), while input
is firing as you type. I'm firing an AJAX request -- I don't want to do that on every keypress. Many of the other answers explain how to handle that. –
Archaeozoic 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 />
Here's what worked for me:
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);
}
});
This thread was very helpful while I was investigating the subject.
I've modified some of the code available here into a jQuery plugin so it is in a re-usable form, primarily to satisfy my needs but others may appreciate a simpler interface to jumpstart using contenteditable tags.
https://gist.github.com/3410122
Due to its increasing popularity the plugin has been adopted by Makesites.org
Development will continue from here:
Non JQuery answer...
function makeEditable(elem){
elem.setAttribute('contenteditable', 'true');
elem.addEventListener('blur', function (evt) {
elem.removeAttribute('contenteditable');
elem.removeEventListener('blur', evt.target);
});
elem.focus();
}
To use it, call on (say) a header element with id="myHeader"
makeEditable(document.getElementById('myHeader'))
That element will now be editable by the user until it loses focus.
contenteditable
until it loses focus. I don't think this answers the question. The question is "How can I run a function when a contenteditable
element changes?" –
Beefburger 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
}
}
To avoid timers and "save" buttons, you may use blur event wich fires when the element loses focus. but to be sure that the element was actually changed (not just focused and defocused), its content should be compared against its last version. or use keydown event to set some "dirty" flag on this element.
Here is the solution I ended up using and works fabulously. I use $(this).text() instead because I am just using a one line div that is content editable. But you may also use .html() this way you dont have to worry about the scope of a global/non-global variable and the before is actually attached to the editor 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!');
}
});
You need to use input event type
Demo
HTML
<div id="editor" contenteditable="true" >Some text here</div>
JS
const input = document.getElementById('editor');
input.addEventListener('input', updateValue);
function updateValue(e) {
console.log(e.target);
}
For me, I want to check the input is valid or not.
If valid, then update, Otherwise show an error message and keep the value as same as before.
Skill: When you edit done, usually, it will trigger the blur event.
<span contenteditable="true">try input somethings.</span>
<script>
const elem = document.querySelector(`span`)
let oldValue = elem.innerText
elem.onkeydown = (keyboardEvent) => {
if (keyboardEvent.key === "Enter") {
elem.blur() // set focusout
}
}
elem.onblur = (e) => {
const curValue = elem.innerText
if (curValue === oldValue) {
return
}
if (curValue.length <= 50) { // 👈 Input your conditions.
// 👇 fail
elem.innerText = oldValue
// (Optional) Add error message
elem.insertAdjacentHTML("beforeend", `<span style="margin-left:5px;color:red">error length=${curValue.length}. Must greater than 50. undo to the previous value.</span>`)
const errMsg = elem.querySelector(`span`)
setTimeout(() => errMsg.remove(), 3500) // wait 3.5 second, and then remove it.
return
}
// 👇 OK, update
oldValue = curValue
}
</script>
Using DOMCharacterDataModified under MutationEvents will lead to the same. The timeout is setup to prevent sending incorrect values (e.g. in Chrome I had some issues with space key)
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());
})
DOMCharacterDataModified
won't fire when the user modifies existing text, for example applying bold or italics. DOMSubtreeModified
is more appropriate in this case. Also, people should remember that legacy browsers don't support these events. –
Pleasantry The onchange event doesn't fires when an element with the contentEditable attribute is changed, a suggested approach could be to add a button, to "save" the edition.
Check this plugin which handles the issue in that way:
I built a jQuery plugin to do this.
(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);
You can simply call $('.wysiwyg').wysiwygEvt();
You can also remove / add events if you wish
innerHTML
is expensive). I would recommend using the input
event where it exists and falling back to something like this but with some kind of debouncing. –
Afflux A simple answer in JQuery, I just created this code and thought it will be helpful for others too
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
}
});
$("div [contenteditable=true]")
will select all childrens of a div, directly or indirectly, that are contenteditable. –
Synchronism document.getElementById("contenteditableElement").addEventListener("input", function() {
elementEdited();
}, false);
jQuery("#contenteditableElement").blur(function(){
//once element looses focus, you can save the new value etc..
});
Focus / Blur events. Use the blur (unfocus) event to record the changes.
document.querySelector("p").addEventListener("focus",function(e){
console.log("focus :" + e.target.textContent);
})
document.querySelector("p").addEventListener("blur",function(e){
console.log("blur :" + e.target.textContent);
})
<p contenteditable>This is a paragraph element</p>
Check this idea out. http://pastie.org/1096892
I think it's close. HTML 5 really needs to add the change event to the spec. The only problem is that the callback function evaluates if (before == $(this).html()) before the content is actually updated in $(this).html(). setTimeout don't work, and it's sad. Let me know what you think.
Based on @balupton's answer:
$(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>
© 2022 - 2024 — McMap. All rights reserved.
document.getElementById("editor").oninput = function() { ...}
. – Burushaski