Null of the increment for empty input does not work - javascript

const formSteps = [...multiStepForm.querySelectorAll("[data-step]")]
let currentStep = formSteps.findIndex(step => {
return step.classList.contains("active")
})
if (currentStep < 0) {
currentStep = 0
showCurrentStep()
}
multiStepForm.addEventListener("click", e => {
let incrementor
if (e.target.matches("[data-next]")) {
incrementor = 1
} else if (e.target.matches("[data-previous]")) {
incrementor = -1
}
if (incrementor == null) return
const inputs = [...formSteps[currentStep].querySelectorAll("input")]
const allValid = inputs.every(input => input.reportValidity())
if (allValid) {
currentStep += incrementor
showCurrentStep()
}
})
formSteps.forEach(step => {
step.addEventListener("animationend", e => {
formSteps[currentStep].classList.remove("hide")
e.target.classList.toggle("hide", !e.target.classList.contains("active"))
})
})
function showCurrentStep() {
formSteps.forEach((step, index) => {
step.classList.toggle("active", index === currentStep)
})
}
In this case of code, the validation of the data entry does not work and then the incriment for the other steps does not work. I also tried to add required in the inputs ignoring this code, the problem that making a "previous", the user does not have the possibility to go back blocked by the inserted required.
In the code, the incrementor object is already null, seen in log.console does not see it. I think the problem in this case is the querySelection as I noticed from the log.console that it takes data that is not of interest to me.

Related

When a key is pressed, the event occurs twice

When I press a key using keyEvent, then I call the function, it is below.
const GeneratedKey: FC<IGenerated> = (props) => {
const [keyBoard, setKeyBoard] = useState("")
const [arrayMovie, setArrayMovie] = useState<string[]>([])
const idPage = props.idpage
const nameMovie = data.results[idPage].title
const [idLetter, setIdLetter] = useState<number>(0)
const [indexArray, setIndexArray] = useState<number[]>([])
const arrayNameMovie = nameMovie.split(" ").join("").split("");
const getKey = (e: any) => {
console.log("test")
const key = e.key
let count = 0
for (let i = 65; i <= 90; i++) {
if (key.toUpperCase() == String.fromCharCode(i)) {
count = 1
} else if (key.toLowerCase() == "backspace") {
count = 10
}
}
if (count == 1) {
indexArray.sort(function (a: any, b: any) {
return a - b;
})
arrayMovie.splice(indexArray[idLetter], 1, key)
setIdLetter(idLetter + 1)
} else if (count == 10) {
if (idLetter >= 1) {
setIdLetter(idLetter - 1)
arrayMovie.splice(indexArray[idLetter], 1, "")
}
}
setKeyBoard(key)
document.removeEventListener("keydown", getKey);
}
useEffect(() => {
for (let i = 3; i >= 0; i--) {
const randomIndex = Math.floor(Math.random() * arrayNameMovie.length)
arrayNameMovie.splice(randomIndex, 1, " ")
indexArray.push(randomIndex)
}
setIdLetter(indexArray[0])
setArrayMovie(arrayNameMovie)
}, [])
document.addEventListener("keydown", getKey)
return (
<div className="down__word">
{arrayMovie.map((letter: any) =>
<LettersView letters={letter} props={undefined} />
)}
</div>
)
}
In fact, it should be called once, but it fires twice, as you can see from console.log();
How can I fix this, I can also show other files with code, but this is unlikely to help
This is due to your component may get rendered twice (due to change of props or any external reason). Moreover I think this is not the correct way to handle the event listener in FC. You should consider the useEffect for registration / un-registration of event listener.
useEffect(() => {
const handler = () => {};
document.addEventListener('keydown', handler);
return () => {
document.removeEventListener('keydown', handler);
}
}, [deps]); // if you have any changing dependency else just remove the array to execute the UseEffect everytime.
This will ensure that you have registered the event only once in your code.
Refer https://reactjs.org/docs/hooks-effect.html

Handling changes in single and multiple select/input in Vanilla JavaScript

