How to iterate through DOM elements in react and add/remove classes? - javascript

I am beginner to react and I am unable to iterate through div elements. Each of these div elements (shown in the code) have a common className="step" initially but once the button Next is clicked I want 1st div to have className="step current" .
Once Next is clicked again 1st div element should have className="step done"(remove current and append done) and 2nd div element would have classname="step current"
I was able to toggle the className from "step" to "step current" in the following code but I am facing difficulty to traverse the div elements and add or remove the class.
class NavBar extends React.Component{
state = {
addClass : false
}
handleClick=()=>{
this.setState({addClass: !this.state.addClass});
}
render(){
let arrowClass = ["step"];
if(this.state.addClass){
arrowClass.push("current");
}
return(
<div id="navbar-div">
<div className="arrow-steps clearfix">
1. <div className={arrowClass.join(' ')}>
<span> Step1</span>
</div>
2. <div className={arrowClass.join(' ')}>
<span>Step2</span>
</div>
3. <div className={arrowClass.join(' ')}>
<span> Step3</span>
</div>
4. <div className={arrowClass.join(' ')}>
<span>Step4</span>
</div>
5. <div className={arrowClass.join(' ')}>
<span>Step5</span>
</div>
</div>
<div className="nav clearfix">
Previous
<a href="#" className="next pull-right" onClick={this.handleClick}>Next</a>
</div>
</div>
);
}
}
ReactDOM.render(
<NavBar />,
document.getElementById("root")
);
.current {
font-weight: bold;
}
.done {
color: #aaa;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
The classes step, current and done are defined in the css file.

Rather than writing the steps explicitly, put them in an array, and then remember where you are within that array with an indexing variable. Here's the minimal-changes approach to doing that with your code (see comments):
class NavBar extends React.Component{
state = {
steps: ["Step1", "Step2", "Step3", "Step4"],
current: 0 // <== Step1 is the current step
}
prevClick = () => {
// Move to previous step, note we use the callback version of setState,
// see https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
this.setState(({current}) => ({current: Math.max(0, current - 1)}));
}
nextClick = () => {
// Move to next step
this.setState(({steps, current}) => ({current: Math.min(steps.length - 1, current + 1)}));
}
render() {
// Get the steps and the current index
const {current, steps} = this.state;
// Render them, checking the position of the step (`index`) relative to `current`
// and outputting the relevant class name.
// I changed your `div`s to an ordered list so we get automatic numbering
return (
<div id="navbar-div">
<div>{current}</div>
<ol className="arrow-steps clearfix">
{steps.map((step, index) =>
<li key={index} className={`step ${index < current ? 'done' : index == current ? 'current' : ''}`}>{step}</li>
)}
</ol>
<div className="nav clearfix">
<a href="#" className="prev" onClick={this.prevClick}>Previous</a>
<a href="#" className="next pull-right" onClick={this.nextClick}>Next</a>
</div>
</div>
);
}
}
ReactDOM.render(
<NavBar />,
document.getElementById("root")
);
.current {
font-weight: bold;
}
.done {
color: #aaa;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
Obviously, that just shows the basic approach, you'll want to modify it. If you have any information other than just the step text, you might make the array entries objects rather than just strings.
(Note: Using index as the key on those lis is only valid if you don't add/remove entries in the array of steps.)
Side note: As Murali Krishna pointed out, you had class rather than className on the div containing the previous/next links and on those links; I've changed those to className above.

Although you can do that but there's a more React'ish way. That is to use state to store the class you wanna add. So when you change the state (using setState) it will auto re-render and set the class.
Eg.
class NavBar extends React.Component{
state = {
status: "current"
}
handleClick=()=>{
this.setState(prevState=>{
if(prevState.status === 'current'){
return {status:"done"}
}else{
return {status:"current"}
}
})
}
render(){
return <button className={"step " + this.state.status} onClick={this.handleClick}>Hello</button>
}

Related

Svelte how to bind div inside each lop to obtain a reference using this

I need to get a reference to every div created inside a each loop in svelte, then I'll use the reference to toggle css class of a certain div when the user clicks on previous div.
let contentOptions;
function handleClick(event) {
contentOptions.classList.toggle("close");
}
{#each items as item, i}
<div class="titleOption" on:click={handleClick}>
<img src="./assets/{item.icon}"/>
<span>{item.label}</span>
</div>
<div class="content close" bind:this={contentOptions}>Content Option {i}</div>
{/each}
Items array have three objects, and it always appears the last div with text "Content Option 2" despite clicking on another div.
is possible to bind each div separately?
You can solve this by making contentOptions an array, bind with the index bind:this={contentOptions[i]} and use the index inside the function to target the right reference > REPL
<script>
const items = [{label: 'item1'}, {label: 'item2'}]
let contentOptions = [];
function handleClick(index) {
contentOptions[index].classList.toggle("close");
}
</script>
{#each items as item, i}
<div class="titleOption" on:click={() => handleClick(i)}>
<span>{item.label}</span>
</div>
<div class="content close" bind:this={contentOptions[i]}>Content Option {i}</div>
{/each}
<style>
.close {
background: red;
}
</style>
This would be an alternative way without the need of the extra array and handling binding and index REPL
<script>
const items = [{label: 'item1'}, {label: 'item2'}]
function handleClick(event) {
event.currentTarget.nextElementSibling.classList.toggle('close')
}
</script>
{#each items as item, i}
<div class="titleOption" on:click={handleClick}>
<span>{item.label}</span>
</div>
<div class="content close">Content Option {i}</div>
{/each}
<style>
.close {
background: red;
}
</style>

How do I change a specific image when a list class is active

I have website which is made with materialize. I am using a ul with three li elements which are all collapsible. On top of that is a card with an image. I want to change the image when a li element is active, respectively, expanded.
There were some similar question on here already, but none really fit my problem.
Following is an example layout of my code.
<div class="card">
<div class="card-image">
<img src="img/image-1.jpg"/>
</div>
<ul class="collapsible expandable">
<li class="active">
<div class="collapsible-header">
<div class="collabsible-body">
</li>
</ul>
</div>
The active class appears when you expand the respective li element. It disappears again if you close it.
image-1.jpg should change, corresponding to the li element when the respective li element is active.
Any help is greatly appreciated!
If you are the one toggling the class between active and collapsed, you could add additional code to your toggle function which can swap out the src of that image.
If not, you can use a Mutation Observer to fire a callback when the class of that DOM element has changed. You'll still have to update the src in response to the class change.
// will watch target element for changes to the 'class'
// attribute and fire the callback with an array of the
// class names when changed
const observeClassList = (element, callback) => {
const config = { attributes: true }
const mutationCallback = mutationsList => {
mutationsList.forEach(mutation => {
if (mutation.attributeName === `class`)
callback([...mutation.target.classList])
})
}
const observer = new MutationObserver(mutationCallback)
observer.observe(element, config)
}
// example implementation
// it's up to you to watch for any class changes you desire on any element,
// and make changes to any other element (ie updating and image src) when desired
const changeImageOnToggle = (imageElement, toggleElement) => {
const callback = classArray => {
if (classArray.includes(`active-li`)) {
// update the image element src
}
}
observeClassList(toggleElement, callback)
}
Is this what you want?
$('.active-li').click(function(){
$(this).toggleClass("active");
var new_src = $(this).attr('data-img');
$(".card-image img").attr("src",new_src);
$(".card-image p").text('src of this img is '+new_src);
});
.active{
color:red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="card">
<div class="card-image">
<img src="img/image-1.jpg"/>
<p > </p>
</div>
<ul class="collapsible expandable">
<li data-img="img/image-2.jpg" class="active-li">
Lorem
</li>
<li data-img="img/image-1.jpg" class="active-li">
Lorem
</li>
</ul>
</div>
I used Midos logic and it worked fine ... but how do I return the image back to its original image?
$('.egg').click(function(){
$(this).toggleClass("active");
var new_src = $(this).attr('data-img');
$(".green-egg img").attr("src",new_src);
return;
});

find out which button was pressed in the foreach loop

I return to the user a list of his notes, surely that there may be an indefinite number of them.
I create buttons when the text is longer than 80 characters.
By clicking on this button, I want to catch a click among other buttons and find out all the information about his parent div. How can i do this?
<div class="d-flex flex-wrap">
#foreach (var item in ViewBag.UserTodoList)
{
<div class="card" style="width: 32%; margin-left: 1%; margin-top:1%">
<div class="card-body">
<h5 class="card-title">#item.Name</h5>
#if (item.Body.Length > 80)
{
<p class="card-text">#item.Body.Substring(0, 80)...</p>
<button class="btn btn-info" id="1234">Прочитать</button>
}
else
{
<p class="card-text">#item.Body</p>
}
<a asp-controller="ToDo" asp-action="DeleteTodos" asp-route-todoId="#item.Id" class="btn btn-primary">Удалить</a>
</div>
</div>
}
</div>
Something like this:
const buttons = Array.from(document.querySelectorAll('.card .btn'));
function handleClick() {
const parent = this.parentNode; // here is parent
console.log(parent) // lets log it to console
}
buttons.forEach((button) => { button.addEventListener('click', handleClick);})
BTW, you should add unique class to your buttons. It will be easier to get them by querySelectorAll or getElementsByClassName
I am not expert in this (new one to it too) but how i would do it is add that button some class like ErrorClass and then inside script i would do
$('.ErrorButton').click(function() {
var parent = this.parent(); // With this you get parent div container
});

addEventListener not working on first click

This code is for opening and closing an accordion menu, but the event I added to 'acc' does not work on the first click.
let acc = document.getElementById("acc");
let list = document.getElementById("list");
let rest = document.getElementById("rest");
let nav = document.getElementById("nav");
let line = document.getElementsByClassName("line")
acc.addEventListener("click", () => {
if (list.style.left == '-50%') {
list.style.left = "0";
Array.prototype.forEach.call(line, function(value, index) {
line[index].style.backgroundColor = "lightBlue";
});
} else {
list.style.left = "-50%";
Array.prototype.forEach.call(line, function(value, index) {
line[index].style.backgroundColor = 'rgba(0, 0, 0, 0.781)';
});
}
});
rest.addEventListener("click", () => {
list.style.left = "-50%";
Array.prototype.forEach.call(line, function(value, index) {
line[index].style.backgroundColor = 'rgba(0, 0, 0, 0.781)';
});
})
<nav class="nav" id="nav">
<p class="header">koooooooooooooon</p>
<button class="accordion" id="acc">
<div class="wrap">
<div class="line"></div>
<div class="line"></div>
<div class="line"></div>
</div>
</button>
</nav>
<div class="list" id="list">
<ul class="ul">
<li>home</li>
<li>konkor</li>
<li>konkor arshad</li>
<li>emtehan nahaii</li>
</ul>
</div>
<div class="rest" id="rest">
<p class="p">welcome to our lil app</p>
</div>
I couldn't think of any other way to do this.
For this syndrome the tipical problem is: CSS overrides. If you hide by default via css class your menu, then in the first click the script will detect the oposit what you think it will. Set the initial value in the style attribute, or use classes to determinate the current state of the elements.
A little explanation: if you use only pure JavaScript, it won't take care about the style settings which you set on the Element with classes. It only returns those style which are directly set on the element with the style attribute.
ie. You set via css the Left property of the element to -50%. The JavaScript will return "" if you call the style.left because it can't detect the CSS class' value. Then on second click it will work properly and it will return -50% than 0 than again -50%. This will be invisible because, the style attribute will be set to a value on first click which is the default value you set with a class previously.
Also I want to note, this is not true in case of jQuery. If you using jQuery it detects somehow the attributes on the Elements, which was applied via classes.

How to select specific elements with JS that are dynamically created?

<div class="gallery-container">
<?php while (have_rows('gallery')): ?>
[...]
<div class="toggle-container">
<button class="toggle-button active" onclick="gridView()">Grid</button>
<button class="toggle-button" onclick="listView()">List</button>
</div>
<div class="gallery-items grid-items">
[...Gallery Items...]
</div>
<?php endwhile; ?>
</div>
What would be the best way to select specific elements on a page when the elements are created with a while loop shown above. It's an ever-growing list and elements can also be removed.
In this example I am generating a page full of small galleries together with the toggle buttons for the Grid/List view next to each gallery.
I am trying to make all of those buttons work with just the gallery they are generated together with.
I know how to select them based on their index manually, but I don't know how I could tweak the code to be able to make it work with every small gallery separately.
This is what I came up with to make it work with the first gallery:
<script>
const button = document.getElementsByClassName('toggle-button');
const element = document.getElementsByClassName('gallery-items');
function listView() {
if ( element[0].classList.contains('grid-items') ){
element[0].classList.remove("grid-items");
}
button[0].classList.toggle('active');
button[1].classList.toggle('active');
}
function gridView() {
if ( !element[0].classList.contains('grid-items') ){
element[0].classList.add("grid-items");
}
button[0].classList.toggle('active');
button[1].classList.toggle('active');
}
</script>
You might consider using event delegation instead: add a click listener to .gallery-container. If the clicked target is a .toggle-button, run the appropriate logic, selecting the relevant surrounding elements on click:
document.querySelector('.gallery-container').addEventListener('click', ({ target }) => {
if (!target.matches('.toggle-button')) {
return;
}
const toggleContainer = target.parentElement;
const btns = toggleContainer.children;
if (target === btns[0]) {
btns[0].classList.add('active');
btns[1].classList.remove('active');
} else {
btns[0].classList.remove('active');
btns[1].classList.add('active');
}
const galleryItems = toggleContainer.nextElementSibling;
if (target === btns[0]) {
galleryItems.classList.add('grid-items');
} else {
galleryItems.classList.remove('grid-items');
}
});
.active {
background-color: yellow;
}
.grid-items {
background-color: red;
}
<div class="gallery-container">
<div class="toggle-container">
<button class="toggle-button active">Grid</button>
<button class="toggle-button">List</button>
</div>
<div class="gallery-items grid-items">
[...Gallery Items...]
</div>
<div class="toggle-container">
<button class="toggle-button active">Grid</button>
<button class="toggle-button">List</button>
</div>
<div class="gallery-items grid-items">
[...Gallery Items 2...]
</div>
</div>
Note that there's no need to explicitly test if a classList.contains a particular class before adding it (though, there's no harm in doing so, it's just unnecessary).

Categories

Resources