How to triggter validation in wizard when cliking on Next button? - javascript

In Laravel 8 app which uses vuejs and jquery
I found wizard made with html like :
<div class="content__inner">
<div class="ccontainer overflow-hiddenn">
<!--multisteps-form-->
<div class="multisteps-form">
<div class="multisteps-form__progress">
<button
class="multisteps-form__progress-btn js-active"
type="button"
title="Add Project Info"
>
Project Info
</button>
<button
class="multisteps-form__progress-btn"
type="button"
title="Add Product"
>
Product Details 22222
</button>
<button
class="multisteps-form__progress-btn"
type="button"
title="Project Budget & Diagram"
>
Project Budget & Diagram
</button>
<button
class="multisteps-form__progress-btn"
type="button"
title="Video & Website Link"
>
Video & Website Link
</button>
</div>
<form class="multisteps-form__form">
<!--single form panel-->
<div
class="multisteps-form__panel shadow p-4 rounded bg-white js-active"
data-animation="scaleIn"
>
<!-- <h3 class="multisteps-form__title">Your User Info</h3> -->
<div class="multisteps-form__content">
<div class="row">
<div class="col-xl-4">
<div class="submit-field">
<h5>
Project Name
<span>*</span>
</h5>
<input
type="text"
class="with-border"
id="name_project"
v-model="project.name"
/>
</div>
</div>
<div class="col-xl-4">
<div class="submit-field">
<h5>
Choose Categories
<span>*</span>
</h5>
<b-form-select
class="tzselectpicker"
v-model="project.category"
:options="project_category"
></b-form-select>
</div>
</div>
and inited js function
setStepForm() {
//DOM elements
const DOMstrings = {
stepsBtnClass: "multisteps-form__progress-btn",
stepsBtns: document.querySelectorAll(
`.multisteps-form__progress-btn`
),
stepsBar: document.querySelector(".multisteps-form__progress"),
stepsForm: document.querySelector(".multisteps-form__form"),
stepsFormTextareas: document.querySelectorAll(
".multisteps-form__textarea"
),
stepFormPanelClass: "multisteps-form__panel",
stepFormPanels: document.querySelectorAll(
".multisteps-form__panel"
),
stepPrevBtnClass: "js-btn-prev",
stepNextBtnClass: "js-btn-next"
};
console.log(" setStepForm DOMstrings::");
console.log(DOMstrings);
//remove class from a set of items
const removeClasses = (elemSet, className) => {
elemSet.forEach(elem => {
elem.classList.remove(className);
});
};
//return exect parent node of the element
const findParent = (elem, parentClass) => {
let currentNode = elem;
while (!currentNode.classList.contains(parentClass)) {
currentNode = currentNode.parentNode;
}
return currentNode;
};
//get active button step number
const getActiveStep = elem => {
return Array.from(DOMstrings.stepsBtns).indexOf(elem);
};
//set all steps before clicked (and clicked too) to active
const setActiveStep = activeStepNum => {
//remove active state from all the state
removeClasses(DOMstrings.stepsBtns, "js-active");
//set picked items to active
DOMstrings.stepsBtns.forEach((elem, index) => {
if (index <= activeStepNum) {
elem.classList.add("js-active");
}
});
};
//get active panel
const getActivePanel = () => {
let activePanel;
DOMstrings.stepFormPanels.forEach(elem => {
if (elem.classList.contains("js-active")) {
activePanel = elem;
}
});
return activePanel;
};
//open active panel (and close unactive panels)
const setActivePanel = activePanelNum => {
//remove active class from all the panels
removeClasses(DOMstrings.stepFormPanels, "js-active");
//show active panel
DOMstrings.stepFormPanels.forEach((elem, index) => {
if (index === activePanelNum) {
elem.classList.add("js-active");
setFormHeight(elem);
}
});
};
//set form height equal to current panel height
const formHeight = activePanel => {
const activePanelHeight = activePanel.offsetHeight;
DOMstrings.stepsForm.style.height = `${activePanelHeight}px`;
};
const setFormHeight = () => {
const activePanel = getActivePanel();
formHeight(activePanel);
};
//STEPS BAR CLICK FUNCTION
DOMstrings.stepsBar.addEventListener("click", e => {
//check if click target is a step button
const eventTarget = e.target;
if (
!eventTarget.classList.contains(
`${DOMstrings.stepsBtnClass}`
)
) {
return;
}
//get active button step number
const activeStep = getActiveStep(eventTarget);
//set all steps before clicked (and clicked too) to active
setActiveStep(activeStep);
//open active panel
setActivePanel(activeStep);
});
//PREV/NEXT BTNS CLICK
DOMstrings.stepsForm.addEventListener("click", e => {
const eventTarget = e.target;
//check if we clicked on `PREV` or NEXT` buttons
if (
!(
eventTarget.classList.contains(
`${DOMstrings.stepPrevBtnClass}`
) ||
eventTarget.classList.contains(
`${DOMstrings.stepNextBtnClass}`
)
)
) {
return;
}
//find active panel
const activePanel = findParent(
eventTarget,
`${DOMstrings.stepFormPanelClass}`
);
let activePanelNum = Array.from(
DOMstrings.stepFormPanels
).indexOf(activePanel);
//set active step and active panel onclick
if (
eventTarget.classList.contains(
`${DOMstrings.stepPrevBtnClass}`
)
) {
activePanelNum--;
} else {
activePanelNum++;
}
setActiveStep(activePanelNum);
setActivePanel(activePanelNum);
setTimeout(() => {
var body = $(".dashboard-content-container");
body.stop().animate(
{ scrollTop: 0 },
500,
"swing",
function() {}
);
}, 100);
});
setTimeout(() => {
setFormHeight();
}, 500);
//SETTING PROPER FORM HEIGHT ONLOAD
window.addEventListener("load", setFormHeight, false);
//SETTING PROPER FORM HEIGHT ONRESIZE
window.addEventListener("resize", setFormHeight, false);
//changing animation via animation select !!!YOU DON'T NEED THIS CODE (if you want to change animation type, just change form panels data-attr)
const setAnimationType = newType => {
DOMstrings.stepFormPanels.forEach(elem => {
elem.dataset.animation = newType;
});
};
//selector onchange - changing animation
const animationSelect = document.querySelector(
".pick-animation__select"
);
if (animationSelect != null) {
animationSelect.addEventListener("change", () => {
const newAnimationType = animationSelect.value;
setAnimationType(newAnimationType);
});
}
},
By clicking on "Next" button next step is opened, but I need to validate inputs before moving to next step.
Please any hints how can I make validation here ?
Also is it some library? I searched in net but drowned...
Thanks!

