Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to limit Max Length of Draft js

How to limit max characters in draft js?

I can get length of the state like that, but how to stop updating component?

var length = editorState.getCurrentContent().getPlainText('').length;
like image 969
Firanolfind Avatar asked Sep 04 '17 21:09

Firanolfind


4 Answers

You should define handleBeforeInput and handlePastedText props. In handler-functions, you check the length of current content + length of pasted text and if it reaches the maximum you should return 'handled' string.

UPD 21.03.2018: Upgraded to the last versions of react/react-dom (16.2.0) and Draft.js (0.10.5).

Working example - https://jsfiddle.net/Ln1hads9/11/

const {Editor, EditorState} = Draft;

const MAX_LENGTH = 10;

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      editorState: EditorState.createEmpty()
    };
  }
  render() {
    return (
      <div className="container-root">
        <Editor
          placeholder="Type away :)"
          editorState={this.state.editorState}
          handleBeforeInput={this._handleBeforeInput}
          handlePastedText={this._handlePastedText}
          onChange={this._handleChange}
        />
      </div>
    );
  }

  _getLengthOfSelectedText = () => {
    const currentSelection = this.state.editorState.getSelection();
    const isCollapsed = currentSelection.isCollapsed();

    let length = 0;

    if (!isCollapsed) {
      const currentContent = this.state.editorState.getCurrentContent();
      const startKey = currentSelection.getStartKey();
      const endKey = currentSelection.getEndKey();
      const startBlock = currentContent.getBlockForKey(startKey);
      const isStartAndEndBlockAreTheSame = startKey === endKey;
      const startBlockTextLength = startBlock.getLength();
      const startSelectedTextLength = startBlockTextLength - currentSelection.getStartOffset();
      const endSelectedTextLength = currentSelection.getEndOffset();
      const keyAfterEnd = currentContent.getKeyAfter(endKey);
      console.log(currentSelection)
      if (isStartAndEndBlockAreTheSame) {
        length += currentSelection.getEndOffset() - currentSelection.getStartOffset();
      } else {
        let currentKey = startKey;

        while (currentKey && currentKey !== keyAfterEnd) {
          if (currentKey === startKey) {
            length += startSelectedTextLength + 1;
          } else if (currentKey === endKey) {
            length += endSelectedTextLength;
          } else {
            length += currentContent.getBlockForKey(currentKey).getLength() + 1;
          }

          currentKey = currentContent.getKeyAfter(currentKey);
        };
      }
    }

    return length;
  }

  _handleBeforeInput = () => {
    const currentContent = this.state.editorState.getCurrentContent();
    const currentContentLength = currentContent.getPlainText('').length;
    const selectedTextLength = this._getLengthOfSelectedText();

    if (currentContentLength - selectedTextLength > MAX_LENGTH - 1) {
      console.log('you can type max ten characters');

      return 'handled';
    }
  }

  _handlePastedText = (pastedText) => {
    const currentContent = this.state.editorState.getCurrentContent();
    const currentContentLength = currentContent.getPlainText('').length;
    const selectedTextLength = this._getLengthOfSelectedText();

    if (currentContentLength + pastedText.length - selectedTextLength > MAX_LENGTH) {
      console.log('you can type max ten characters');

      return 'handled';
    }
  }

  _handleChange = (editorState) => {
    this.setState({ editorState });
  }
}

ReactDOM.render(<Container />, document.getElementById('react-root'))
like image 178
Mikhail Shabrikov Avatar answered Nov 11 '22 06:11

Mikhail Shabrikov


Mikhail's methods are correct, but the handler return value is not. 'not_handled' is a fall-through case that allows the Editor component to process the input normally. In this case, we want to stop the Editor from processing input.

In older versions of DraftJS, it looks like the presence of a string evaluated to 'true' in the handling code, and so the above code behaved correctly. In later versions of DraftJS, the above fiddle doesn't work - I don't have the reputation to post more that one Fiddle here, but try Mikhail's code with v0.10 of DraftJS to replicate.

