How can I loop through indexes that rely on multiple functions? - javascript

I am using two types of elements, .element and .summary. The index of both classes correspond to each other, so when a user has a mouseover event on .element, a class should be added to its corresponding .summary. Similarly, that same class should be removed on mouseout.
In other words, I'm looking to have the .styling class only applied to .summary when .element is on mouseover
Without declaring so many variables, I was wondering the most efficient way to loop through these elements.
var abc = document.getElementsByClassName("element");
var xyz = document.getElementsByClassName("summary");
for (let i = 1; i < xyz.length; i++) {
abc[i].addEventListener("mouseover", movein(i), false);
abc[i].addEventListener("mouseout", moveout(i), false);
}
function movein(i) {
xyz[i].classList.add("styling");
}
function moveout(i) {
xyz[i].classList.remove("styling");
}
.element {
cursor: pointer;
}
.styling {
background: red;
}
<div class="element">Element 1</div>
<div class="element">Element 2</div>
<div class="element">Element 3</div>
<div class="summary">Summary 1</div>
<div class="summary">Summary 2</div>
<div class="summary">Summary 3</div>
From what I can tell, these functions run but it doesn't work on mouseover.

You don't actually have to loop, in fact it's probably best you don't. If you have containers for each section of your HTML: elements, and summaries, it's much more efficient to use event delegation to catch element events as they bubble up the DOM, and use one handler to decide how to style your summaries depending on their data attributes.
// Cache the containers
const elements = document.querySelector('#elements');
const summaries = document.querySelector('#summaries');
// Add listeners to the containers
elements.addEventListener('mouseover', handleMouse, false);
elements.addEventListener('mouseout', handleMouse, false);
function handleMouse(e) {
// Destructure the type and id from the element
const { type, target: { dataset: { id } } } = e;
// Find the corresponding summary - this uses
// a template literal to create the query
const summary = summaries.querySelector(`[data-id="${id}"]`);
// And then, depending on the event type, add or remove the style
if (type === 'mouseover') {
summary.classList.add("styling");
} else {
summary.classList.remove("styling");
}
}
.element { cursor: pointer; }
.styling { background: red; }
<div id="elements">
<div data-id="1" class="element">Element 1</div>
<div data-id="2" class="element">Element 2</div>
<div data-id="3" class="element">Element 3</div>
</div>
<div id="summaries">
<div data-id="1" class="summary">Summary 1</div>
<div data-id="2" class="summary">Summary 2</div>
<div data-id="3" class="summary">Summary 3</div>
</div>
Additional documentation
Destructuring assignment
Template literals
Data attributes

Fixing your code to just refer to the functions (not call them) in addEventListener, to loop starting at 0 (not 1), and to use ids to get the corresponding elements...
There's nothing wrong with a short loop.
var abc = document.getElementsByClassName("element");
var xyz = document.getElementsByClassName("summary");
// fixed, was i=1
for (let i = 0; i < xyz.length; i++) {
// fixed, was movein(i), moveout(i)
abc[i].addEventListener("mouseover", movein, false);
abc[i].addEventListener("mouseout", moveout, false);
}
// these get events as params, and can use the event.target.id
// to distinguish which element was triggered
function movein(event) {
let id = event.target.id
xyz[id].classList.add("styling");
}
function moveout(event) {
let id = event.target.id
xyz[id].classList.remove("styling");
}
.element {
cursor: pointer;
}
.styling {
background: red;
}
<div class="element" id="0">Element 1</div>
<div class="element" id="1">Element 2</div>
<div class="element" id="2">Element 3</div>
<div class="summary">Summary 1</div>
<div class="summary">Summary 2</div>
<div class="summary">Summary 3</div>