Each wizard view send wizard position in ajax.In backend based on wizard position validate fields .since you know which and all field exist in each wizard view.
For example consider you have 3 wizard steps.
Step 1 has 3 input fields
Step 2 has 2 input fields
Step 3 has 1 input fields
Suppose if you send current step is 1 then you can only validate those fields.
$validations=[
'step1'=>[
//field validation array
],
'step3'=>[
//field validation array
],
'step3'=>[
//field validation array
],
]
then based on request wizard step you can easily fetch validation rules
Validation::make($validations[$request->step]);
Or you can make all fields validate only if exists in the request

Related

Unable to Deselect the Previous item, when clicked on another Item using Reactjs

I have list of items and I am changing the background color when I clicked on that item...
I need to change only the current Selected Item only , Previous selected Item should deselect... Now here, all items are getting changed when I clicked on each item... How can I solve this?? Can anyone help me in this ??
Here is my code
const Time = () => {
const [selectedItem, setSelectedItem] = React.useState();
const handleSelect = (event,selectedItem) => {
setSelectedItem(selectedItem);
event.target.style.backgroundColor = 'black';
event.target.style.color = 'white';
};
const dataTime = useMemo(() => TimeData, []);
const chunks = useMemo(() => {
const _chunks = [];
const tmp = [...dataTime];
while (tmp.length) {
_chunks.push(tmp.splice(0, 3));
}
//console.log(_chunks);
return _chunks;
}, [dataTime]);
return (
<div className="Main-Section">
<div className="First-Section">Time</div>
<div className="Second-Section">
<div className="date_title">Wednesday , June 13</div>
<div className="time_Slots">
{chunks.map((timeslot) => {
return (
<div key={timeslot} className="inline">
{timeslot.map((time) => {
return <p className='timeslots target' key={time}
onClick={(event) => handleSelect(event,time)}>{time}</p>;
})}
</div>
);
})}
</div>
<div>{selectedItem}</div>
<div className='new_Date'>PICK A NEW DATE</div>
</div>
</div>
);
};
U can struct Ur time data to an object like this...
{text:"",selected:false}
and use selected property flag in data then assign a class conditionally into items based on that flag. then in clicking iterate Ur list and make all the flags false except the selected one.
function selectFn(selectedItem) {
data.forEach((item) => {
item.selected = false;
});
selectedItem.selected = true;
}
and UR template be some
...
{chunks.map((timeslot) => {
return (
<div key={timeslot} className="inline">
{timeslot.map((time) => {
return <p className={`timeslots target ${time.selected ? "selectedClass" : ""}`} key={time.text}
onClick={(event) => selectFn(time)}>{time.text}</p>;
})}
</div>
);
})}
...
the css stuff..
.selectedClass{
background:black;
color:white;
}
you have to first change color for all the element to default color. Then you have to change the current element color to desired color.

