Draft-js styling a ContentBlock

This is the seventh in an ongoing series of posts about draft-js.

  1. draft-js pieces
  2. draft-js EditorState
  3. draft-js ContentState
  4. draft-js ContentBlock
  5. draft-js SelectionState
  6. draft-js highlight text
  7. draft-js styling a ContentBlock

In this post I’ll look at defining a custom ContentBlock.  As mentioned previously a ContentBlock is roughly analogous to a paragraph in your document.  The default ContentBlock type that you will get as you start typing is “unstyled”.  There are however, several other ContentBlock types provided out of the box.  A block type is comprised of just a name and an html  element (name) and optionally a React Component wrapper.  The complete list is here but it’s basically several different H[1-6] blocks, ul/ol, div, blockQuote and some others.

In order to use either a default block type or a custom one that you have defined you need to apply the type to a block.  There are a couple ways to do but this essentially they all look at SelectionState.  As I mentioned in the SelectionState post, if you don’t have any text currently selected, then the SelectionState is the location of the cursor relative to the beginning of the block.  So the startOffset might be 642 and the endOffset would be 642.  However you could select some text inside of a block or select some text in two or more blocks and all blocks touched by the selection would then have their block type set if you were to do so.

So to set the block type you would get your EditorState by catching the onChange event.  Then you could invoke either

import { RichUtils, Modifier } from 'draft-js'
const myEditorState 
    = RichUtils.toggleBlockType(myEditorState, "header-one")

or

const myContentState = myEditorState.getCurrentContent();
const myContentState 
    = Modifier.setBlockType(myContentState, mySelectionState, "header-one")

Note that the Modifier‘s function acts on the ContentState rather than the EditorState.  I’m not really sure why you must provide a SelectionState since you can get it off of the ContentState.  Perhaps you can persist SelectionState(s) and apply them out of context which would be cool.

Also just for clarity, RichUtils and Modifier are two helper libraries provided by draft-js which allow you to interact with pieces of the draft-js document.

So now you have a bock type of <h1> and that’s nice but what if you want to define your own block type?  Well, much like with inlineSelections, you would pass a blockRenderMap to a property by the same name in your Editor Component at design time.  As I mentioned above a block type is comprised of a name and an element.  So you might do

const blockRenderMap = Immutable.Map({
  'happyTimeBlock': {
   element: 'div'
  },
  'sadTimeBlock': {
    element: 'div'
  }
});

<Editor
  ...
  blockRenderMap={blockRenderMap}
  />

couple things to note.  You must use an Immutable.Map to define it.  Not a big deal if you don’t know the api, just use what’s above.  Also note that you can reuse the same element multiple times.  In fact there is a default block type of div ( “unstyled” ) so now we would have three.  What’s important is that you now have a name that you can use to hook css styles off of for the block.

So now we have some custom block types and we know how to apply them to different parts of our document.  For the default block styles this is actually useful because they have default styles applied to them.  But for our custom styles, they will render exactly the same as the “unstyled” type.  To apply a special style to our ‘happyTimeBlock’ and our ‘sadTimeBlock’ we would have to define a special function and again provide it to the Editor Component at design time.

function myBlockStyleFn(contentBlock) {
  const type = contentBlock.getType();
  switch (type) {
    case 'happyTimeBlock': {
        return 'myVeryHappyCSSFontStyleClass';
    }
    case 'sadTimeBlock': {
        return 'myVerySadCSSFontStyleClass';
    }
  }
}
// don't return a default from the switch because if it's not one of these
// we want to let draft-js do it's own thing.
 
<Editor 
    ...
    blockStyleFn={myBlockStyleFn} />;

Ok whats going on here?  Well, first we’re using a switch statement which seems to be ok these days.  I would prefer to use a strategy pattern, but maybe I’m stuck in the past.  Second when rendering draft-js will call this function on each block and if we return a string it will apply that string as a className to the block element which you can then reference in your own css file.

Now I do find it a bit weird that in this case we are applying  a class to an element, but in the case of inlineStyles we are applying … well, inlineStyles.  So I guess that’s not so weird but I have been asked before if we can define these style in a css file and in fact we can’t.  I’m sure there’s  a good reason for the two different approaches, but it’s still a pain.

Ok so to summarize or consolidate the code what we would have is the following

import { RichUtils, Modifier } from 'draft-js'

...
onChange = (editorState) => {
  const myEditorState 
    = RichUtils.toggleBlockType(editorState, "happyTimeBlock")
//or
//const myContentState = editorState.getCurrentContent();
//const myContentState = Modifier
   .setBlockType(myContentState, mySelectionState, "happyTimeBlock")
 this.setState({editorState: myEditorState});
}

blockRenderMap = Immutable.Map({
  'happyTimeBlock': {
   element: 'div'
  },
  'sadTimeBlock': {
    element: 'div'
  }
});

myBlockStyleFn = function(contentBlock) {
  const type = contentBlock.getType();
  switch (type) {
    case 'happyTimeBlock': {
        return 'myVeryHappyCSSFontStyleClass';
    }
    case 'sadTimeBlock': {
        return 'myVerySadCSSFontStyleClass';
    }
  }
}

<Editor 
    ...
    blockStyleFn={myBlockStyleFn} 
    blockRenderMap={blockRenderMap}
/>;

...

 

Before ending this post I want to mention something I alluded to above.  I guess I want to further allude to it.  When defining a custom block type, instead of just defining the element, you can define the element and a wrapper.  That wrapper being a React Component that you have full control over.  This is super cool and powerful.  You can have any kind of crazy functionality in there.  You can have onClick events that display modal dialogs, whatever.  It’s pretty cool.  You would just define your custom block as follows

 'happyTimeBlock': {
   element: 'div',
   wrapper: <Confetti {...props} />
  }

now your Confetti Component will wrap the entire block and who knows what frivolities may ensue.

Advertisements
Draft-js styling a ContentBlock

7 thoughts on “Draft-js styling a ContentBlock

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s