To correct this, return 'handled' or true when you don't want the Editor to continue handling the input.

Fiddle with corrected return values

For example,

_handleBeforeInput = () => {
  const currentContent = this.state.editorState.getCurrentContent();
  const currentContentLength = currentContent.getPlainText('').length

  if (currentContentLength > MAX_LENGTH - 1) {
    console.log('you can type max ten characters');
    return 'handled';
  }
}

See the DraftJS docs on Cancelable Handlers for more.

like image 27
JSH Avatar answered Nov 11 '22 07:11

JSH


This is a bit of an old thread, but thought I would share a solution for anyone else facing the problem of character limit and behaviour while pasting text...

Put together something pretty quickly based on the above code by Mikhail to handle this use case which works for me - although I haven't done any work on trying to optimise it.

Basically the handle pasted text looks like so:

      const __handlePastedText = (pastedText: any) => {
        const currentContent = editorState.getCurrentContent();
        const currentContentLength = currentContent.getPlainText('').length;
        const selectedTextLength = _getLengthOfSelectedText();
    
        if (currentContentLength + pastedText.length - selectedTextLength > MAX_LENGTH) {
            const selection = editorState.getSelection()
            const isCollapsed = selection.isCollapsed()
            const tempEditorState = !isCollapsed ? _removeSelection() : editorState
            _addPastedContent(pastedText, tempEditorState)
          return 'handled';
        }
        return 'not-handled'
      }

We have a helper function to handle the deletion of the selection prior to pasting new characters which returns the new editor state:

      const _removeSelection = () => {
        const selection = editorState.getSelection()
        const startKey = selection.getStartKey()
                const startOffset = selection.getStartOffset()
                const endKey = selection.getEndKey()
                const endOffset = selection.getEndOffset()
                if (startKey !== endKey || startOffset !== endOffset) {
                    const newContent = Modifier.removeRange(editorState.getCurrentContent(), selection, 'forward')
                    const tempEditorState = EditorState.push(
                        editorState,
                        newContent,
                        "remove-range"
                    )
                    setEditorState(
                        tempEditorState
                    )
                    return tempEditorState
                }
                return editorState
      }

and finally the function to add the pasted text with a limit:

      const _addPastedContent = (input: any, editorState: EditorState) => {
        const inputLength = editorState
        .getCurrentContent()
        .getPlainText().length;
        let remainingLength = MAX_LENGTH - inputLength;

        const newContent = Modifier.insertText(
            editorState.getCurrentContent(),
            editorState.getSelection(),
            input.slice(0,remainingLength)
        ); 
        setEditorState(
            EditorState.push(
                editorState,
                newContent,
                "insert-characters"
            )
        )
      }

Link to worked example: https://codesandbox.io/s/objective-bush-1h9x6

like image 38
Scott Waddell Avatar answered Nov 11 '22 07:11

Scott Waddell


As Mikhail mentioned, you need to handle typing and pasting text. Here are both handlers. Note the paste handler will preserve text that is not outside the limit

function handleBeforeInput(text: string, state: EditorState): DraftHandleValue {
    const totalLength = state.getCurrentContent().getPlainText().length + text.length;
    return totalLength > MAX_LENGTH ? 'handled' : 'not-handled';
  }

function handlePastedText(text: string, _: string, state: EditorState): DraftHandleValue {
    const overflowChars = text.length + state.getCurrentContent().getPlainText().length - MAX_LENGTH;

    if (overflowChars > 0) {
      if (text.length - overflowChars > 0) {
        const newContent = Modifier.insertText(
          state.getCurrentContent(),
          state.getSelection(),
          text.substring(0, text.length - overflowChars)
        );
        setEditorState(EditorState.push(state, newContent, 'insert-characters'));
      }
      return 'handled';
    } else {
      return 'not-handled';
    }
  }
like image 1
Alx Avatar answered Nov 11 '22 06:11

Alx