I have a webpage with two select menus, one input, three buttons, and a checkbox. Everything is added and manipulated by JavaScript. On page page load, there is an object initiated with no values ( either value correspond either one of select or input ). You can select values and update it ( target is array of objects ) in array. Then, you can choose one of the buttons to copy selected options. Each object has three value (copying means that you copy the segment containing those values) times as many as you typed in input. After that, all objects are shown on screen. You can click a button to check all checkboxes and all objects change value. Value is updated in all selects of changed type and in the corresponding array.
Now my problems begin. When you unselect all checkboxes and want to change only 1 object all objects are updated and I have no idea why. There are conditions to check for each object if the checkbox is unchecked. If a single object is updated (either select or input) all of the other objects are updated ( none of checkbox is checked).
Here is github link to this project to check everything out without problems.
Also one more thing... whenever I update all values handleDOMChange function should fire automatically, but as you can see I have to fire it up manually so the changes are "visible" on my target array
const collectedData = [ {
cableType: "",
calbleLen_m: 0,
deviceType: ""
} ];
const completeData = {};
window.addEventListener('load', () => {
handleButtonEvents();
const segments = document.querySelectorAll('.installationSegment');
segments.forEach((segment, i) => {
handleInputAndSelectChange(segment, i);
});
const targetNode = document.getElementById("installationContainer");
targetNode.append(selectAllCheckboxesButton);
const config = {
childList: true,
subtree: true,
attributes: true,
characterData: true
};
const observer = new MutationObserver(handleDOMChange);
observer.observe(targetNode, config);
}
//Handling DOMChanges
const handleDOMChange = function() {
const powerSupplyElement = document.getElementById('powerSupply');
powerSupplyElement.addEventListener('change', e => completeData.supplyType = e.target.value);
const segments = document.querySelectorAll('.installationSegment');
if( segments.length >= 2 ) {
segments.forEach((segment, i) => {
const checkbox = segment.querySelector('input[type="checkbox"]');
if( !checkbox.checked ) {
//updating value for single change, i is index of element in array and none of checkboxes is checked.
handleInputAndSelectChange(segment, i);
} else if( checkbox.checked ) {
//updating value for every segment exisitng in DOM whose checkbox has been checked.
handleManySegmentsChange(segment);
}
});
}
completeData.bus = [ ...collectedData ];
console.table(completeData.bus);
}
//handling ButtonEvents
handleButtonEvents = function() {
const installationSegment = document.getElementById('installationContainer');
installationSegment.addEventListener('click', e => {
if( e.target.id.includes("Skopiuj") ) {
handleCopyNthTimes(e);
} else if( e.target.id.includes("Usun") ) {
handleDeleteDevice(e);
} else if( e.target.id === 'selectAllCheckboxes' ) {
checkAllCheckboxes(e);
}
});
}
//handling all checkboxes select and unselect
const checkAllCheckboxes = function() {
const segments = document.querySelectorAll('.installationSegment');
segments.forEach((segment, i) => {
const checkbox = segment.querySelector('input[type="checkbox"]');
checkbox.checked = !checkbox.checked;
handleDOMChange();
});
};
//handling single and multiple changes in select/input
handleManySegmentsChange = function(segment) {
segment.addEventListener('change', (e) => {
switch( e.target.name ) {
case 'cableSelect': {
const cableSelect = document.querySelectorAll('.cableSelect');
cableSelect.forEach(cable => cable.value = e.target.value);
collectedData.forEach(cable => cable.cableType = e.target.value);
handleDOMChange();
break;
}
case 'deviceSelect': {
const deviceSelect = document.querySelectorAll('.deviceSelect');
deviceSelect.forEach(device => device.value = e.target.value);
collectedData.forEach(device => device.deviceType = e.target.value);
handleDOMChange();
break;
}
case 'cableInput': {
const cableInput = document.querySelectorAll('input[name="cableInput"]');
cableInput.forEach(input => input.value = e.target.value);
collectedData.forEach(input => input.calbleLen_m = parseInt(e.target.value));
handleDOMChange();
break;
}
}
})
}
handleInputAndSelectChange = function(segment, index) {
segment.addEventListener('change', (event) => {
switch( event.target.name ) {
case 'cableSelect': {
collectedData[index].cableType = event.target.value;
break;
}
case 'deviceSelect': {
collectedData[index].deviceType = event.target.value;
break;
}
case 'cableInput': {
collectedData[index].calbleLen_m = parseInt(event.target.value);
break;
}
}
});
}
All right, I've found the answer. The reason it didn't work is that handleManySegmentsChange function is going to be fired in one circumstance - if user changes many select values at once. But this cannot be the case. My solution to the problem is to detect one change in segment, check for checkbox value, pass it to the function and depending on the checkbox value either update one value or all of them. Like so:
const segments = document.querySelectorAll('.installationSegment');
segments.forEach((segment, i) => {
segment.addEventListener('change', e => {
const checkbox = segment.querySelector('input[type="checkbox"]');
handleInputAndSelectChange(segment, e, i, checkbox.checked);
})
});
and then in function
handleInputAndSelectChange = function(segment, event, index, checked) {
switch( event.target.name ) {
case 'cableSelect': {
if( checked ) {
const cableSelect = document.querySelectorAll('.cableSelect');
cableSelect.forEach(cable => cable.value = event.target.value);
collectedData.forEach(data => data.cableType = event.target.value);
} else if( !checked ) {
collectedData[index].cableType = event.target.value;
}
break;
}
case 'deviceSelect': {
if( checked ) {
const deviceSelect = document.querySelectorAll('.deviceSelect');
deviceSelect.forEach(device => device.value = event.target.value);
collectedData.forEach(device => device.deviceType = event.target.value);
} else if( !checked ) {
collectedData[index].deviceType = event.target.value;
}
break;
}
case 'cableInput': {
if( checked ) {
const cableInput = document.querySelectorAll('input[name="cableInput"]');
cableInput.forEach(input => input.value = event.target.value);
collectedData.forEach(input => input.calbleLen_m = parseInt(event.target.value));
} else if( !checked ) {
collectedData[index].calbleLen_m = parseInt(event.target.value);
}
break;
}
}
}

Multiple elements -> one function while the elements do not colide with each other

I have multiple selectiontags on different picture slides but same page. Each slide has a set of selectiontags and I want users to only choose 1 selectiontag. I have written the code to do this but I wonder if there is another way.
So, basically I want:
Slide1 w. selectiontags1: Choose 1 selectiontag (out of 4)
Slide2 w.selectiontags2: Choose 1 selectiontag
Slide3 w. selectiontags3: Choose 1 selectiontag
Slide4 w. selectiontags4: Choose 1 selectiontag
This is my code so far.
var prevSelectedValue = null;
var prevSelectedValue2 = null;
var prevSelectedValue3 = null;
var prevSelectedValue4 = null;
$w.onReady(function () {
//TODO: write your page related code here...
let tags = $w('#selectionTags1');
if (tags.value.length === 1) {
prevSelectedValue = tags.value[0];
} else if (tags.value.length > 1) {
tags.value = [];
}
let tags2 = $w('#selectionTags2');
if (tags2.value.length === 1) {
prevSelectedValue2 = tags2.value[0];
} else if (tags2.value.length > 1) {
tags2.value = [];
}
let tags3 = $w('#selectionTags3');
if (tags3.value.length === 1) {
prevSelectedValue3 = tags3.value[0];
} else if (tags3.value.length > 1) {
tags3.value = [];
}
let tags4 = $w('#selectionTags4');
if (tags4.value.length === 1) {
prevSelectedValue4 = tags4.value[0];
} else if (tags4.value.length > 1) {
tags4.value = [];
}
});
export function selectionTags1_change(event) {
//Add your code for this event here:
if (!event.target.value || event.target.value.length === 0) {
event.target.value = [prevSelectedValue];
} else {
event.target.value = event.target.value.filter(x => x !== prevSelectedValue);
prevSelectedValue = event.target.value[0];
}
}
export function selectionTags2_change(event) {
//Add your code for this event here:
if (!event.target.value || event.target.value.length === 0) {
event.target.value = [prevSelectedValue2];
} else {
event.target.value = event.target.value.filter(x => x !== prevSelectedValue2);
prevSelectedValue2 = event.target.value[0];
}
}
export function selectionTags3_change(event) {
//Add your code for this event here:
if (!event.target.value || event.target.value.length === 0) {
event.target.value = [prevSelectedValue3];
} else {
event.target.value = event.target.value.filter(x => x !== prevSelectedValue3);
prevSelectedValue3 = event.target.value[0];
}
}
export function selectionTags4_change(event) {
//Add your code for this event here:
if (!event.target.value || event.target.value.length === 0) {
event.target.value = [prevSelectedValue4];
} else {
event.target.value = event.target.value.filter(x => x !== prevSelectedValue4);
prevSelectedValue4 = event.target.value[0];
}
}
Just a couple notes to help clean up the code:
It is better to use event.target.value[event.target.value.length-1] instead of .filter(). While it doesn't make a huge performance difference since our array is so small, we know that the most recently selected item is the last in the array, so using length-1 is more efficient code.
It is better to manipulate the selector and not the event, so I would recommend replacing event.target.value = event.target.value.filter... with $w('#selectionTags2') = event.target.value[..... This will also make your code more readable as you know which element is being updated.
If you are not preloading anything into the $w('#selectionTags') value array, I am not sure the code in your onReady function is necessary (unless I am missing a step where you do populate that selection value array).

React component shows empty data on first load, but renders properly after a refresh?

I'm just taking some data from accounts, chunking it into separate pages then displaying the first page by default. It begins empty, but after I refresh it works properly.
const Dashboard = ({ accounts }) => {
const [pageNum, setPageNum] = useState(0);
const pages = useMemo(() => {
const pages = [];
for (let i = 0; i < accounts.length; i += 8) {
pages.push(accounts.slice(i, i + 8));
}
return pages;
}, [accounts]);
const numOfPages = pages.length;
useEffect(() => {
getAccount();
}, []);
const onClick = (num) => {
if (num == -1 && pageNum == 0) {
//Do nothing
} else if (num == 1 && pageNum >= numOfPages - 1) {
//Do nothing
} else {
setPageNum(pageNum + num);
}
};
...
<Fragment>
{pages[pageNum] !== null &&
pages[pageNum] !== undefined &&
pages[pageNum].map((account, index) => (
<SavedPassword
key={index}
site={account.site}
login={account.login}
password={account.password}
/>
))}
</Fragment>
The useEffect hook with get accounts isn't actually required, I added that in hopes it would make it refresh or something. The webpage actually loads the getAccount action upon entering, regardless of what page is loaded. I've tried putting all that information into the getEffect hook, but that was actually a total nightmare. Any ideas of why it's doing this?

How I can show a loader while setTimeOut() function is processing

Need to show a loader wile setTimeOut() function is processing, without using states(react component's state).
When the function is processing the data till then the loader should be displayed on the screen after that the loader should be disappear automatically.
showData = () => {
if (!this.errorObject.isValid(this.getColHeader())) {
alert('Please correct the invalid cells and try again...')
} else {
setTimeout(() => {
const totalRows = this.hotTableComponent.current.hotInstance.countRows();
const { data :{ dimensions } } = this.props;
const nullString = dimensions.reduce((acc, currentValue) => acc + currentValue.delimiter, '');
// eslint-disable-next-line no-plusplus
for (let rowIndex = 0; rowIndex < totalRows; rowIndex++) {
const rowData = this.hotTableComponent.current.hotInstance.getDataAtRow(rowIndex)
rowData.pop();
const genStr = rowData.reduce((acc, currentValue, index) => {
const fieldData = dimensions[index].field.data;
if (fieldData.valueListType === "value" && fieldData.valueType === "undefined") {
if (fieldData.defaultValue) {
currentValue = (currentValue) || fieldData.defaultValue;
}
} else if (fieldData.valueListType === "codeValue" && currentValue) {
currentValue = currentValue.slice(currentValue.indexOf('(') + 1, currentValue.length - 1);
}
if (currentValue === null) {
currentValue = ' ';
}
return acc + currentValue + dimensions[index].delimiter;
}, '');
if (nullString !== genStr) {
this.updateCell(rowData, rowIndex, genStr);
}
}
}, 100);
}
}
If you really, and I mean really, don't want to use states you can achieve this by having the loading icon always be rendered but having an opacity of 0. Then before you call setTimeout you can use a ref to the loading icon to set it's opacity to 1. Then you can set it to 0 again when the setTimeout is executing.
I would not advice going this route though and instead to just use a state to indicate whether or not a component (such as a loading icon) should be shown.

Categories

Resources