contenteditable change events
Asked Answered
A

23

497

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!

Aretina answered 7/9, 2009 at 23:15 Comment(2)
I usually do this: document.getElementById("editor").oninput = function() { ...}.Burushaski
There is an excellent answer for 2020 down the page: https://mcmap.net/q/74081/-contenteditable-change-eventsBeefburger
A
430

2022 update

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.

Original answer

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>

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

Afflux answered 11/9, 2009 at 14:39 Comment(16)
I should also add that it's possible to change an editable element by using the browser's edit menu or context menu to cut, copy or paste content, so you'll need to handle cut, copy and paste events in browsers that support them (IE 5+, Firefox 3.0+, Safari 3+, Chrome)Afflux
You could use keyup, and you wont have a timing issue.Appreciable
@Blowsie: Yes, but you could potentially end up with lots of changes happening from an auto-repeated keypress before the event fires. There are also other ways of changing editable text not considered by this answer, such as drag and drop and edits via the Edit and context menus. Long-term, this should all be covered by the HTML5 input event.Afflux
I'm doing keyup with debounce.Leigh
Update Oct 2013: Unfortunately it still does not work in IE, not even in IE10! It's a pity, because it works fine in Chrome and Firefox, and indeed is the best solution.Geneticist
@Jamrelian: Indeed. Have you checked IE 11 preview?Afflux
Unfortunately, key events are not sufficient. You can also change the contents by cut and paste with the mouse or drag and drop (both of which could be monitored with mouse events, I guess). But HTM5 input is not an answer, because the question was about contenteditable divs.Comeau
@Gullbyrd: Yes. I should have mentioned cut/copy/paste/drag-and-drop; I have done so in similar answers (https://mcmap.net/q/75349/-onchange-event-with-contenteditable-duplicate, for example). However, I'm not wrong about the HTML5 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
Added a new solution hereLangur
Just to make this clear. The "input" solution does not detect copy, paste or cut. It does however detect delete.Circumscribe
@AndroidDev I testet Chrome, and the input event is also fired on copy paste and cut as required by the specs: w3c.github.io/uievents/#events-inputeventsBrimstone
Nothing is fired in the jsfiddle in my chrome ver 65.Accommodative
@NadavB: Something has changed in jsFiddle that made it not work any more. I've replaced the fiddle with a code snippet and it works fine now.Afflux
The issue I have is that 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 contenteditableDrumhead
Am I missing something here? The author specifically asked for something similar to the onchange event but this fires after every key input? The answer from Dennkster solved my problem but with a slight modificationClearheaded
@code-is-life: You're absolutely right. I clearly read into the question what I was expecting rather than what was written.Afflux
J
221

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

Jazzy answered 7/6, 2011 at 9:54 Comment(10)
Worked perfect for me, thanks for both CoffeeScript and JavascriptMaroc
There are some changes that won't be caught by this code until the contenteditable is blurred. For example: dragging & dropping, cutting from the context menu, and pasting from the browser window. I've setup an example page that uses this code which you can interact with here.Gibbs
@Gibbs Yes I had problems with this code when drag&dropping images because it doesn't listen to the image load (it was a problem to handle a resize operation in my case)Eyeshade
@Gibbs Very nice, this works even then changing value with javascript, try in console: document.getElementById('editor_input').innerHTML = 'ssss'. Drawback is that it requires IE11Yonne
Why do you need the "return $this;"?Accommodative
@NadavB result from the coffeescript conversion, in coffeescript you must always return things as it has implicit returns, and with jquery a return false can mean prevent default action. I've removed the coffeescript code (no one uses it anymore) and updated the javascript code.Jazzy
Thanks. When you paste text, it doesn't trigger the change twice?Accommodative
@NadavB it shouldn't, as that is what "if ($this.data('before') !== $this.html()) {" is forJazzy
Note: You may want to use either blur paste input or blur paste keyup in order to prevent the event from triggering twice.Herve
This is still the best solution in 2021, I just removed 'keyup' and 'input' from the event list to get REAL 'change' event behaviour, which only fires after losing focus on the element and not while user is still doing changes to the string.Differentia
G
69

Consider using MutationObserver. These observers are designed to react to changes in the DOM, and as a performant replacement to Mutation Events.

Pros:

  • Fires when any change occurs, which is difficult to achieve by listening to key events as suggested by other answers. For example, all of these work well: drag & drop, italicizing, copy/cut/paste through context menu.
  • Designed with performance in mind.
  • Simple, straightforward code. It's a lot easier to understand and debug code that listens to one event rather than code that listens to 10 events.
  • Google has an excellent mutation summary library which makes using MutationObservers very easy.

Cons:

  • Requires a very recent version of Firefox (14.0+), Chrome (18+), or IE (11+).
  • New API to understand
  • Not a lot of information available yet on best practices or case studies

Learn more:

  • I wrote a little snippet to compare using MutationObserers to handling a variety of events. I used balupton's code since his answer has the most upvotes.
  • Mozilla has an excellent page on the API
  • Take a look at the MutationSummary library
Gibbs answered 26/12, 2012 at 17:24 Comment(6)
It's not quite the same as the HTML5 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
+1, but did you realize that Mutation Events do not report the effects of line feed in a contenteditable? Press enter in your snippet.Bovill
@citykid: That's because the snippet is only watching for changes to character data. It's possible to observe DOM structural changes too. See jsfiddle.net/4n2Gz/1, for example.Afflux
Internet Explorer 11+ supports Mutation Observers.Elamite
I was able to verify (at least in Chrome 43.0.2357.130) that the HTML5 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
Snippet is missing.Monogyny
C
30

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);
});
Cuprous answered 11/10, 2015 at 12:10 Comment(1)
what is this delete event?Olivas
G
26

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')});
Greeting answered 13/9, 2010 at 14:6 Comment(1)
Exactly what I needed. Removed keyup though as I need the alert to fire when element looses focusClearheaded
C
24

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 });
Constipate answered 14/3, 2018 at 20:23 Comment(2)
Just use the input event! If you need to support IE11, use a mutation observer. Love it. Short and to the point.Beefburger
Not really. The point is that the OP wants to do something on the final change (what a 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
K
12

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 />
Kantian answered 7/11, 2018 at 10:29 Comment(1)
This looks interesting. Can someone provide some explanation? 😇Wade
B
4

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);
            }
   });