You could do it like this, and not have to update your html. We programmatically set up an attribute so we can get it's index when we mouse over it. Then we use that index to find the associated summary. Note that you don't pass the i through the event listener - you test for the event.target and pick up that shiny new attribute with event.target.dataset per the code below.
Another bonus here is that we've combined the event listener logic which you can see makes sense. This answer leverages your existing structure, uses less code and is easy to understand.
var abc = document.getElementsByClassName("element");
var xyz = document.getElementsByClassName("summary");
for (let i = 0; i < xyz.length; i++) {
abc[i].setAttribute('data-index', i)
abc[i].addEventListener("mouseover", mousemove);
abc[i].addEventListener("mouseout", mousemove);
}
function mousemove(event) {
let element = xyz[event.target.dataset.index];
if (event.type == 'mouseover') element.classList.add("styling");
else element.classList.remove("styling");
}
.element {
cursor: pointer;
}
.styling {
background: red;
}
<div class="element">Element 1</div>
<div class="element">Element 2</div>
<div class="element">Element 3</div>
<div class="summary">Summary 1</div>
<div class="summary">Summary 2</div>
<div class="summary">Summary 3</div>

Related

How do I wrap adjacent elements of the same class using Javascript (no jQuery)

Everywhere I looked, it seemed that this problem has only been solved using jQuery, which I'm trying to remove completely from my project.
Here's the HTML:
<div class="codeblock"></div>
<div class="codeblock"></div>
<div class="codeblock"></div>
<p></p>
<div class="codeblock"></div>
<div class="codeblock"></div>
<p></p>
<div class="codeblock"></div>
desired result:
<div class="contentBox">
<div class="codeblock"></div>
<div class="codeblock"></div>
<div class="codeblock"></div>
</div>
<p></p>
<div class="contentBox">
<div class="codeblock"></div>
<div class="codeblock"></div>
</div>
<p></p>
<div class="contentBox">
<div class="codeblock"></div>
</div>
And here's how this can be done using jQuery, thanks to the many answers I've found on the topic
const e = '.codeblock';
$(e).not(e + '+' + e).each(function () {
$(this).nextUntil(':not(' + e + ')').addBack().wrapAll('<div class="contentBox" />');
});
Is there a way to replicate this same functionality using vanilla Javascript? I've tried using Element.nextElementSibling and checking if the class matches, but this approach wasn't very elegant and resulted in much more code than the jQuery solution.
Shortest version I could come up with:
let firstDivs = document.querySelectorAll('.codeblock:first-child, :not(.codeblock) + .codeblock');
firstDivs.forEach(function(div) {
let wrapper = document.createElement("div");
wrapper.className = 'wrapper';
div.parentNode.insertBefore(wrapper, div);
while(div.nextElementSibling && div.nextElementSibling.className == 'codeblock') {
wrapper.appendChild(div.nextElementSibling);
}
wrapper.insertBefore(div, wrapper.firstChild);
});
First, select the first .codeblock element out of each "group" - by selecting the element with that class that is the first child of its parent, and all those that do not have a .codeblock element before them.
For each of those elements, insert a new wrapper div before that element, then loop through the following element siblings, as long as they have that same class - and append those to the wrapper. And then afterwards, insert the first item to the beginning of the group. (If we did it before, the following elements would stop being siblings at this point.)
You could do something like this:
// Find all elements that match the class
document.querySelectorAll(`.${e}`).forEach(
// For each elemnt
elem => {
// If it's not the first of the group, skip it
if (elem.previousElementSibling!==null && elem.previousElementSibling.classList.contains(e)){
return;
}
// Find all adjacent elements with the same class
let o = [elem];
while (o[o.length - 1].nextElementSibling.classList.contains(e)) {
o.push(o[o.length - 1].nextElementSibling);
}
// Create a new wrapper element and give it a proper class
let wrapper = document.createElement('div');
wrapper.classList.add('contentBox');
// Insert the new wrapper immediatly before the group
elem.insertAdjacentElement('beforebegin', wrapper);
// Move the contents of the group to inside the wrapper element
wrapper.replaceChildren(...o);
}
)
It's a bit more code, but you can loop through all div and p, check every element and when matched append it to a new or existing div.codeBlock.
const isTargeted = el => el.classList.contains(`codeblock`);
const createWrap = (beforeEl) => beforeEl.insertAdjacentElement(`beforebegin`,
Object.assign(document.createElement(`div`), {className: `contentBox`}));
const divsAndPs = document.querySelectorAll(`div, p`);
divsAndPs.forEach(
(elem, i, self) => {
if (!i || isTargeted(elem)) {
const wrap = i && self[i-1].closest(`.contentBox`) ||
createWrap(elem);
wrap.appendChild(elem);
}
}
);
.contentBox {
color: green;
}
.contentBox .codeblock {
margin-left: 2rem;
}
.contentBox:before {
content: 'I am the great contentBox, here are my codeblocks:';
color: grey;
}
<div class="codeblock">x</div>
<div class="codeblock">x</div>
<div class="codeblock">x</div>
<p>paragraph</p>
<div class="codeblock">x</div>
<div class="codeblock">x</div>
<p>paragraph</p>
<div class="codeblock">x</div>
This can be a helper function (see also this stackblitz project):
const wrapIt = () => wrapAll(
document.querySelectorAll(`.codeblock, .codeblock + :not(.codeblock)`),
`codeblock`,
Object.assign(document.createElement(`div`), {className: `contentBox`}) );
setTimeout( wrapIt, 1000 );
function wrapAll(elems2Wrap, groupByClass, wrapperElement) {
const wrap = elem =>
elem.classList?.contains(groupByClass) && (elem
.previousElementSibling?.closest(`.${wrapperElement.className}`) ||
elem.insertAdjacentElement(`beforebegin`, wrapperElement.cloneNode())
).appendChild(elem);
elems2Wrap.forEach(wrap);
}
.contentBox {
color: green;
}
.contentBox .codeblock {
margin-left: 2rem;
}
.contentBox:before {
content: 'Wrapped!';
color: grey;
}
<div class="codeblock otherClass">x</div>
<div class="codeblock">x</div>
<div class="codeblock">x</div>
<p>Just a paragraph</p>
<div class="codeblock">x</div>
<div class="codeblock otherClass">x</div>
<p>Just a paragraph</p>
<div class="codeblock">x</div>