Javascript: how to display certain amount of items from an array and display others on button click?

Implementation:
I have an HTML page with items container:
<section class="products">
<div class="container">
<h2 class="products-title">Some title</h2>
<div class="products-items"></div>
<button class="products-btn">Show more</button>
</div>
</section>
I have a data.js file with an array of items (16 items), here is an example:
export const products = [
{
id: 0,
name: 'Product 1',
price: 23,
category: 'Category 1',
imgSrc: './images/product-photo.jpg',
},
];
I parsed data.js file using .map() to populate 'products-items' div:
const displayProducts = products => {
const productsContainer = document.querySelector('.products-items');
const newProducts = products.map(product => {
const {
id,
name,
price,
category,
imgSrc,
} = product;
return `
<div class="product-item" data-id="${id}">
<p>${name}</p>
<p>${price}</p>
<p>${category}</p>
<img src="${imgSrc}" alt="product photo">
</div>
`;
}).join('');
productsContainer.innerHTML = newProducts;
};
export default displayProducts;
In app.js I've imported products variable and displayProducts function.
If I pass products variable to display products like this displayProducts(products), it will show all 16 items.
Desired result:
What I need is to show only first 4 items and load 4 new items each time user clicks 'Show more' button. In the end all 16 items should be displayed and 'Show more' button should be hidden.
When I had all 16 items as static data in HTML. I used CSS to hide items by default: .product-item {display: none}
Then I added class 'product-item--active' to first 4 items to display them by default: .product-item--active {display: block} and used this functionality to add active class for remaining items on button click:
const showMoreBtn = document.querySelector('.products-btn');
let currentItems = 4;
showMoreBtn.addEventListener('click', e => {
const elementList = [
...document.querySelectorAll('.products-items .product-item'),
];
for (let i = currentItems; i < currentItems + 4; i++) {
if (elementList[i]) {
elementList[i].classList.add('product-item--active');
}
}
currentItems += 4;
// Hide load more button after all items were loaded
if (currentItems >= elementList.length) {
e.target.style.display = 'none';
}
});
Issue:
But now, when items load dynamically, this functionality does not work.
I figured out how to display first 4 items using .slice():
let firstItems = products.slice(0, 4);
displayProducts(firstItems);
But, I can't figure out how to load new items on button click and hide it once all item displayed.
Update (Solved): I appreciate provided answers with good explanation and examples. Thank you.
After examining them I have the following solution (in case if someone may find it useful). Also, I removed redundant
'product-item--active' class from CSS, now there is no need to hide items by default:
import { products } from './data.js';
import displayProducts from './components/displayProducts.js';
const showMoreBtn = document.querySelector('.products-btn');
let currentItems = 0;
const displayNextFour = () => {
displayProducts(products.slice(currentItems, currentItems + 4));
// Display next 4 items until their amount exceeds
// the array length
if (!(currentItems + 4 > products.length)) {
currentItems += 4;
}
// Remove event listener from 'Show more' button and
// hide it after all items from the array are displayed
if (currentItems === products.length) {
showMoreBtn.removeEventListener('click', displayNextFour);
showMoreBtn.style.display = 'none';
}
};
displayNextFour();
showMoreBtn.addEventListener('click', displayNextFour);
Feel free to play with this minimal reproducable example using an array of 16 random strings for the content to display. It uses insertAdjacentHTML to append a block of 4 items on button click. That prevents overwriting all html on every click. The click handler is assigned using Event Delegation. If the number of items shown equals the available items, the button is disabled.
A second idea may be to hide all but [pageSize] items and on button click unhide the next [pageSize] items. See this stackblitz snippet. That snippet is more generic: it enables a variable amount of items and setting a page size (number of items to show subsequently). It requires no extra variables for tracking.
initRandomStrExt();
document.addEventListener(`click`, handle);
const fakeArray = [...Array(16)].map(v => String.getRandom(32));
// show first 4 items on page load
addFourItems(fakeArray);
function handle(evt) {
if (evt.target.classList.contains(`products-btn`)) {
return addFourItems(fakeArray);
}
}
function addFourItems(fromArr) {
// determine n of currently visible items
const start = document.querySelectorAll(`.product-item`).length;
// disable the button if all items will be visible after running this
if (start === fromArr.length - 4) {
document.querySelector(`.products-btn`).setAttribute(`disabled`, true);
}
// append 4 items of the array to the .product-items container
document.querySelector(`.products-items`)
.insertAdjacentHTML(`beforeend`,
`<div>${fromArr.slice(start, start + 4)
// ^ slice the next 4 items from the array
.map((item, i) => `<div class="product-item">${i + 1 + start} - ${
item}</div>`).join(``)}</div>`);
}
// for demo, random string helper
function initRandomStrExt() {
if (String.getRandom) {
return;
}
const characters = [...Array(26)]
.map((x, i) => String.fromCharCode(i + 65))
.concat([...Array(26)].map((x, i) => String.fromCharCode(i + 97)))
.concat([...Array(10)].map((x, i) => `${i}`));
const getCharacters = excludes =>
excludes && characters.filter(c => !~excludes.indexOf(c)) || characters;
String.getRandom = (len = 12, excludes = []) => {
const chars = getCharacters(excludes);
return [...Array(len)]
.map(() => chars[Math.floor(Math.random() * chars.length)])
.join("");
};
};
<section class="products">
<div class="container">
<h2 class="products-title">Some title</h2>
<div class="products-items"></div>
<p><button class="products-btn">Show more</button></p>
</div>
You can create a global variable to keep track of which items should be dispalyed e.g. current_index. Then create a function e.g. displayNextFour() to display four items each time and update current_index accordingly. Then on button click call displayNextFour() function.
let current_index = 0
const displayNextFour = () => {
displayProducts(products.slice(current_index, current_index+4));
//console.log(products.slice(current_index, current_index+4))
if(current_index + 4 <= products.length)
current_index+=4
}
(In displayProducts create elements with product-item--active class)

