.forEach validates each element but not the whole input? JS - javascript

I'm trying to validate a form input with multiple hashtags. I absolutely have to split the input and convert it to lowerCase.
ex) #sun #sea #summer #salt #sand
When I'm typing a hashtag that fails the validation, bubble message pops up and tells me it's wrong.
But if I type the next hashtag correctly, previous bubble message clears and the whole validation fails (form can be sent).
I'm assuming it has something to do with .forEach – possibly there are better solutions I'm not yet aware of.
I'm new to JS and would appreciate your answers very much.
// conditions for validation
const hashtagInput = document.querySelector('.text__hashtags');
const commentInput = document.querySelector('.text__description');
const testStartWith = (hashtag) => {
if (!hashtag.startsWith('#')) {
return 'hashtag should start with #';
}
return undefined;
};
const testShortValueLength = (hashtag) => {
if (hashtag.length === 1) {
return 'hashtag should have something after #';
}
return undefined;
};
const testValidity = (hashtag) => {
const regex = /^[A-Za-z0-9]+$/;
const isValid = regex.test(hashtag.split('#')[1]);
if (!isValid) {
return 'hashtag can't have spaces, symbols like #, #, $, etc, or punctuation marks';
}
return undefined;
};
const testLongValueLength = (hashtag) => {
if (hashtag.length > 20) {
return 'maximum hashtag length is 20 symbols';
}
return undefined;
};
const testUniqueName = (hashtagArray, index) => {
if (hashtagArray[index - 1] === hashtagArray[index]) {
return 'the same hashtag can't be used twice';
}
return undefined;
};
const testHashtagQuantity = (hashtagArray) => {
if (hashtagArray.length > 5) {
return 'only 5 hashtags for each photo';
}
return undefined;
};
const testCommentLength = (commentInput) => {
if (commentInput.value.length >= 140) {
return 'maximum comment length is 140 symbols';
}
return undefined;
};
const highlightErrorBackground = (element) => {
element.style.backgroundColor = '#FFDBDB';
};
const whitenBackground = (element) => {
element.style.backgroundColor = 'white';
};
Here is the validation at work
const testHashtagInput = () => {
const hashtagArray = hashtagInput.value.toLowerCase().split(' ');
hashtagArray.forEach((hashtag, index) => {
let error = testStartWith(hashtag)
|| testShortValueLength(hashtag)
|| testValidity(hashtag)
|| testLongValueLength(hashtag)
|| testUniqueName(hashtagArray, index)
|| testHashtagQuantity(hashtagArray);
if (error) {
highlightErrorBackground(hashtagInput);
hashtagInput.setCustomValidity(error);
} else {
whitenBackground(hashtagInput);
hashtagInput.setCustomValidity('');
}
hashtagInput.reportValidity();
});
if (hashtagInput.value === '') {
whitenBackground(hashtagInput);
hashtagInput.setCustomValidity('');
}
hashtagInput.reportValidity();
};
const testCommentInput = () => {
let error = testCommentLength(commentInput);
if (error) {
highlightErrorBackground(commentInput);
commentInput.setCustomValidity(error);
} else {
whitenBackground(commentInput);
commentInput.setCustomValidity('');
}
commentInput.reportValidity();
};
hashtagInput.addEventListener('input', testHashtagInput);

Yes your forEach reevaluates the entire validity of the input based on the individual items but only the last one actually remains in the end because it's the last one being evaluated.
You could change your evaluation to an array-reducer function; best fit for your intention is the reducer "some" (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some).
It iterates over the list of items and returns whether any one (or more) of the items fullfills the criterion formulated in your callback function.
I didn't get to test it now, but I guess this should do it:
let error = hashtagArray.some((hashtag, index) => {
return (testStartWith(hashtag)
|| testShortValueLength(hashtag)
|| testValidity(hashtag)
|| testLongValueLength(hashtag)
|| testUniqueName(hashtagArray, index)
|| testHashtagQuantity(hashtagArray));
});
if (error) {
highlightErrorBackground(hashtagInput);
hashtagInput.setCustomValidity(error);
} else {
whitenBackground(hashtagInput);
hashtagInput.setCustomValidity('');
}
hashtagInput.reportValidity();
HTH, cheers

Related

Discord anti nuke bot whitelist check error