How to make tabs with pure JS? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
i'am trying to do a "tab" menu with only pure JS CSS and HTML, where basically i have 7 divs with content and 7 "buttons" which should open the matching div. The strategy i want to use is to put all the divs with the "hidden" or the "Display: none" attribute stacked in the same spot and then, when i click on button it turns it´s matching div to visible. The problem i am facing is how to tell the button which div it should open (using arrays instead of doing it manually), and how to set it back to invisible when i click in other div (i was thinking about an if() that just turns on the visibility if the number of the button selected matches the number of the div, but supposedly every button has a div, so i am confused).
I think you want something like this?
document.addEventListener('click', ({ target: { dataset: { id = '' }}}) => {
if (id.length > 0) {
document.querySelectorAll('.tab').forEach(t => t.classList.add('hidden'));
document.querySelector(`#${id}`).classList.remove('hidden');
}
});
.hidden {
display:none;
}
<button data-id="tab1">tab 1</button>
<button data-id="tab2">tab 2</button>
<div id="tab1" class="tab">Tab 1</div>
<div id="tab2" class="tab hidden">Tab 2</div>
But the reality is we probably don't want to actually use button.
So lets change that up.
const tabClick = ({ target }) => {
const { dataset: { id = '' }} = target;
document.querySelectorAll('.tab').forEach(t => t.classList.remove('selected'));
target.classList.add('selected');
document.querySelectorAll('.tab-panel').forEach(t => t.classList.add('hidden'));
document.querySelector(`#${id}`).classList.remove('hidden');
};
const bindTabs = () => {
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', tabClick);
})
};
// Belts and braces, lets ensure our DOM is loaded and only assign click to the `tabs`
document.addEventListener('DOMContentLoaded', () => {
bindTabs();
});
.tab {
display: inline-block;
background-color: grey;
padding: 0.75rem;
color: #fff;
}
.selected {
background-color: black;
}
.tab-panel {
border: 2px solid black;
min-height: 50px;
max-width: 250px;
padding: 1rem;
}
.hidden {
display:none;
}
<div data-id="tab1" class="tab selected">tab 1</div>
<div data-id="tab2" class="tab">tab 2</div>
<div data-id="tab3" class="tab">tab 3</div>
<div data-id="tab4" class="tab">tab 4</div>
<div id="tab1" class="tab-panel">Tab 1</div>
<div id="tab2" class="tab-panel hidden">Tab 2</div>
<div id="tab3" class="tab-panel hidden">Tab 3</div>
<div id="tab4" class="tab-panel hidden">Tab 4</div>
So how can we approach this with basic javascript.
const tabCount = 4; // If we add a new tab, increase.
const tabClick = (event) => {
const tabButtonClicked = event.target;
const id = event.target.dataset.id;
// First remove selected and hide all tabs
for(let i = 1; i <= tabCount; i++) {
let tabButtonID = "#tabButton" + i;
let tabButton = document.querySelector(tabButtonID);
let tabID = "#" + tabButton.dataset.id;
let tab = document.querySelector(tabID);
tabButton.classList.remove("selected");
tab.classList.add("hidden");
}
// Now we set selected and show the selected tab.
document.querySelector("#" + id).classList.remove("hidden");
tabButtonClicked.classList.add("selected");
};
const bindTabs = () => {
// Loop through number of tabs and add a click event.
for(let i = 1; i <= tabCount; i++) {
let tabButtonID = "#tabButton" + i;
let tabButton = document.querySelector(tabButtonID);
tabButton.addEventListener('click', tabClick);
}
};
// Belts and braces, lets ensure our DOM is loaded and only assign click to the `tabs`
document.addEventListener('DOMContentLoaded', () => {
bindTabs();
});
.tab {
display: inline-block;
background-color: grey;
padding: 0.75rem;
color: #fff;
}
.selected {
background-color: black;
}
.tab-panel {
border: 2px solid black;
min-height: 50px;
max-width: 250px;
padding: 1rem;
}
.hidden {
display:none;
}
<div id="tabButton1" data-id="tab1" class="tab selected">tab 1</div>
<div id="tabButton2" data-id="tab2" class="tab">tab 2</div>
<div id="tabButton3" data-id="tab3" class="tab">tab 3</div>
<div id="tabButton4" data-id="tab4" class="tab">tab 4</div>
<div id="tab1" class="tab-panel">Tab 1</div>
<div id="tab2" class="tab-panel hidden">Tab 2</div>
<div id="tab3" class="tab-panel hidden">Tab 3</div>
<div id="tab4" class="tab-panel hidden">Tab 4</div>

