Search code examples
javascriptreactjsdraftjs

Set Custom Defined Type in DraftJS


I'm curious if we can define our own block type instead of using one from DRAFTBLOCKTYPE.

Currently I'm playing with draft-wysiwyg which uses plugin named draft-image-plugin. The problem is that I've to pass the block-image as the type of the block instead of atomic to make the plugin working.

Actually, I had tried to use the solution from this where I override the plugin's type to atomic. But it affects other blocks with atomic type on the application where I can't create my own blockRendererFn since the blockRenderer is 'swallowed' by that plugin's blockRenderer.

To set the block type to atomic, I can easily achieved it by:

AtomicBlockUtils.insertAtomicBlock(
  editorState,
  entityKey,
  ' '
)

How to set the block type to any custom defined type such as block-image or block-table? Is that even possible?


Solution

  • Yes, that's possible, and you have a few different options. Here are some I know of:

    1. If you have control over the component that renders blocks of type atomic, it would probably be easiest to add your new type as an entity to those blocks.

    2. If that's not an option, it get's a bit more cumbersome. AtomicBlockUtils is actually just a module made to help people create media (atomic) blocks easier (even though more utility functions will probably be added in the future). If you want the exact same behavior, but with a different type, you could copy that module and just exchange 'atomic' with something else (e.g. 'block-image' or a variable to make it more generic/resuable).

      The technique they use is basically to create a selection of an empty block, and then use the Modifier.setBlockType() function to give it a new block type:

    const asAtomicBlock = DraftModifier.setBlockType(
        afterSplit, // ContentState
        insertionTarget, // SelectionState
        'atomic' // your custom type
    );
    
    1. In this example, the author has created his own version, called addNewBlock() (it doesn't work exactly like the one in AtomicBlockUtils though):
    /*
    Adds a new block (currently replaces an empty block) at the current cursor position
    of the given `newType`.
    */
    const addNewBlock = (editorState, newType = Block.UNSTYLED, initialData = {}) => {
      const selectionState = editorState.getSelection();
      if (!selectionState.isCollapsed()) {
        return editorState;
      }
      const contentState = editorState.getCurrentContent();
      const key = selectionState.getStartKey();
      const blockMap = contentState.getBlockMap();
      const currentBlock = getCurrentBlock(editorState);
      if (!currentBlock) {
        return editorState;
      }
      if (currentBlock.getLength() === 0) {
        if (currentBlock.getType() === newType) {
          return editorState;
        }
        const newBlock = currentBlock.merge({
          type: newType,
          data: getDefaultBlockData(newType, initialData),
        });
        const newContentState = contentState.merge({
          blockMap: blockMap.set(key, newBlock),
          selectionAfter: selectionState,
        });
        return EditorState.push(editorState, newContentState, 'change-block-type');
      }
      return editorState;
    };
    

    So if you want to e.g. create a block of type 'block-image', with a src attribute, you can use this function like so:

    const newEditorState = addNewBlock(this.state.editorState, 'block-image', { src: 'https://...' })    
    this.setState({ editorState: newEditorState })
    

    Update: If you add a new type, you also need to add it to your editor's blockRenderMap:

    import { Map } from 'immutable'
    
    <Editor 
      // editor props
      blockRenderMap={Map({
        ['unstyled']: {
          element: 'div'
        },
        ['block-image']: {
          element: 'div' // or whatever element you want as a wrapper
        },
        // all your other block types
      })}
    />