Boffin answered 28/1, 2011 at 21:9 Comment(0)
C
4

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

Update:

Due to its increasing popularity the plugin has been adopted by Makesites.org

Development will continue from here:

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

Chromonema answered 21/8, 2012 at 7:36 Comment(0)
S
4

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.

Swore answered 17/5, 2016 at 9:9 Comment(2)
Ugh, why the downvote with no explanation? This does a disservice to both the person who wrote the answer, and everyone who reads the answer later.Insolent
This code makes an element 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
E
4

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


Emulsoid answered 17/11, 2019 at 17:15 Comment(0)
I
3

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.

Incompressible answered 9/5, 2010 at 6:19 Comment(0)
A
3

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!');
    }
});
Aspectual answered 5/5, 2011 at 17:22 Comment(0)
S
3

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

know more

Salmonoid answered 1/4, 2021 at 1:36 Comment(0)
J
2

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.

Example

<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>
Jaquez answered 18/10, 2021 at 7:41 Comment(0)
S
1

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

JSFIDDLE example

Sexennial answered 28/2, 2012 at 14:33 Comment(2)
+1 - 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
Just a note that Mutation Events are depreciated by the w3c because of performance problems. More can be found in this Stack Overflow question.Gibbs
V
0

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:

Veneering answered 7/9, 2009 at 23:56 Comment(1)
for posterity, ten years forward and there's no need for a "save" button, check the accepted answer's jsfiddleAesir
A
0

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

Appreciable answered 4/6, 2013 at 16:22 Comment(1)
That will get slow and laggy as the editable content gets longer (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
S
0

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
        }           
    });
Solnit answered 29/3, 2016 at 9:44 Comment(1)
Note that $("div [contenteditable=true]") will select all childrens of a div, directly or indirectly, that are contenteditable.Synchronism
C
0
  1. Set input event trigger on the contenteditable-element
document.getElementById("contenteditableElement").addEventListener("input", function() {
            elementEdited();
        }, false);
  1. Save value once contenteditable-element looses focus
    jQuery("#contenteditableElement").blur(function(){
        //once element looses focus, you can save the new value etc..
        });
Ciprian answered 8/8, 2023 at 19:48 Comment(0)
M
0

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>
Marylnmarylou answered 14/2 at 6:35 Comment(0)
F
-1

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.

Fusil answered 17/8, 2010 at 3:14 Comment(1)
Try keyup instead of keypress.Leigh
W
-2

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>
Were answered 30/8, 2017 at 18:25 Comment(2)
Remember, if you down vote an answer, you are responsible to leave a comment. Explain why the answer is unsatisfactory (unless someone has already left a comment, and you just agree with them).Beefburger
jQuery is never an appropriate answer @JackSteamLuger

© 2022 - 2024 — McMap. All rights reserved.