I recently started to study JS and wanted to do a clickable tab showing different text with forEach method, but unfortunately i'm struggling!
const buttons = document.querySelectorAll('.button');
const texts = document.querySelectorAll('.text');
buttons.forEach(button => {
button.addEventListener('click', (event) => {
//make buttons active
buttons.forEach((button) => {
button.classList.remove('active')
})
event.target.classList.add('active')
//show text
texts.forEach(text => {
text.classList.remove('text');
})
});
})
<button class="button">Tab1</button>
<button class="button">Tab2</button>
<button class="button">Tab3</button>
<div class="text-tabs">
<div class="text">
<h4>Text from tab1</h4>
</div>
<div class="text">
<h4>Text from tab2</h4>
</div>
<div class="text">
<h4>Text from tab3</h4>
</div>
The CSS already has the display: none and then block
a way to do it is to add a data attribute to your different tab (for sample an id or an index) <button class="button" data-tab="1">Tab1</button>
and recover this value when you click on element
event.target.dataset.tab
const buttons = document.querySelectorAll('.button');
const texts = document.querySelectorAll('.text');
buttons.forEach(button => {
button.addEventListener('click', (event) => {
//make buttons active
buttons.forEach((button) => {
button.classList.remove('active')
})
event.target.classList.add('active')
texts.forEach(text => text.classList.add('hidden'));
const tabToShow = document.querySelector(`.text:nth-child(${event.target.dataset.tab})`);
if (tabToShow) {
tabToShow.classList.remove('hidden');
}
});
})
.hidden {
display: none;
}
button.active {
background: red;
}
<button class="button" data-tab="1">Tab1</button>
<button class="button" data-tab="2">Tab2</button>
<button class="button" data-tab="3">Tab3</button>
<div class="text-tabs">
<div class="text hidden">
<h4>Text from tab1</h4>
</div>
<div class="text hidden">
<h4>Text from tab2</h4>
</div>
<div class="text hidden">
<h4>Text from tab3</h4>
</div>
</div>
Related
I'm so new that I don't know how to make a scoring system in JavaScript. All I need is; if the drag1 is dropped on div1, 1 score must add up. Here's my index.php, css, and js files
function submit() {
document.getElementById('handler').style.display = 'block'
const quest1 = document.getElementById('div1')
const ans1 = document.getElementById('drag1')
const totalScore = document.getElementById('score')
const score = 0
totalScore.textContent = score;
}
<div id="drop_area">
<div id="div1 drop" ondrop="drop(event);" ondragover="allowDrop(event);" value="10000 Years"></div>
<div id="div2 drop" ondrop="drop(event);" ondragover="allowDrop(event);" value="Evil Queen"></div>
<div id="div3 drop" ondrop="drop(event);" ondragover="allowDrop(event);" value="12"></div>
<div id="div4 drop" ondrop="drop(event);" ondragover="allowDrop(event);" value="Nana"></div>
<div id="div5 drop" ondrop="drop(event);" ondragover="allowDrop(event);" value="Maurice"></div>
</div>
<div id="drag_options">
<div id="wrapper">
<div id="drag1 option" draggable="true" ondragstart="drag(event)">Evil Queen</div>
<div id="drag2 option" draggable="true" ondragstart="drag(event)">Maurice</div>
<div id="drag3 option" draggable="true" ondragstart="drag(event)">Nyla</div>
<div id="drag4 option" draggable="true" ondragstart="drag(event)">Cruela Devil</div>
<div id="drag5 option" draggable="true" ondragstart="drag(event)">4</div>
<div id="drag6 option" draggable="true" ondragstart="drag(event)">Moris</div>
<div id="drag7 option" draggable="true" ondragstart="drag(event)">10000 Years</div>
<div id="drag8 option" draggable="true" ondragstart="drag(event)">1000 Years</div>
<div id="drag9 option" draggable="true" ondragstart="drag(event)">12</div>
<div id="drag10 option" draggable="true" ondragstart="drag(event)">Nana</div>
</div>
</div>
<div id="submit_button">
<button type="submit" id="submit" onclick="submit()">Submit</button>
</div>
NEVER call anything in a form id or name=submit. Also do not call your function submit.
You do not have a form,so no need to use a submit button. Just have <button id="run" type="button">Run</button>.
Use eventListeners and delegation
Here is a full version for you to study
I took the drag/drop from MDN
I have not added error handling when the same answer is dropped twice.
I guessed a point calculation
window.addEventListener("DOMContentLoaded", () => { // when page has loaded
const target = document.getElementById("drop_area");
const source = document.getElementById("drag_options");
const resetBut = document.getElementById("reset");
const runBut = document.getElementById("run");
const answerDiv = document.getElementById("answers");
let dragged = null;
source.addEventListener("dragstart", (event) => {
// store a ref. on the dragged elem
dragged = event.target;
});
target.addEventListener("dragover", (event) => {
// prevent default to allow drop
event.preventDefault();
});
target.addEventListener("drop", (event) => {
// prevent default action (open as link for some elements)
event.preventDefault();
// move dragged element to the selected drop target
if (event.target.dataset.value) {
// dragged.parentNode.removeChild(dragged);
event.target.textContent = dragged.textContent;
}
});
const pointsPerCorrect = 5;
runBut.addEventListener("click", () => {
const correct = [...target.querySelectorAll("div")].filter(div => div.textContent === div.dataset.value); // run filter on the child divs
const numberOfCorrect = correct.length;
const answers = correct.map(div => div.textContent).join(", ");
answerDiv.innerHTML = `You had ${numberOfCorrect} answer${numberOfCorrect===1?"":"s"} correct.<br/> ${answers}`; // using template strings
});
resetBut.addEventListener("click", () => {
target.querySelectorAll("div").forEach((div,i) => div.textContent = (i+1));
});
});
#drop_area {
background-color: green;
}
#drag_options {
background-color: red;
}
#drop_area div {
padding: 2px;
border: 1px solid black;
}
#drag_options div {
padding: 2px;
border: 1px solid black;
}
<div id="drop_area">
<div data-value="10000 Years">1</div>
<div data-value="Evil Queen">2</div>
<div data-value="12">3</div>
<div data-value="Nana">4</div>
<div data-value="Maurice">5</div>
</div>
<hr/>
<div id="drag_options">
<div draggable="true">Evil Queen</div>
<div draggable="true">Maurice</div>
<div draggable="true">Nyla</div>
<div draggable="true">Cruella de Vil</div>
<div draggable="true">4</div>
<div draggable="true">Moris</div>
<div draggable="true">10000 Years</div>
<div draggable="true">1000 Years</div>
<div draggable="true">12</div>
<div draggable="true">Nana</div>
</div>
<div id="submit_button">
<button type="button" id="reset">Reset</button>
<button type="button" id="run">Submit</button>
</div>
<div id="handler">
<div id="score_board">
<div id="wrap">
<h1>SCORE</h1>
<div id="answers"></div>
</div>
</div>
</div>
Can anyone advice me why the allTabs forEach wont work when I am trying to add the .none class
?
When clicking second item the other class should disappear but it doesn't,
let allTabs = document.querySelectorAll('.none');
function setActiveTab(tab) {
allTabs.forEach((tabs) => {
tabs.classList.add("none")
})
let singleTab = document.querySelector(`.${tab}`)
singleTab.classList.toggle("block")
}
.none {
display: none;
}
.block {
display: block;
}
<div>
<button onclick="setActiveTab('first')">
Test1
</button>
<button onclick="setActiveTab('second')">
Test2
</button>
</div>
<div class="none first">
first tab
</div>
<div class="none second">
second tab
</div>
A better approach is just to remove the none class on the current tab. You won't need the block class.
let allTabs = document.querySelectorAll('.none');
function setActiveTab(tab) {
allTabs.forEach((tabs) => {
tabs.classList.add("none")
})
let singleTab = document.querySelector(`.${tab}`)
singleTab.classList.remove("none")
}
.none {
display: none;
}
<div>
<button onclick="setActiveTab('first')">
Test1
</button>
<button onclick="setActiveTab('second')">
Test2
</button>
</div>
<div class="none first">
first tab
</div>
<div class="none second">
second tab
</div>
if you look at the inspector, the class none is always present.
In the below link, there is a add more button, i want the add more to create the same input field with a delete button associated with it, but i would like to do it all with native js if possible.
https://codepen.io/aazim-khaki/pen/vYZmMRq
Current JS :
$(function() {
$(".btn-copy").on('click', function() {
var ele = $(this).closest('.example-2').clone(true);
ele.find('input').val('')
if (ele.find('button').length < 2) {
let btn = document.createElement("button");
btn.innerHTML = "Delete";
btn.onclick = (e) => {
e.preventDefault()
ele.remove()
}
ele[0].appendChild(btn);
}
$(this).closest('.example-2').after(ele);
})
})
Delegate
I moved the form tag and gave the button a delete class
window.addEventListener("load", function() {
document.querySelector(".row").addEventListener("click", function(e) {
const tgt = e.target;
if (tgt.classList.contains('delete')) {
tgt.closest('.example-2').remove()
} else if (tgt.classList.contains('btn-copy')) {
const ele = tgt.closest(".example-2").cloneNode(true);
ele.querySelector("input").value = "";
if (ele.querySelectorAll("button").length < 2) {
let btn = document.createElement("button");
btn.innerHTML = "Delete";
btn.classList.add("delete");
ele.appendChild(btn);
}
tgt.closest(".card-body").appendChild(ele)
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<h5 class="card-title">Add Class</h5>
</div>
<form action="#">
<div class="card-body">
<div class="example-2 form-group row">
<!--<label class="col-form-label col-md-2">Input Addons</label>-->
<div class="col-xs-2">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Class Name</span>
</div>
<input class="form-control" type="text">
<div class="input-group-append">
<button class="btn-copy btn btn-primary" type="button">Add More</button>
</div>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-xs-2">
<button class="btn btn-primary" type="button">Submit</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
Use a container and event delegation so you only use one listener rather than attaching a listener to each remove button.
This is a very simple example but the principles are the same.
// Cache your elements
const container = document.querySelector('#container');
const add = document.querySelector('button');
// Add your container an add button listeners
container.addEventListener('click', handleEvent, false);
add.addEventListener('click', handleAdd(), false);
// If a remove button is clicked, find the
// the closest div wrapper and remove it from
// the container
function handleEvent(e) {
const { id } = e.target.dataset;
const row = e.target.closest('.row');
container.removeChild(row);
}
// `handleAdd` returns a function (closure) that
// is used for the add listener rather than
// maintaining a global variable.
// We initialise the id at this point
function handleAdd(id = 0) {
// And now return the function that will be called
// when the add button is clicked
// For the purposes of this example it simply adds new HTML
// to the container, and then increases the id
return function() {
const html = `<div class="row"><input value="${id}" /><button data-id="${id}">Remove</button></div>`;
container.insertAdjacentHTML('beforeend', html);
++id;
}
}
<button>Add</button>
<div id="container"></div>
I'm having a hard time with some JS DOM traversal. I'm stuck with html that's something like this:
<h2>Header 1</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Smith</h4>
</div>
</div>
<h2>Header 2</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">Emily Jones</h4>
</div>
</div>
This is all hidden by default. I'm trying to use a text field so that if it matches an h4 person-name, it displays the some-content container, as well as the preceding h2. I can make it work for the some-content bit, but I'm having trouble targeting the h2 that's above it. I've tried various combinations of jQuery parent(), siblings(), and prev(). I do not have the ability to add additional class names.
Edit: here is the script I have for the text field event:
$('#text-field').keyup(function() {
var nameSearch = $(this).val().toUpperCase();
$('.person-name').each(function() {
var x = $(this).text().toUpperCase();
if (x.includes(nameSearch)) {
$(this).prev('h2').show();
$(this).closest('.some-content').show();
}
})
});
Edit 2:
I apologize, my code example was oversimplified. Some very good answers by the way. If for example there was a search done for Emily Jones in this bit, would there need to be something extra done?
<div class="container">
<h2>Header 1</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Smith</h4>
</div>
<div class="inner-content">
<h4 class="person-name">Emily Jones</h4>
</div>
</div>
</div>
If the header/content is not nested withing a wrapping div, you will need to step over every two child nodes and toggle class.
const triggerEvent = (el, eventName) => {
var event = document.createEvent('HTMLEvents');
event.initEvent(eventName, true, false);
el.dispatchEvent(event);
};
const
search = document.querySelector('.search'),
container = document.querySelector('.container');
const onSearch = (e) => {
const
searchValue = e.target.value,
nodes = container.children;
for (let i = 0; i < nodes.length; i += 2) {
const
h2 = nodes[i],
someContent = nodes[i + 1],
matches = someContent.querySelector('.person-name').textContent === searchValue;
h2.classList.toggle('hidden', !matches);
someContent.classList.toggle('hidden', !matches);
}
};
search.addEventListener('change', onSearch);
triggerEvent(search, 'change');
.hidden {
color: #DDD; /* Replace with -: display: none */
}
<input type="text" class="search" value="Emily Jones" />
<div class="container">
<h2>Header 1</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Smith</h4>
</div>
</div>
<h2>Header 2</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">Emily Jones</h4>
</div>
</div>
</div>
Alternatively, you can start with the names and work your way back to the corresponding h2.
const h2 = child
.closest('.inner-content')
.closest('.some-content')
.previousElementSibling; // h2
const triggerEvent = (el, eventName) => {
var event = document.createEvent('HTMLEvents');
event.initEvent(eventName, true, false);
el.dispatchEvent(event);
};
const
search = document.querySelector('.search'),
container = document.querySelector('.container');
const onSearch = (e) => {
const searchValue = e.target.value;
[...container.querySelectorAll('.person-name')].forEach(child => {
const
matches = child.textContent === searchValue,
h2 = child.closest('.inner-content')
.closest('.some-content').previousElementSibling;
[child, h2].forEach(el => el.classList.toggle('hidden', !matches));
});
};
search.addEventListener('change', onSearch);
triggerEvent(search, 'change');
.hidden {
color: #DDD; /* Replace with -: display: none */
}
<input type="text" class="search" value="Emily Jones" />
<div class="container">
<h2>Header 1</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Smith</h4>
</div>
</div>
<h2>Header 2</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">Emily Jones</h4>
</div>
</div>
</div>
Edit
Here is an altered version of the first example. If you have multiple names within .some-content you will have to find all the names that match and keep the parent, if at least one child matches.
const triggerEvent = (el, eventName) => {
var event = document.createEvent('HTMLEvents');
event.initEvent(eventName, true, false);
el.dispatchEvent(event);
};
const
search = document.querySelector('.search'),
container = document.querySelector('.container');
const onSearch = (e) => {
const
searchValue = e.target.value,
nodes = container.children;
for (let i = 0; i < nodes.length; i += 2) {
const
h2 = nodes[i],
someContent = nodes[i + 1],
names = [...someContent.querySelectorAll('.person-name')],
found = names.filter(name => name.textContent === searchValue);
h2.classList.toggle('hidden', found.length === 0);
names.forEach(name => {
const matches = name.textContent === searchValue;
name.closest('.inner-content').classList.toggle('hidden', !matches);
});
}
};
search.addEventListener('change', onSearch);
triggerEvent(search, 'change');
.hidden {
color: #DDD; /* Replace with -: display: none */
}
<input type="text" class="search" value="Emily Jones" />
<div class="container">
<h2>Header 1</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Smith</h4>
</div>
<div class="inner-content">
<h4 class="person-name">Emily Jones</h4>
</div>
</div>
<h2>Header 2</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Doe</h4>
</div>
<div class="inner-content">
<h4 class="person-name">Erica Jones</h4>
</div>
</div>
</div>
I need for help, how to change parent div from each child div i have html like bellow
<div id="parent1">
<div class="toParent3">
</div>
<div class="toParent2">
</div>
<div class="toParent4">
</div>
</div>
<div id="parent2">
//content
</div>
<div id="parent3">
//content
</div>
i want to do ,when the first load i wanna show only #parent1 and its child, and then when child's #parent1 is clicked, I want to change whole #parent1 to #parent2 and hide all div except #parent2
You can use append, this code is only for parent2 as per your example.
$('.toParent2').click(function(){
$('.parent2').append($('.toParent2'));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="parent1">
<div class="toParent3">
Child to parent 3
</div>
<div class="toParent2">
Child to parent 2
</div>
<div class="toParent4">
Child to parent 4
</div>
</div>
<div class="parent2">
//content
</div>
<div class="parent3">
//content
</div>
here's a vanilla js solution that is a bit more generic.
let all = document.getElementsByClassName('movable');
function hideParents() {
Array.prototype.forEach.call(document.getElementsByClassName('_parent_'), (e) => {
e.classList.add('hidden');
});
}
Array.prototype.forEach.call(document.getElementsByClassName('toParent1'), (e) => {
console.log('lols', e.addEventListener);
e.addEventListener('click', () => {
hideParents();
Array.prototype.forEach.call(all, (e2) => {
setTimeout(() => {
const p = document.getElementById('parent1');
p.appendChild(e2);
p.classList.remove('hidden');
});
});
});
});
Array.prototype.forEach.call(document.getElementsByClassName('toParent2'), (e) => {
e.addEventListener('click', () => {
hideParents();
Array.prototype.forEach.call(all, (e2) => {
setTimeout(() => {
const p = document.getElementById('parent2');
p.appendChild(e2);
p.classList.remove('hidden');
});
});
});
});
Array.prototype.forEach.call(document.getElementsByClassName('toParent3'), (e) => {
e.addEventListener('click', () => {
hideParents();
Array.prototype.forEach.call(all, (e2) => {
setTimeout(() => {
const p = document.getElementById('parent3');
p.appendChild(e2);
p.classList.remove('hidden');
});
});
});
});
._parent_.hidden {
display: none;
}
<div class="_parent_" id="parent1">
I'm parent1
<div class="movable toParent3">To3
</div>
<div class="movable toParent2">To2
</div>
<div class="movable toParent1">To1
</div>
</div>
<div class="_parent_ hidden" id="parent2">
I'm parent2
</div>
<div class="_parent_ hidden" id="parent3">
I'm parent3
</div>
not that removing setTimeout() there's a glitch happening, the elements are moved correctly but the DOM is refreshed too early
here's another solution, that replaces the text of the parent instead of moving the children.