i get error
let executor = await this.members.fetch(executorID);
^^^^^
SyntaxError: await is only valid in async function
when using the code below (use is to check if user breaks any of set filters and if so remove roles or ban user whatever they set option to)
ive tried my best to lable what parts of code does please not english isnt my first language
ive only recieved this error since trying to add a check whitelist feature - everything else works without the whitelist check code
without the code for whitelist the code works and performs as intended and the whitelist code succesfully logs ids for that guild
if(whitelisted && whitelisted.length) {
whitelisted.forEach(x => {
if (executorID === x.user) return;
const { Structures } = require('discord.js');
let whitelisted = db.get(`whitelist_${message.guild.id}`)
const { limits, defaultPrefix } = require('../config.js');
Structures.extend('Guild', Guild => {
class GuildExt extends Guild {
constructor(...args) {
super(...args);
}
get prefix() {
return this.get('prefix', defaultPrefix);
}
get(key, fallback) {
return this.client.db.get(`${this.id}_${key}`) || fallback;
}
set(key, data) {
return this.client.db.set(`${this.id}_${key}`, data);
}
delete(key) {
return this.client.db.delete(`${this.id}_${key}`);
}
resolveChannel(channelID) {
const channel = this.channels.cache.get(channelID);
return channel;
}
get limits() {
var obj = {};
for (var k in limits) {
obj[k] = {
minute: this.get(
`limits.${k}.minute`,
limits[k].per_minute
),
hour: this.get(`limits.${k}.hour`, limits[k].per_hour)
};
}
return obj;
}
getActions(limit = 10, filter = () => true) {
var obj = {};
var l = limits;
for (var k in limits) {
obj[k] = {
name: this.client.Utils.toProperCase(k),
actions: this.client.Utils.convertEntries(
[
...this.get(
this.client.Utils.convertLimitNameToActionType(
k
),
[]
),
...this.get(
`archive.${this.client.Utils.convertLimitNameToActionType(
k
)}`,
[]
)
]
.filter(filter)
.slice(0, limit)
)
};
}
return obj;
}
find_entry(action, filter) {
let guild = this;
return new Promise(resolve => {
(async function search(iter) {
//console.log(`ACTION = ${action} | ITER = ${iter}`);
if (!guild.me) return resolve(null);
if (guild.me.hasPermission('VIEW_AUDIT_LOG')) {
let logs = await guild.fetchAuditLogs({
limit: 10,
type: action
});
let entries = logs.entries;
let entry = null;
entries = entries.filter(filter);
for (var e of entries)
if (!entry || e[0] > entry.id) entry = e[1];
if (entry) return resolve(entry);
}
if (++iter === 5) return resolve(null);
else return setTimeout(search, 200, iter);
})(0);
});
}
push_entry(entry, displayName) {
const action = ['MEMBER_KICK', 'MEMBER_BAN_ADD'].includes(
entry.action
)
? 'MEMBER_REMOVE'
: entry.action;
const oneHourAgo = Date.now() - 1000 * 60 * 60;
// Fetch Entries for a sepcific action (Last Hour)
let entries = this.get(action, []);
// Filter entries older than one hour to a new variable
let olderThanOneHour = entries.filter(
i => !(i.timestamp > oneHourAgo)
);
// Prepend entries older than one hour to the archive
if (olderThanOneHour.length > 0)
this.set(`archive.${action}`, [
...olderThanOneHour,
...this.get(`archive.${action}`, [])
]);
// Filter entries older than one hour from old variable
entries = entries.filter(i => i.timestamp > oneHourAgo);
// Prepend new entry if not already found
if (
!entries.find(
i =>
i.target.id === entry.target.id &&
i.executor.id === entry.executor.id
)
)
entries.unshift({
timestamp: entry.createdTimestamp,
action: entry.action,
target: {
id: entry.target.id,
displayName,
targetType: entry.targetType
},
executor: {
id: entry.executor.id,
displayName: entry.executor.tag
}
});
// Update entries newer than one hour
return this.set(action, entries);
}
async check_limits(entries, executorID, configAction) {
// Ignore if executor is the owner or is whitelisted
if (executorID === this.ownerID) return;
if(whitelisted && whitelisted.length) {
whitelisted.forEach(x => {
if (executorID === x.user) retrun;
// Filter actions relating to executor
const oneMinuteAgo = Date.now() - 1000 * 60;
let executorActionsHour = entries.filter(
i => i.executor.id === executorID
);
let executorActionsMinute = executorActionsHour.filter(
i => i.timestamp > oneMinuteAgo
);
console.log(
`${configAction}/${executorID}: LAST_HOUR: ${executorActionsHour.length} LAST_MINUTE: ${executorActionsMinute.length} `
);
let limits = this.limits;
let limitReached = null;
if (executorActionsHour.length >= limits[configAction].hour)
limitReached = 'Hour';
if (executorActionsMinute.length >= limits[configAction].minute)
limitReached = 'Minute';
// Check if the amount of actions is greater than or equal to the limit
if (limitReached) {
// Remove all of the executor's roles
let executor = await this.members.fetch(executorID);
executor.roles.remove(executor.roles.cache);
// Handle managed roles
let managed = executor.roles.cache
.filter(r => r.managed)
.array();
for (var i = 0; i < managed.length; i++)
managed[i].setPermissions(0, 'Guardian Action');
// Notify owner, executor, and logging channel
const embed = this.client.util
.embed()
.setTitle(`Limit Reached - ${limitReached}`)
.setDescription(
this.client.Utils.convertEntries(
limitReached === 'Hour'
? executorActionsHour
: executorActionsMinute
)
)
.setColor(0x7289da);
await this.owner.send(
embed.setFooter(
"This message was sent to you because you're the Guild owner."
)
);
await executor.send(
embed.setFooter(
'This message was sent to you because you were the executor.'
)
);
const loggingChannel = this.resolveChannel(
this.get(`loggingChannelID`)
);
if (loggingChannel)
await loggingChannel.send(embed.setFooter(''));
}
})
}
}
}
return GuildExt;
});
i am new to JS and any help would be appreciated
please dont hate if i do have bad syntax !!
i am new - sorry if i dont get things the first time
You forgot to make your forEach function async, just change it to:
/* ... */
whitelisted.forEach(async (x) => {
/* ... */
let executor = await this.members.fetch(executorID);
/* ... */
}
/* ... */
Not part of your question but you misspelled return here
if (executorID === x.user) retrun;
Your line
let executor = await this.members.fetch(executorID);
is inside a non-async anonymous function in:
if (whitelisted && whitelisted.length) {
whitelisted.forEach(x => { // <- This line
if (executorID === x.user) return;
// ...
Try changing it with:
if (whitelisted && whitelisted.length) {
whitelisted.forEach(async (x) => { // <- Use async here
if (executorID === x.user) return;
// ...
Also, avoid using forEach to make asynchronous calls.

console.log and arithmatic operations does not work inside (addEventListener) javascript

i try to create typing test web application //error ///
this code i copied from github https://github.com/WebDevSimplified/JS-Speed-Typing-Game
i try to add timer when user press a key..
var err=0;
let sttime =0;
console.log(sttime);
quoteInputElement.addEventListener('input', () => {
err ++; /////does not work
console.log(sttime); ///does not work
console.log('jbjabj');
const arrayQuote = quoteDisplayElement.querySelectorAll('span');
const arrayValue = quoteInputElement.value.split('');
let correct = true;
arrayQuote.forEach((characterSpan, index) => {
const character = arrayValue[index];
if (character == null) {
characterSpan.classList.remove('correct');
characterSpan.classList.remove('incorrect');
correct = false
} else if (character === characterSpan.innerText) {
characterSpan.classList.add('correct');
characterSpan.classList.remove('incorrect');
} else {
characterSpan.classList.remove('correct');
characterSpan.classList.add('incorrect');
correct = false;
}
})
})
console.log(sttime);
if(sttime == 1){
startTimer();
}

How to add comma to alphabetic in JavaScript

I am trying to add a comma when user type number or alphabetic. Actually, I applied regex for it and it working for number. When user type number it add comma to every input, but when I type alphabetic it does not work. Could someone please help me how to accept regex to accept both number and alphabetic.
Thanks
Regex
value.replace(/\B(?=(\d{1})+(?!\d))/g, ",");
UPDATE
If you want to insert a comma, every time you hit enter, just repair the commas using the following epxression:
/ BEGIN
(?<=\w) POSITIVE LOOKBEHIND - PRECEDED BY WORD_CHAR
\b WORD BOUNDARY
[\s,]* ZERO OR MORE - WHITE_SPACE OR COMMA_CHAR
\b WORD BOUNDARY
/ END
g FLAGS = GLOBAL
const VK_ENTER = 13
const txt = document.querySelector('#sample-text')
const insertComma = (value) => value.replace(/(?<=\w)\b[\s,]*\b/g, ' , ')
const handleEnterKey = (e) => {
var code = e.keyCode ? e.keyCode : e.which
switch (code) {
case VK_ENTER:
txt.value = insertComma(txt.value)
break
}
}
document.addEventListener('keydown', handleEnterKey)
<form autocomplete="off" onsubmit="return false;">
<input type="text" id="sample-text" value="" />
</form>
And now for something completely different…
Note: Original answer
You can listen for text input changes and swap the value by removing the present commas and re-adding them in the correct places.
For this example, if you type: "Hello 12345", the text will eventually become: "Hello 12,345"
const TYPE_AHEAD_DELAY = 0 // No delay
const SAMLE_TEXT = "Hello / 12345 / World"
let state = {
sampleText: ''
}
const main = () => {
state = createState(state) // Wrap the state in a proxy
init()
autoType('sampleText', SAMLE_TEXT, 250)
}
const processValue = (value, model) => {
switch (model) {
case 'sampleText':
return fixCommas(value)
default:
return value
}
}
const fixCommas = (value) => value
.replace(/(\d),(\d)()/g, '$1$2') // Revert commas
.replace(/(\d)(?=(\d{3})+\b)/g, '$1,') // Add commas back
const autoType = (prop, value, timeout) => {
let pos = 0, interval = setInterval(() => {
state[prop] += value[pos++]
if (pos >= value.length) {
clearInterval(interval)
}
}, timeout)
}
/* #BEGIN BOILERPLATE */
const createState = (state) => {
return new Proxy(state, {
set(target, property, value) {
target[property] = processValue(value, property)
render(property)
return true
}
})
}
const init = () => {
Object.keys(state).forEach(property => {
const input = document.querySelector(`[data-model="${property}"]`)
let timeout
input.addEventListener('keyup', (e) => {
if (TYPE_AHEAD_DELAY) {
if (timeout) clearTimeout(timeout)
timeout = setTimeout(() => {
listener(e)
}, TYPE_AHEAD_DELAY)
} else {
listener(e)
}
})
})
}
const listener = (event) => {
const {type, value, dataset} = event.target
state[dataset.model] = value
}
const render = (property) => {
document.querySelector(`[data-model="${property}"]`).value = state[property]
}
main()
<input type="text" data-model="sampleText" />
You're matching any digit [0-9] with /d, if you change to any char it will work with any digit/letter.
Regex
"123abc".replace(/\B(?=(.{1})+(?!.))/g, ",");
var value = document.getElementById("original").innerText;
document.getElementById("modified").innerText = value.replace(/\B(?=(.{1})+(?!.))/g, ",");
var element = document.getElementById("inp");
element.addEventListener("keydown", function($event) {
if($event.code === 'Enter'){
$event.preventDefault();
element.value += ",";
}
});
<span id="original">123abc</span>
<span id="modified"></span>
<input type="text" id="inp">

Making "Fill in the Blank" in react

I am quite new to react and I am trying to make a fill in the blank app with react. Basically, I have a passage with a word list. I want to replace all occurrences each word with a blank so that the user can type in the answer. After that, if the user clicks the submit button, it displays the result saying how many they got right.
After doing some research, I found reactStringReplace package which can safely replace strings with react components. This is how I generate the blanks in the passage:
getFillInTheBlank() {
let passage = this.passage;
for (var i = 0; i < this.wordList.length; i++) {
let regexp = new RegExp("\\b(" + this.wordList[i] + ")\\b", "gi");
passage = reactStringReplace(passage, regexp, (match, i) => (
<input type="text"></input>
));
}
return <div>passage</div>
}
However, I can't figure out a way to check each input text with respective words to calculate the score when the submit button is clicked. Can anyone suggest a way of doing this? Thank you in advance.
I made it using Mobx, but it can be easily edited to work without this library.
This is the model, which contains the word to guess and the main events callbacks
word-guess.tsx
import { makeAutoObservable } from "mobx";
export class WordGuess {
private wordToGuess: string;
// Needed to select the next empty char when the component gain focus
private nextEmptyCharIndex = 0;
guessedChars: string[];
focusedCharIndex = -1;
constructor(wordToGuess: string) {
this.wordToGuess = wordToGuess;
// In "guessedChars" all chars except white spaces are replaced with empty strings
this.guessedChars = wordToGuess.split('').map(char => char === ' ' ? char : '');
makeAutoObservable(this);
}
onCharInput = (input: string) => {
this.guessedChars[this.focusedCharIndex] = input;
this.focusedCharIndex += 1;
if(this.nextEmptyCharIndex < this.focusedCharIndex){
this.nextEmptyCharIndex = this.focusedCharIndex;
}
};
onFocus = () => this.focusedCharIndex =
this.nextEmptyCharIndex >= this.wordToGuess.length ? 0 : this.nextEmptyCharIndex;
onFocusLost = () => this.focusedCharIndex = -1;
}
Input Component
guess-input.tsx
interface GuessInputProps {
wordGuess: WordGuess;
}
export const GuessInput = observer((props: GuessInputProps) => {
const { guessedChars, focusedCharIndex, onCharInput, onFocus, onFocusLost } =
props.wordGuess;
const containerRef = useRef(null);
const onClick = useCallback(() => {
const ref: any = containerRef?.current;
ref?.focus();
}, []);
useEffect(() => {
const onKeyDown = (params: KeyboardEvent) => {
const key = params.key;
if (focusedCharIndex >= 0 && key.length === 1 && key.match(/[A-zÀ-ú]/)) {
onCharInput(params.key);
// Clear focus when last character is inserted
if(focusedCharIndex === guessedChars.length - 1) {
const ref: any = containerRef?.current;
ref?.blur();
}
}
};
document.addEventListener('keydown', onKeyDown);
return () => {
document.removeEventListener('keydown', onKeyDown);
};
}, [focusedCharIndex, guessedChars]);
return <div className='guess-input'
onClick={onClick} ref={containerRef}
onFocus={onFocus} onBlur={onFocusLost} tabIndex={-1}>
{guessedChars.map((char, index) =>
<CharToGuess key={index} value={char} focused={index === focusedCharIndex} />)
}
</div>;
});
Component representing each one of the characters
char-to-guess.tsx
import './guess-input.scss';
interface CharToGuessProps {
value: string;
focused: boolean;
}
export const CharToGuess = (props: CharToGuessProps) => {
const { focused, value } = props;
return <span className={`char-to-guess ${focused ? ' focused-char' : ''}`}>
{value || '_'}
</span>;
};
You don't want to create blank strings.
Take a look at this code and see if you understand it.
var answer = document.getElementById('guess-input').name;
var hint = document.getElementById('guess-input').value;
function guessAnswer() {
$("button.guess-submit").click(function(event) {
var guess = $('#guess-input').val();
guess = guess.toLowerCase();
if ( guess == answer) {
$('#correct').show();
$('#wrong').hide();
} else {
$('#wrong').show().fadeOut(1000);
$('#guess-input').val(hint);
}
});
}
function enterSubmit() {
$("#guess-input").keyup(function(event){
if(event.keyCode == 13){
$("#guess-submit").click();
}
});
guessAnswer();
}
enterSubmit();
if ( $('#correct').css('display') == 'block') {
alert('hi');
}
I suggest to send a request to server with the questions and answers and return the results. If you save the points or the answers in the frontend, is possible that the game will be altered.

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;
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'))
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.
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
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';
}
}
Let's think about this for a second. What is called to make the changes? Your onChange, right? Good. We also know the length. Correct? We attact the "worker" which is the onChange:
const length = editorState.getCurrentContent().getPlainText('').length;
// Your onChange function:
onChange(editorState) {
const MAX_LENGTH = 10;
const length = editorState.getCurrentContent().getPlainText('').length;
if (length <= MAX_LENGTH) {
this.setState({ editorState }) // or this.setState({ editorState: editorState })
}
} else {
console.log(`Sorry, you've exceeded your limit of ${MAX_LENGTH}`)
}
I have not tried this but my 6th sense says it works just fine.

Categories