Search code examples
javascriptjquerykeypresscontenteditablekeyup

How to get the exact element edited within a contenteditable div?


I have a contenteditable div with a keyup event attached to it. There are many p tags created within that div as the user types.

$(".pad").keyup(function(e) {
    doSomething();
});

That's the keyup event.

How could I work out which element the user has edited/created within the contenteditable div?

I have tried the following but it doesn't seem to work:

$(".pad p").keyup(function(e) {
    doSomething();
});

The goal is to have something like this:

$(".pad").keyup(function(e) {
    theEditedElement = ...;
    $(theEditedElement).css("color","red");
    $(theEditedElement).(...);
});

We start off with:

<div class="pad" contenteditable="true">
    <p>Some text</p>
</div>

This is then edited by the user to:

<div class="pad" contenteditable="true">
    <p>Some text</p>
    <p>Some more text</p>
    <p>Even <b>more</b> text</p>
</div>

If the user decides to edit <p>Some more text</p>, then that specific <p> element should be retrieved.


Solution

  • If you want your event target to be a child of a div that is content editable instead of the div itself, you need to set those child nodes to be contenteditable as well. You can do that dynamically like this.

    const contentEl = document.querySelector('.pad');
    const paras = contentEl.childNodes;
    
    // here, set contenteditable attribute for all paragraphs inside of that div
    for (let i = 0; i < paras.length; i++) {
      if (paras[i].nodeType === document.ELEMENT_NODE) {
        paras[i].setAttribute('contenteditable', true);
      }
    }
    
    contentEl.addEventListener('keyup', event => {
      // event.target will be the element that you are asking for 
      const theEditedElement = event.target;
      console.log(theEditedElement);
      theEditedElement.style.color = 'red';
    });
    <div class="pad">
        <p>Some text</p>
        <p>Some more text</p>
        <p>Even <b>more</b> text</p>
    </div>

    Note that you might need to run the piece of code that sets contenteditable attribute to div's childs again, when a new p element is created inside of that div.

    Another option would be to create mutation observer and let it handle the changes to the enclosing div in case any new paragraphs are added (let it set contenteditable attribute of the newly added paragraph).

    const contentEl = document.querySelector('.pad');
    const paras = contentEl.childNodes;
    
    for (let i = 0; i < paras.length; i++) {
      if (paras[i].nodeType === document.ELEMENT_NODE) {
        paras[i].setAttribute('contenteditable', true);
      }
    }
    
    contentEl.addEventListener('keyup', event => {
      // event.target will be the element that you are asking for 
      const theEditedElement = event.target;
      console.log(theEditedElement);
      theEditedElement.style.color = 'red';
    });
    
    // this code here is just to demonstrate the change to the enclosing div
    const addNewParagraph = () => {
      const p = document.createElement('p');
      contentEl.appendChild(p);
      p.textContent = 'new paragraph';
    };
    
    const btn = document.querySelector('button');
    btn.addEventListener('click', addNewParagraph);
    
    // create mutation observer
    const config = { childList: true };
    const callback = function(mutationList) {
      for (const mutation of mutationList) {
        if (mutation.type === 'childList') {
          console.log('new paragraph has been added');
          // get the added node and set its contenteditable attribute
          mutation.addedNodes[0].setAttribute('contenteditable', true);
        }
      }
    }
    const observer = new MutationObserver(callback);
    observer.observe(contentEl, config);
    <button>add new paragraph</button>
    
    <div class="pad">
        <p>Some text</p>
        <p>Some more text</p>
        <p>Even <b>more</b> text</p>
    </div>