Let's suppose you want to limit the maximum length of text you allow users to enter in a form based on Editor.js.
Editor.js doesn't have such an option out of the box. Instead of this, it allows you to implement a wide kind of business logic using its API. The required behavior could be achieved using the onChange callback and some other API methods.
Idea
What we need to do:
- Add the onChange callback
- Get current editor data
- Calculate existed text length
- Trim a working block text if the limit is reached
Step 1. Add the onChange callback
const editor = new EditorJS({
// ... Other configuration properties ...
/**
* The onChange callback
*/
onChange: async (api, event) => {
// method will be fired on every editor change
}
})
Steps 2—3. Calculate the length of Editor.js text content
Next code fragments are parts of the onChange handler.
First, save the Editor to get the current content data.
const content = await api.saver.save()
Now we need to calculate the content length.
function couldBeCounted(block){
return 'text' in block.data // it depends on tools you use
}
function getBlocksTextLen(blocks){
return blocks
.filter(couldBeCounted)
.reduce((sum, block) => {
sum += block.data.text.length
return sum
}, 0)
}
const content = await api.saver.save()
const contentLen = getBlocksTextLen(content.blocks)
The couldBeCounted
method is used to filter only simple Blocks containing the text
property in data. And the getBlocksTextLen
method just iterates through all such Blocks and sums their text length.
If you need to support different kinds of Blocks, just provide your specific logic here. For example, to support the List Block, you need to loop over the items and calculate the overall length.
Step 4. Check limit and trim
The editor can contain several Blocks so user can enter text into any of them. We will trim the current working Block. To do that, we need to calculate the other Blocks` text lengths and then we'll find the allowed limit for the current Block.
const limit = 10
if (contentLen <= limit){
return;
}
const workingBlock = event.detail.target
const workingBlockSaved = content.blocks.filter(block => block.id === workingBlock.id).pop()
const otherBlocks = content.blocks.filter(block => block.id !== workingBlock.id)
const otherBlocksLen = getBlocksTextLen(otherBlocks)
const workingBlockLimit = limit - otherBlocksLen
We use the event.detail
to access the current Block. The "event" here is a CustomEvent passed to the onChange callback by the Editor.
Then, just trim the current Block text and update it using the Editor.js API:
api.blocks.update(workingBlock.id, {
text: workingBlockSaved.data.text.substr(0, workingBlockLimit)
})
And also we'll set the Caret to the end of the working Block to allow the user continuing modification of the Block.
const workingBlockIndex = event.detail.index
api.caret.setToBlock(workingBlockIndex, 'end')
The final result
const editor = new EditorJS({
// ... Other configuration properties ...
/**
* The onChange callback
*/
onChange: async (api, event) => {
// only count block modifications and skip events like 'block-added' etc
if (event.type !== 'block-changed'){
return;
}
function couldBeCounted(block){
return 'text' in block.data
}
function getBlocksTextLen(blocks){
return blocks
.filter(couldBeCounted)
.reduce((sum, block) => {
sum += block.data.text.length
return sum
}, 0)
}
const limit = 10
const content = await api.saver.save()
const contentLen = getBlocksTextLen(content.blocks)
if (contentLen <= limit){
return;
}
const workingBlock = event.detail.target
const workingBlockIndex = event.detail.index
const workingBlockSaved = content.blocks.filter(block => block.id === workingBlock.id).pop()
const otherBlocks = content.blocks.filter(block => block.id !== workingBlock.id)
const otherBlocksLen = getBlocksTextLen(otherBlocks)
const workingBlockLimit = limit - otherBlocksLen
api.blocks.update(workingBlock.id, {
text: workingBlockSaved.data.text.substr(0, workingBlockLimit)
})
api.caret.setToBlock(workingBlockIndex, 'end')
}
})
Don't forget to check the final length on the backend side as well.