Search code examples
javascriptreactjsdraftjs

Draft-JS - How to create a custom block with some non-editable text


In Draft-JS, I would like a basic custom block, rendering an <h1> element. I would like to add some text before my h1, that the user cannot edit. The text is here to inform people that this block is for Title. So I would like to add "TITLE" in front of the block that is not editable.

What is the best way to achieve this in Draft JS?


Solution

  • You can achieve your aim by applying contentEditable={false} and readOnly property on the node that should be read-only:

    class MyCustomBlock extends React.Component {
      constructor(props) {
        super(props);
      }
    
      render() {
        return (
          <div className="my-custom-block">
            <h1
              contentEditable={false} // <== !!!
              readOnly // <== !!!
            >
              Not editable title
            </h1>
            <div className="editable-area">
              <EditorBlock {...this.props} />
            </div>
          </div>
        );
      }
    }
    

    Check working demo in the hidden snippet below:

    const {Editor, CharacterMetadata, DefaultDraftBlockRenderMap, ContentBlock, EditorBlock, genKey, ContentState, EditorState} = Draft;
    const { List, Map, Repeat } = Immutable;
    
    class MyCustomBlock extends React.Component {
      constructor(props) {
        super(props);
      }
    
      render() {
        return (
          <div className="my-custom-block">
            <h1
              contentEditable={false}
              readOnly
            >
              Not editable title
            </h1>
            <div className="editable-area">
              <EditorBlock {...this.props} />
            </div>
          </div>
        );
      }
    }
    
    function blockRendererFn(contentBlock) {
      const type = contentBlock.getType();
      
      if (type === 'MyCustomBlock') {
        return {
          component: MyCustomBlock,
          props: {}
        };
      }
    }
    
    const RenderMap = new Map({
      MyCustomBlock: {
        element: 'div',
      }
    }).merge(DefaultDraftBlockRenderMap);
    
    const extendedBlockRenderMap = Draft.DefaultDraftBlockRenderMap.merge(RenderMap);
    
    class Container extends React.Component {
      constructor(props) {
        super(props);
    
      	this.state = {
          editorState: EditorState.createEmpty()
        };
      }
    
      _handleChange = (editorState) => {
        this.setState({ editorState });
      }
      
      _onAddCustomBlock = () => {
        const selection = this.state.editorState.getSelection();
        
      	this._handleChange(addNewBlockAt(
          this.state.editorState,
          selection.getAnchorKey(),
          'MyCustomBlock'
        ))
      }
    
      render() {
        return (
          <div>
            <div className="container-root">
              <Editor
                placeholder="Type"
                blockRenderMap={extendedBlockRenderMap}
                blockRendererFn={blockRendererFn}
                editorState={this.state.editorState}
                onChange={this._handleChange}
              />
              </div>
              <button onClick={this._onAddCustomBlock}>
                ADD CUSTOM BLOCK
              </button>
          </div>
        );
      }
    }
    
    ReactDOM.render(<Container />, document.getElementById('react-root'));
    
    const addNewBlockAt = (
      editorState,
      pivotBlockKey,
      newBlockType = 'unstyled',
      initialData = new Map({})
    ) => {
      const content = editorState.getCurrentContent();
      const blockMap = content.getBlockMap();
      const block = blockMap.get(pivotBlockKey);
    
      if (!block) {
        throw new Error(`The pivot key - ${ pivotBlockKey } is not present in blockMap.`);
      }
    
      const blocksBefore = blockMap.toSeq().takeUntil((v) => (v === block));
      const blocksAfter = blockMap.toSeq().skipUntil((v) => (v === block)).rest();
      const newBlockKey = genKey();
    
      const newBlock = new ContentBlock({
        key: newBlockKey,
        type: newBlockType,
        text: '',
        characterList: new List(),
        depth: 0,
        data: initialData,
      });
    
      const newBlockMap = blocksBefore.concat(
        [[pivotBlockKey, block], [newBlockKey, newBlock]],
        blocksAfter
      ).toOrderedMap();
    
      const selection = editorState.getSelection();
    
      const newContent = content.merge({
        blockMap: newBlockMap,
        selectionBefore: selection,
        selectionAfter: selection.merge({
          anchorKey: newBlockKey,
          anchorOffset: 0,
          focusKey: newBlockKey,
          focusOffset: 0,
          isBackward: false,
        }),
      });
    
      return EditorState.push(editorState, newContent, 'split-block');
    };
    body {
      font-family: Helvetica, sans-serif;
    }
    
    .container-root {
      border: 1px solid black;
      padding: 5px;
      margin: 5px;
    }
    
    .my-custom-block {
      background-color: cadetblue;
      margin: 15px 0;
      font-size: 16px;
      position: relative;
    }
    
    .editable-area {
      background-color: lightblue;
      height: 50px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.0/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.0/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/draft-js/0.10.0/Draft.js"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/draft-js/0.7.0/Draft.css" rel="stylesheet"/>
    <div id="react-root"></div>
    enter image description here