DEV Community

loading...

Draft.js: Simple Content Manipulation

rose profile image Rose ・4 min read

Hey it’s part 5 of the series! I hope you are enjoying playing with Draft.js so far.

Today, I want to talk a bit about inserting content into the editor.

Some examples of when you may want to do this:

  • (simple) emoji picker: Maybe you want to let people click an emoji icon and have it automatically insert that emoji character
  • (simple) mentioning: Maybe you want to have an icon you click that inserts the @ mention trigger character
  • (more complicated) markdown: Maybe you want to be able to insert markdown characters under certain circumstances, or wrap text in markdown characters (eg maybe highlighting a word and hitting cmd/ctrl b wraps it in ** instead of actually bolding)
  • (more complicated) find-and-replace functionality

Let’s look at a simple example first 🙂

Here is what the finished product will look like


Simple example: Emoji Insertion

⁉️ For this example we’ll just be inserting the emoji characters into our editor when you click on them. If you are doing some real-life emoji work with your editor, you may want to look at something like This Draft.Js emoji plugin which converts emoji into their own custom Entity and provides a lot more flexibility for styling in-editor, as well as swapping out emoji assets for non-native assets if you want to.

The Modifier Module

We’re going to be using Draft.js’s Modifier to insert content into our editor.

What we want to do is insert the emoji at whatever location the user’s caret is currently at (their current “selection”) . We also want to replace content, if they have a range of text selected.

For this we’ll use Modifier’s method replaceText which takes:

  • The editor’s current ContentState
  • The text to be replaced, as indicated by SelectionState (this provides a range so the editor can say “ok I need to replace the content that currently exists between location X and location Y”. You could provide any range you like, provided content exists in that range, but for this simple case we just want to insert text where the selection already is, so no need to specify anything custom)
  • The text to be inserted.

What it returns is a new instance of ContentState that contains this change.

However, we still need to get this change into our EditorState and we can do this by using EditorState.push

EditorState.push takes

  • The editor state to apply changes to
  • The content state to apply (so we want the result of Modifier.replaceText here)
  • What kind of change you’re making, also known as an EditorChangeType. We’re doing insert-characters

EditorState.push returns a new instance of EditorState.

So with this in mind I wrote this small insertCharacter helper function:

function insertCharacter(characterToInsert, editorState) {
  const currentContent = editorState.getCurrentContent(),
        currentSelection = editorState.getSelection();

  const newContent = Modifier.replaceText(
    currentContent,
    currentSelection,
    characterToInsert
  );

  const newEditorState = EditorState.push(editorState, newContent, 'insert-characters');

  return  newEditorState;
}

It takes the character you want to insert, as well as the current editorState. It applies the modifications needed, then returns the new editorState.

We’d then need to apply this.setState with the new editorState to ensure we are actually using it, so the code that calls it would be something like:

const newEditorState = insertCharacter('💖', this.state.editorState);
this.setState({
  editorState: newEditorState
});

For my example I wanted to make some little emoji buttons, so I added this to my render method:

<div className="emoji-picker">
  <h2 className="toolbar-title">Insert Emoji:</h2>
  <button
      className="emoji"
      onMouseDown={(e) => e.preventDefault()}
      onClick={this.onEmojiClick}
      data-emoji="🎊">
        <span role="img" aria-label="confetti">🎊</span>
  </button>
  <button
      className="emoji"
      onMouseDown={(e) => e.preventDefault()}
      onClick={this.onEmojiClick}
      data-emoji="💖">
        <span role="img" aria-label="sparkle heart">💖</span>
  </button>
  <button
      className="emoji"
      onMouseDown={(e) => e.preventDefault()}
      onClick={this.onEmojiClick}
      data-emoji="🌼">
        <span role="img" aria-label="yellow flower">🌼</span>
  </button>
</div>

Then I defined onEmojiClick like so -

  onEmojiClick (e) {
    let emoji = e.currentTarget.getAttribute('data-emoji');
    this.setState({editorState: insertCharacter(emoji, this.state.editorState)});
  }

In other words, exactly what we talked about 🙂

There’s one other little gotcha here. You may notice that if you click the button when the editor doesn’t have focus, an emoji is inserted but the editor remains unfocused, which is likely not your desired behaviour.

If you like, you can ensure focus by using EditorState.forceSelection and update your insertCharacter method slightly:

function insertCharacter(characterToInsert, editorState) {
  const currentContent = editorState.getCurrentContent(),
        currentSelection = editorState.getSelection();

  const newContent = Modifier.replaceText(
    currentContent,
    currentSelection,
    characterToInsert
  );

  const newEditorState = EditorState.push(editorState, newContent, 'insert-characters');

  return  EditorState.forceSelection(newEditorState, newContent.getSelectionAfter());
}

This will make sure that after you insert the character, the selected state will be immediately after that character. (Official forceSelection documentation here)

So there’s our little intro into making modifications to your editor’s content. I will follow up in my next post with some trickier content replacement 🙂 (I would have done it all in one but I think shorter articles are a bit easier for people to consume, plus it makes it easier for me to publish updates more frequently 🙃)

Thanks (as always) for reading 💕

Discussion (9)

pic
Editor guide
Collapse
nkhil profile image
Nikhil Vijayan

Hi Rose, suggestion for a follow-up article would be using some of the draftjs plugins.

I'm fairly new to react, and tried to follow some of the documentation (github.com/draft-js-plugins/draft-...) and was more confused than ever before.

Thanks again for these articles, they're pure gold.

Collapse
rose profile image
Rose Author • Edited

Absolutely I'd love to dive into that! I've been a bit out of commission recently but as soon as I have free time again I'll try to add some more to this series :)

Collapse
jcbbb profile image
jcbbb

Hey Rose, awesome article! Are you planning on continuing with the series? If so, it would be great if you wrote about entities. Your explanations are awesome!

Collapse
rose profile image
Rose Author

I'm so glad you're finding them valuable :) a few people have commented with requests for various draftjs topics and many of them are great suggestions. I do want to continue the series but they are pretty labour intensive so I need to both have the time and the inclination, and those two things need to line up perfectly 😅 so yes to more posts (I hope/think.) But it may not be for quite some time 🙁 comments like yours do inspire me to want to continue so thanks for asking. I'll definitely prioritize a draftjs topic for my next post!

Collapse
jcbbb profile image
jcbbb

Thank you for putting so much effort. I will wait for your next post then!

Collapse
amasadelano profile image
r_zelazny

Hi, great series. It would be cool to do a post on code highlighting via prism or the like.

Collapse
rose profile image
Rose Author

Great idea!

Collapse
shekhars094 profile image
Shashi shekhar

Hi, I wanted to know that If I deleted some block from the contentState then How I can detect those blocks..???

Collapse
briunsilo profile image
Brian Pedersen

Great tutorial.
A post on handling sanitizing on paste would be great, even more so a post about custom types, entities and decorators.