DEV Community

Cover image for Draft.js introduction: Custom styles (highlighted text!) and have formatting buttons show whether they are “on” or “off”
Rose
Rose

Posted on

Draft.js introduction: Custom styles (highlighted text!) and have formatting buttons show whether they are “on” or “off”

Hello!

I can’t believe we’re already on #4 of this series 😱

Today I wanted to cover 2 fun topics that I think are useful to know about:

  1. Defining custom styles (in my example we’ll be adding a “highlighter” option to the editor)
  2. Detecting what styles/blocks are currently active based on the selected text or cursor position. This can be useful for things like showing an “on” and “off” in state in the formatting buttons.

🚀 This is how the finished product will look at the end of the post


Defining custom styles

Draft.js makes this very simple 🙂 We need to define a "style object" and pass it in to the editor.

I liked the idea of letting users highlight certain blocks of text, so that’s the style I thought we could add for this demo.

We create an object that we’ll call styleMap that has the key for the style’s name, and the value is another object that defines the CSS we want to use to style that item. So if bold didn’t already exist the key would be BOLD and the value would be {'fontWeight': 'bold'}

But bold does exist of course, so here’s our little custom style for highlight:

const styleMap = {
  'HIGHLIGHT': {
    'backgroundColor': '#faed27',
  }
};

(🤓 Newbie hint: If you want to define multiple custom styles, they can be comma-separated here, so after the closing } for HIGHLIGHT you could add another style definition).

Then we need to pass in our styleMap to the editor component itself, as customStyleMap -

<Editor
  customStyleMap={styleMap}
  placeholder={"Start typing!"}
  editorState={this.state.editorState}
  onChange={this.onChange}
  handleKeyCommand={this.handleKeyCommand}
  keyBindingFn={keyBindingFunction}
/>

🌼 So now the editor knows about the highlight style and knows what to do with it, but that’s not much use if we don’t expose anywhere for the user to toggle it.

As taught in this previous series post I’ll add a keyboard shortcut (cmd [or ctrl] + shift + h) for highlighting text, and I’ll also add a button to my inline style buttons.

I’d go back and read that post if you need the full block of code to jog your memory, but for the keyboard shortcut, I’ll be adding to my keyBindingFunction the following if statement:

if (KeyBindingUtil.hasCommandModifier(event) && event.shiftKey && event.key === 'h') {
  return 'highlight';
}

And then in the handleKeyCommand function I’ll add this:

if (!editorState && command === 'highlight') {
  editorState = RichUtils.toggleInlineStyle(this.state.editorState, 'HIGHLIGHT');
}

And now the keyboard shortcut to highlight text should be functioning!

Adding the button should also be fairly straightforward. Since I have an array defining all my inline styles which then uses .map to render the buttons, I just add a new item to that array:

const inlineStyleButtons = [
  {
    value: 'Bold',
    style: 'BOLD'
  },

  {
    value: 'Italic',
    style: 'ITALIC'
  },

  {
    value: 'Underline',
    style: 'UNDERLINE'
  },

  {
    value: 'Strikethrough',
    style: 'STRIKETHROUGH'
  },

   {
    value: 'Code',
    style: 'CODE'
  },

  {
    value: 'Highlight',
    style: 'HIGHLIGHT'
  }
];

And we’re done. Highlighting functionality complete ✅

Detecting active styles and blocks

The editorState instance contains everything there is to know about your Draft editor at any given moment, and this includes knowing where your cursor (selection state) is.

Draft.js also provides a couple of handy helper functions to use this knowledge to tell you exactly what styles are active, and what block element is selected.

To get the current inline style - in other words, bold, italic, code, highlight, etc, you can call this.state.editorState.getCurrentInlineStyle()

This returns an Ordered Set of the currently active styles. If you aren’t familiar with immutable.js’s Ordered Set you can check out that link if you like, but the thing we care about right now is that it has a method called has to check for the existence of a key.

We can use that to search for things like .has('BOLD') to get a true-or-false response.

Here’s my updated renderInlineStyleButton method that uses this check and then conditionally sets an active class on the button if .has returns true for that style type:

renderInlineStyleButton(value, style) {
  const currentInlineStyle = this.state.editorState.getCurrentInlineStyle();
  let className = '';
  if (currentInlineStyle.has(style)) {
    className = 'active';
  }

  return (
    <input
      type="button"
      key={style}
      value={value}
      className={className}
      data-style={style}
      onClick={this.toggleInlineStyle}
      onMouseDown={preventDefault}
    />
  );
}

We’re going to do something similar with our renderBlockButton but instead of using editorState.getCurrentInlineStyle we’re going to use a helper method on RichUtils

⁉️ I have no idea why two fairly similar functionalities are found in two different parts of Draft.js 😬

Draft only allows you to have one block type at a time, so instead of getting a set of values, we’ll just be getting a single block type.

The method we want to use is RichUtils.getCurrentBlockType which takes editorState as an argument and returns a string of a block type as a response. So if we were checking for a blockquote we could do something like RichUtils.getCurrentBlockType(this.state.editorState) === 'blockquote'

Here’s my block button render method with the additional conditional active class:

renderBlockButton(value, block) {
  const currentBlockType = RichUtils.getCurrentBlockType(this.state.editorState);
  let className = '';
  if (currentBlockType === block) {
    className = 'active';
  }

  return (
    <input
      type="button"
      key={block}
      value={value}
      className={className}
      data-block={block}
      onClick={this.toggleBlockType}
      onMouseDown={preventDefault}
    />
  );
}

And then you’re basically done. The only thing left is to add some CSS so that toggling the active class on and off actually has some visual effect.

Here’s the simple CSS I used if you want something to get started:

.toolbar input[type="button"] {
  border: 1px solid #eee;
  background-color: #fff;
  border-radius: 5px;
  cursor: pointer;
  margin-right: 2px;
  margin-bottom: 2px;
}

.toolbar input[type="button"]:active {
  transform: scale(.95);
}

.toolbar input[type="button"]:not(.active):hover {
  background-color: #eee;
}

.toolbar input[type="button"].active {
  background-color: turquoise;
  border-color: transparent;
}

🎊 Another tutorial complete! Again, you can check out the finished product of this tutorial here.

I’m hoping next time to step away from some of the basics and jump into a post about an interesting problem I had to solve recently that required some more complex manipulation of content. So stay tuned for that if you’re finding these posts a little too easy for your skill level 🙂 But also, don’t worry! If you like the simplicity of these posts, I have some more beginner-friendly ideas up my sleeve too. 💕

Discussion (3)

Collapse
nkhil profile image
Nikhil Vijayan

Rose, I cannot thank you enough for this series. I've been going back and forth between Draft, Plaid, Quill and Medium Editor and finally settled on using Draft because of your really detailed tutorial series. Much appreciated!

BTW, In the last part, we ended with the editor just being a one-liner (i.e. a height of 10px or so), and in this one, the end result's height is about 150px. I found a solution to this here: github.com/facebook/draft-js/issue... in case anyone else comes across this.

Thanks again for the comprehensive write up!

Collapse
danaustinweb profile image
Daniel Austin

WoW!!!

Wonderfully Taught....

Collapse
rose profile image
Rose Author

Aw thank you!