Javascript carousel, display random div and change every 3 seconds

Im trying to create a Javascript carousel that displays a random div and changes every 3 seconds.
I have the random div displaying on load but unsure what to add make it change to the next one.
var elems = $("div");
if (elems.length) {
var keep = Math.floor(Math.random() * elems.length);
for (var i = 0; i < elems.length; ++i) {
if (i !== keep) {
$(elems[i]).hide();
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="content1">This is content 1</div>
<div id="content2">This is content 2</div>
<div id="content3">This is content 3</div>
<div id="content4">This is content 4</div>
<div id="content5">This is content 5</div>
<div id="content6">This is content 6</div>
put your code in an interval like this :
setInterval(function(){
var elems = $("div");
if (elems.length) {
var keep = Math.floor(Math.random() * elems.length);
for (var i = 0; i < elems.length; ++i) {
if (i !== keep) {
$(elems[i]).hide();
}
else{
$(elems[i]).show();
}
}
}
},1000); // change every 1s ;
================================
UPDATE
to hide all divs before interval starts u have two way .
first way is to use css . u can add a css class to divs named "hide-at-start" and define it like this :
.hide-at-start
{
display:none;
}
and add it to divs :
<div id="content1" class="hide-at-start">This is content 1</div>
<div id="content2" class="hide-at-start">This is content 2</div>
<div id="content3" class="hide-at-start">This is content 3</div>
<div id="content4" class="hide-at-start">This is content 4</div>
<div id="content5" class="hide-at-start">This is content 5</div>
<div id="content6" class="hide-at-start">This is content 6</div>
second way is to do it via javascript like this :
function showRandomDiv(){
var elems = $("div");
if (elems.length) {
var keep = Math.floor(Math.random() * elems.length);
for (var i = 0; i < elems.length; ++i) {
if (i !== keep) {
$(elems[i]).hide();
}
else{
$(elems[i]).show();
}
}
}
}
showRandomDiv(); // runs once
setInterval(showRandomDiv,1000); // change every 1s ;
i prefer the first solution however .
========================
UPDATE
to add animations there are many ways . for instance u can use css #keyframes like this :
#keyframes fade-in-from-left {
0% {left: 0px;opacity: 0;}
50% {left: 20px;opacity: 0.5;}
100%{left:0px;opacity: 1;}
}
.fade-in-from-left{
position: relative;
animation: fade-in-from-left 0.5s forwards;
}
then add fade-in-from-left to your divs like this :
<div class="hide-at-start fade-in-from-left">This is content 1</div>
<div class="hide-at-start fade-in-from-left">This is content 2</div>
<div class="hide-at-start fade-in-from-left">This is content 3</div>
<div class="hide-at-start fade-in-from-left">This is content 4</div>
<div class="hide-at-start fade-in-from-left">This is content 5</div>
<div class="hide-at-start fade-in-from-left">This is content 6</div>

Add class to an element without an id

I have a list of items:
<div class="crew-item>
<div class="crew-grid"></div>
<div class="crew-detail></div>
</div>
<div class="crew-item>
<div class="crew-grid"></div>
<div class="crew-detail></div>
</div>
<div class="crew-item>
<div class="crew-grid"></div>
<div class="crew-detail></div>
</div>
When I click on a selected 'crew-grid' I'd like to add a class ('active') to its 'crew-item' parent, but I have no idea how to achieve that using vanilla js or jQuery.
The goal is to reveal the 'crew-detail' part, with active class added to its parent.
Like this?:
$('.crew-grid').on('click', function () {
$(this).closest('.crew-item').addClass('active');
});
Basically, starting from the clicked element, get the closest ancestor element which matches that selector. You don't need an id to target an element, just a way to identify it based on the information you have (in this case the clicked element).
If you want to de-activate other elements at the same time:
$('.crew-grid').on('click', function () {
$('.crew-item').removeClass('active');
$(this).closest('.crew-item').addClass('active');
});
Using jQuery :
$('.crew-grid').click(function() {
$(this).closest('.crew-item').addClass('active');
});
Use Document.querySelectorAll()
var crews = document.querySelectorAll('.crew-item');
if (crews) {
for (var i = 0; i < crews.length; i++) {
var grid = crews[i].querySelector('.crew-grid');
grid.addEventListener('click', toggleActive, false);
}
}
function toggleActive() {
var grids = document.querySelectorAll('.crew-item');
for (var i = 0; i < grids.length; i++) {
if (grids[i].classList.contains('active')) {
grids[i].classList.remove('active');
}
}
this.parentNode.classList.add('active');
}
.crew-item.active {
background: #DDD;
}
.crew-grid:hover {
cursor: pointer;
background: #eee;
}
<div class="crew-item active">
<div class="crew-grid">crew-grid</div>
<div class="crew-detail">crew-detail</div>
</div>
<br>
<div class="crew-item">
<div class="crew-grid">crew-grid</div>
<div class="crew-detail">crew-detail</div>
</div>
<br>
<div class="crew-item">
<div class="crew-grid">crew-grid</div>
<div class="crew-detail">crew-detail</div>
</div>

Change child element of array item (syntax)

I have a few divs which are using the same class.
Inside the divs are three more divs with identical classes.
<div class="plane">
<div class="win1">Lorem ipsum</div>
<div class="win2">Dolor sit</div>
<div class="win3">amet.</div>
</div>
<div class="plane">
<div class="win1">Lorem ipsum</div>
<div class="win2">Dolor sit</div>
<div class="win3">amet.</div>
</div>
var allPlanes = $('.plane');
for (var i = 0; i < allPlanes.length; i++) {
var onePlane = allPlanes[i];
var baseHeight = 10;
$(onePlane + " .win1").css("height", parseInt(baseHeight*1));
$(onePlane + " .win2").css("height", parseInt(baseHeight*2));
$(onePlane + " .win3").css("height", parseInt(baseHeight*3));
}
(Don't mind about the names. It's just an example...)
Now I made an array with the outside divs and I can select the single divs inside. But I did not get the right syntax for the child divs inside.
Can anyone help?
My Fiddle: http://jsfiddle.net/SchweizerSchoggi/559xvww6/
Change you script to this:
var allPlanes = $('.plane');
var baseHeight = 10;
$(".plane > .win1").css("height", parseInt(baseHeight*1)+"px");
$(".plane > .win2").css("height", parseInt(baseHeight*2)+"px");
$(".plane > .win3").css("height", parseInt(baseHeight*3)+"px");
You don't need the for loop in such a case.
A prettier way:
var baseHeight = 10;
for (var i = 1; i <= 3; i++) {
$(".plane > .win"+i).css("height", parseInt(baseHeight*i)+"px");
}
http://jsfiddle.net/559xvww6/3/
If you don't want to use a for loop and want to dinamically configure from an array:
var baseHeight = 10;
$.map([1,2,3], function(i) {
$(".plane > .win"+i).css("height", parseInt(baseHeight*i)+"px");
});
http://jsfiddle.net/559xvww6/10/
Edit:: Just a side note: all these approachs are valid, but that doesn't mean that they are the best / most efficient ones. Feel free to use the one you like the most, understand it and try to use it or adapt it to your very personal situation. The "easiest" approach is surely the first one, but it is also the longest one.
isn't this one is better:
var base = 10;
$('.plane > div').css('height', function(){
return base*($(this).index()+1)
});
.plane {
background-color: #ccc;
border: solid 1px #cdcdcd;
margin-bottom: 15px;
}
.plane > .win1 { background-color: #ddd; }
.plane > .win2 { background-color: #eee; }
.plane > .win3 { background-color: #fff; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="plane">
<div class="win1">Lorem ipsum</div>
<div class="win2">Dolor sit</div>
<div class="win3">amet.</div>
</div>
<div class="plane">
<div class="win1">Lorem ipsum</div>
<div class="win2">Dolor sit</div>
<div class="win3">amet.</div>
</div>
You cannot use + operator between a jQuery object and a string.
The correct way to do it is this:
$(".win1", onePlane).css("height", parseInt(baseHeight*1));
$(".win2", onePlane).css("height", parseInt(baseHeight*2));
$(".win3", onePlane).css("height", parseInt(baseHeight*3));
Each of these queries translates to: select all elements with .winX that are inside the jQuery object onePlane.
I would use all the same class names inside the nest and then just do $('.plane:eq(0) .win:eq(2)').html()
alert( $('.plane:eq(0) .win:eq(2)').html() );
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div class="plane">
<div class="win">Lorem ipsum</div>
<div class="win">Dolor sit</div>
<div class="win">amet.</div>
</div>
<div class="plane">
<div class="win">Lorem ipsum</div>
<div class="win">Dolor sit</div>
<div class="win">amet.</div>
</div>
if your classes are fixed then you can do with this code
$(".win1", $(".plane")).css("height", parseInt(baseHeight*1));
$(" .win2", $(".plane")).css("height", parseInt(baseHeight*2));
$(" .win3", $(".plane")).css("height", parseInt(baseHeight*3));
You can do using each loop of plane class.
$('.plane').each(function(){
baseHeight = 10;
$(this).find(".win1").css("height", parseInt(baseHeight*1));
$(this).find(".win2").css("height", parseInt(baseHeight*2));
$(this).find(".win3").css("height", parseInt(baseHeight*3));
});
Demo

Categories

Resources