Edit option is opened in every row when clicked

I am working on an application , where I've to show Edit Popup when click on icon . Now it working fine but problem is whenever I click on row it showing in each individual rows. I want to show single popup with each row not multiple . Now if I click on 5 rows popup showing 5 times I want to show single popup in each individual row
const editToggle = (index) => {
if (index === id) {
setShowModal(true)
}
}
{showModal && (
<div className="card-container">
<div className="container_separator card-container-text">Edit</div>
<div className="container_separator card-container-text">Delete</div>
</div>
)}
Set the id of the row you wish to open in the showModal state, and open the modal if the showModal value is identical to the id:
const [showModal, setShowModal] = useState(-1)
const editToggle = (index) => {
if (index === id) {
setShowModal(id)
}
}
{showModal === id && (
<div className="card-container">
<div className="container_separator card-container-text">Edit</div>
<div className="container_separator card-container-text">Delete</div>
</div>
)}

React - change state of one button from 100 buttons array, and reset state of all buttons on one click

So, I have 100 mapped buttons from array, something like that
{
buttons.map((button, index) =>
<StyledGameButton
key={index}
value={button}
onClick={this.checkButton}>
{ button < 10 ? `0${button}` : button }
</StyledGameButton>
)
}
I want user to click all the buttons from 0 to 99, so when he click for example 0 the button should change color. I made function checking if he clicked correct button, and if yes then I am addind data-attr to that button (thats how I am changing the color of buttons):
checkButton = e => {
const buttonId = e.currentTarget.getAttribute('value');
const nextButton = this.state.currentButton === null ? 0 : this.state.currentButton + 1;
if (buttonId == nextButton){
this.setState({
currentButton: parseInt(buttonId),
});
if (this.state.currentButton === 99) {
this.gameEnd();
};
e.currentTarget.setAttribute('data-status', 'correct');
}
}
The problem is that I want to make reset button, that will change all buttons to 'unclicked' so I have to delete data-attr from all buttons on one click. How can I do this? Or is there a better solution to manage 'state' of single button without making 100 states?
100 checkboxes demo
use an Array to store the state would be fine.
const list = [...Array(100).keys()].map(x => ({ id: x }));
const App = () => {
const [selected, setSelected] = React.useState([]); // init with empty list
const onChangeHandler = id => () => { // pass index/identify params
selected.includes(id) // check whether been checked
? setSelected(selected.filter(x => x !== id)) // yes, remove
: setSelected([...selected, id]); // no, add
};
const onRemove = () => {
setSelected([]); // remove all, set to empty list
};
return (
<div className="App">
{list.map(item => (
<input
type="checkbox"
key={item.id}
checked={selected.includes(item.id)}
onChange={onChangeHandler(item.id)}
/>
))}
<br />
<button onClick={onRemove}>Remove all</button>
<div>{selected.join(",")}</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

Automatically scroll down to next listgroup item and display the content in center in mobile view (React)

1) I am trying to Auto scroll to the next item in listgroup. For example if user answer the first question it should auto scroll to the second question. (React) and onSubmit it should scroll to the first not answered question
2) When user view this list in mobile view the YES or NO Radio button should display in center and also SUBMIT AND CLEAR BUTTON (BOOTSTRAP)
3) How to know which item is selected from the drop down and display it in console.
Code
There are a number of ways this can be achieved. One way would be to add a method that scrolls to an item in your form, via "vanilla js", and then use that in both your onInputChanged on onSubmut methods.
You could defined this function in your component as:
// Scrolls the list to a list item by list item index
scrollToItemByIndex = (index) => {
// Find the list item element (by index), and scroll wrapper element
const scrollItem = document.querySelector(`[scrollIndex="${ (index) }"]`)
const scrollWrapper = document.querySelector(`[scrollWrapper="scrollWrapper"]`)
if(scrollItem && scrollWrapper) {
// If list item found in DOM, get the top offset
const itemRect = scrollItem.offsetTop // [UPDATED]
const wrapperRect = scrollWrapper.offsetTop // [UPDATED]
// Scroll the wrapper to the offset of the list item we're scrolling to
scrollWrapper.scrollTo(0, itemRect - wrapperRect)
}
}
You onInputChange function could then be updated as follows:
onInputChange = ({ target }) => {
const { cards } = this.state;
const { options } = this.state;
const nexState = cards.map((card, index) => {
if (card.cardName !== target.name) return card;
const options = card.options.map(opt => {
const checked = opt.radioName === target.value;
return {
...opt,
selected: checked
}
})
// [ADD] When input changes (ie something is set), scroll to next item
this.scrollToItemByIndex( index + 1 )
const style = options.every(option => !option.selected) ? 'danger' : 'info'
return {
...card,
style,
options
}
});
this.setState({ cards: nexState })
}
Also, your onSubmit would be updated to scroll to any form items that are not valid:
onSubmit = () => {
this.state.cards.forEach((card, index) => {
var invalid = card.options.every(option => !option.selected)
if (invalid) {
card.style = 'danger'
// [ADD] this item has invalid input, so scroll to it
this.scrollToItemByIndex(index)
}
else {
card.style = 'info'
}
});
...
}
Finally, you'd need to update your component's render method with the following, to ensure that the query selectors above function correctly:
<ul class="nav nav-pills nav-stacked anyClass" scrollWrapper="scrollWrapper">
and:
{cards.map((card, idx) => (<ListGroup bsStyle="custom" scrollIndex={idx}>
...
</ ListGroup >)}
[UPDATED] A full working sample can be found here:
https://stackblitz.com/edit/react-z7nhgd?file=index.js
Hope this helps!

Categories